Dynamic app shortcuts
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user