Dynamic app shortcuts

pull/1/head
Koitharu 6 years ago
parent 402c66ae87
commit 127779f783

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

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

@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
@ -14,6 +15,8 @@ import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moxy.MvpDelegate import moxy.MvpDelegate
import moxy.ktx.moxyPresenter import moxy.ktx.moxyPresenter
import org.koin.core.inject 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.ui.reader.wetoon.WebtoonReaderFragment
import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ShortcutUtils
import org.koitharu.kotatsu.utils.anim.Motion import org.koitharu.kotatsu.utils.anim.Motion
import org.koitharu.kotatsu.utils.ext.* 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) { if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
presenter.init(state.manga) 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.app.ActivityManager
import android.content.Context 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.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import coil.Coil import coil.Coil
import coil.api.get import coil.api.get
import coil.size.PixelSize
import coil.size.Scale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -18,14 +24,65 @@ import org.koitharu.kotatsu.utils.ext.safe
object ShortcutUtils { 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 icon = safe {
val size = getIconSize(context)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Coil.loader().get(manga.coverUrl) { val bmp = Coil.loader().get(manga.coverUrl) {
context.getSystemService<ActivityManager>()?.let { size(size)
size(it.launcherLargeIconSize) scale(Scale.FILL)
}
}.toBitmap() }.toBitmap()
ThumbnailUtils.extractThumbnail(
bmp,
size.width,
size.height,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT
)
} }
} }
MangaDataRepository().storeManga(manga) MangaDataRepository().storeManga(manga)
@ -33,12 +90,23 @@ object ShortcutUtils {
.setShortLabel(manga.title) .setShortLabel(manga.title)
.setLongLabel(manga.title) .setLongLabel(manga.title)
.setIcon(icon?.let { .setIcon(icon?.let {
IconCompat.createWithBitmap(it) IconCompat.createWithAdaptiveBitmap(it)
} ?: IconCompat.createWithResource(context, R.drawable.ic_launcher_foreground)) } ?: IconCompat.createWithResource(context, R.drawable.ic_launcher_foreground))
.setIntent( .setIntent(
MangaDetailsActivity.newIntent(context, manga.id) MangaDetailsActivity.newIntent(context, manga.id)
.setAction(MangaDetailsActivity.ACTION_MANGA_VIEW) .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 { inline fun <T, R> T.safe(action: T.() -> R?) = try {
this.action() this.action()
} catch (e: Exception) { } catch (e: Throwable) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace() e.printStackTrace()
} }

Loading…
Cancel
Save