diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt index 7e9616e97..600a24f78 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsActivity.kt @@ -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, diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt index c453aa8ec..37ad9c40b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/list/history/HistoryListPresenter.kt @@ -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>() { @@ -58,6 +61,9 @@ class HistoryListPresenter : BasePresenter>() { 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>() { 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) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt index 5e2fd9dd9..1fbfb6473 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt @@ -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) + } + } + } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ShortcutUtils.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ShortcutUtils.kt index 19ac3ffd6..e394e380f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ShortcutUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ShortcutUtils.kt @@ -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()?.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) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt index 5e939bd0a..39b790d39 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt @@ -10,7 +10,7 @@ import java.io.IOException inline fun T.safe(action: T.() -> R?) = try { this.action() -} catch (e: Exception) { +} catch (e: Throwable) { if (BuildConfig.DEBUG) { e.printStackTrace() }