Dynamic app shortcuts

This commit is contained in:
Koitharu
2020-03-15 18:17:45 +02:00
parent 402c66ae87
commit 127779f783
5 changed files with 98 additions and 15 deletions

View File

@@ -126,12 +126,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
R.id.action_shortcut -> {
manga?.let {
lifecycleScope.launch {
if (!ShortcutManagerCompat.requestPinShortcut(
this@MangaDetailsActivity,
ShortcutUtils.createShortcutInfo(this@MangaDetailsActivity, it),
null
)
) {
if (!ShortcutUtils.requestPinShortcut(this@MangaDetailsActivity, manga)) {
Snackbar.make(
pager,
R.string.operation_not_supported,

View File

@@ -1,17 +1,20 @@
package org.koitharu.kotatsu.ui.main.list.history
import android.os.Build
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import moxy.InjectViewState
import moxy.presenterScope
import org.koin.core.get
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.ui.common.BasePresenter
import org.koitharu.kotatsu.ui.main.list.MangaListView
import org.koitharu.kotatsu.utils.ShortcutUtils
@InjectViewState
class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
@@ -58,6 +61,9 @@ class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
withContext(Dispatchers.IO) {
repository.clear()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutUtils.clearAppShortcuts(get())
}
viewState.onListChanged(emptyList())
} catch (_: CancellationException) {
} catch (e: Throwable) {
@@ -77,6 +83,9 @@ class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
withContext(Dispatchers.IO) {
repository.delete(manga)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutUtils.removeAppShortcut(get(), manga)
}
viewState.onItemRemoved(manga)
} catch (_: CancellationException) {
} catch (e: Throwable) {

View File

@@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.*
import android.widget.Toast
@@ -14,6 +15,8 @@ import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moxy.MvpDelegate
import moxy.ktx.moxyPresenter
import org.koin.core.inject
@@ -32,6 +35,7 @@ import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment
import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ShortcutUtils
import org.koitharu.kotatsu.utils.anim.Motion
import org.koitharu.kotatsu.utils.ext.*
@@ -84,6 +88,13 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
presenter.init(state.manga)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
GlobalScope.launch {
safe {
ShortcutUtils.addAppShortcut(applicationContext, state.manga)
}
}
}
}
}

View File

@@ -2,12 +2,18 @@ package org.koitharu.kotatsu.utils
import android.app.ActivityManager
import android.content.Context
import androidx.core.content.getSystemService
import android.content.pm.ShortcutManager
import android.media.ThumbnailUtils
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import coil.Coil
import coil.api.get
import coil.size.PixelSize
import coil.size.Scale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
@@ -18,14 +24,65 @@ import org.koitharu.kotatsu.utils.ext.safe
object ShortcutUtils {
suspend fun createShortcutInfo(context: Context, manga: Manga): ShortcutInfoCompat {
suspend fun requestPinShortcut(context: Context, manga: Manga?): Boolean {
return manga != null && ShortcutManagerCompat.requestPinShortcut(
context,
buildShortcutInfo(context, manga).build(),
null
)
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
suspend fun addAppShortcut(context: Context, manga: Manga) {
val id = manga.id.toString()
val builder = buildShortcutInfo(context, manga)
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
val limit = manager.maxShortcutCountPerActivity
val shortcuts = manager.dynamicShortcuts
for (shortcut in shortcuts) {
if (shortcut.id == id) {
builder.setRank(shortcut.rank + 1)
manager.updateShortcuts(listOf(builder.build().toShortcutInfo()))
return
}
}
builder.setRank(1)
if (shortcuts.isNotEmpty() && shortcuts.size >= limit) {
manager.removeDynamicShortcuts(listOf(shortcuts.minBy { it.rank }!!.id))
}
manager.addDynamicShortcuts(listOf(builder.build().toShortcutInfo()))
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun removeAppShortcut(context: Context, manga: Manga) {
val id = manga.id.toString()
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
manager.removeDynamicShortcuts(listOf(id))
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun clearAppShortcuts(context: Context) {
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
manager.removeAllDynamicShortcuts()
}
private suspend fun buildShortcutInfo(
context: Context,
manga: Manga
): ShortcutInfoCompat.Builder {
val icon = safe {
val size = getIconSize(context)
withContext(Dispatchers.IO) {
Coil.loader().get(manga.coverUrl) {
context.getSystemService<ActivityManager>()?.let {
size(it.launcherLargeIconSize)
}
val bmp = Coil.loader().get(manga.coverUrl) {
size(size)
scale(Scale.FILL)
}.toBitmap()
ThumbnailUtils.extractThumbnail(
bmp,
size.width,
size.height,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT
)
}
}
MangaDataRepository().storeManga(manga)
@@ -33,12 +90,23 @@ object ShortcutUtils {
.setShortLabel(manga.title)
.setLongLabel(manga.title)
.setIcon(icon?.let {
IconCompat.createWithBitmap(it)
IconCompat.createWithAdaptiveBitmap(it)
} ?: IconCompat.createWithResource(context, R.drawable.ic_launcher_foreground))
.setIntent(
MangaDetailsActivity.newIntent(context, manga.id)
.setAction(MangaDetailsActivity.ACTION_MANGA_VIEW)
)
.build()
}
private fun getIconSize(context: Context): PixelSize {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
(context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager).let {
PixelSize(it.iconMaxWidth, it.iconMaxHeight)
}
} else {
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).launcherLargeIconSize.let {
PixelSize(it, it)
}
}
}
}

View File

@@ -10,7 +10,7 @@ import java.io.IOException
inline fun <T, R> T.safe(action: T.() -> R?) = try {
this.action()
} catch (e: Exception) {
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}