Fix shortcuts
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package org.koitharu.kotatsu.core.os
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.media.ThumbnailUtils
|
||||
import android.os.Build
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.PixelSize
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
|
||||
class ShortcutsRepository(
|
||||
private val context: Context,
|
||||
private val coil: ImageLoader,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepository: MangaDataRepository
|
||||
) {
|
||||
|
||||
private val iconSize by lazy {
|
||||
getIconSize(context)
|
||||
}
|
||||
|
||||
suspend fun updateShortcuts() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return
|
||||
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
|
||||
val shortcuts = historyRepository.getList(0, manager.maxShortcutCountPerActivity)
|
||||
.map { buildShortcutInfo(it).build().toShortcutInfo() }
|
||||
manager.dynamicShortcuts = shortcuts
|
||||
}
|
||||
|
||||
suspend fun requestPinShortcut(manga: Manga): Boolean {
|
||||
return ShortcutManagerCompat.requestPinShortcut(
|
||||
context,
|
||||
buildShortcutInfo(manga).build(),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder {
|
||||
val icon = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
val bmp = coil.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
.size(iconSize)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
ThumbnailUtils.extractThumbnail(bmp, iconSize.width, iconSize.height, 0)
|
||||
}
|
||||
}.fold(
|
||||
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
|
||||
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) }
|
||||
)
|
||||
mangaRepository.storeManga(manga)
|
||||
return ShortcutInfoCompat.Builder(context, manga.id.toString())
|
||||
.setShortLabel(manga.title)
|
||||
.setLongLabel(manga.title)
|
||||
.setIcon(icon)
|
||||
.setIntent(
|
||||
ReaderActivity.newIntent(context, manga.id, null)
|
||||
.setAction(ReaderActivity.ACTION_MANGA_READ)
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
@@ -31,10 +32,10 @@ import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||
import org.koitharu.kotatsu.download.DownloadService
|
||||
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
|
||||
import org.koitharu.kotatsu.utils.MangaShortcut
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||
@@ -197,7 +198,7 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
|
||||
R.id.action_shortcut -> {
|
||||
viewModel.manga.value?.let {
|
||||
lifecycleScope.launch {
|
||||
if (!MangaShortcut(it).requestPinShortcut(this@DetailsActivity)) {
|
||||
if (!get<ShortcutsRepository>().requestPinShortcut(it)) {
|
||||
Snackbar.make(
|
||||
binding.pager,
|
||||
R.string.operation_not_supported,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.history
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
@@ -10,5 +9,5 @@ val historyModule
|
||||
get() = module {
|
||||
|
||||
single { HistoryRepository(get()) }
|
||||
viewModel { HistoryListViewModel(get(), androidContext(), get()) }
|
||||
viewModel { HistoryListViewModel(get(), get(), get()) }
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package org.koitharu.kotatsu.history.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
@@ -15,7 +14,6 @@ import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.MangaWithHistory
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.utils.MangaShortcut
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
@@ -26,8 +24,8 @@ import kotlin.collections.ArrayList
|
||||
|
||||
class HistoryListViewModel(
|
||||
private val repository: HistoryRepository,
|
||||
private val context: Context, //todo create ShortcutRepository
|
||||
private val settings: AppSettings
|
||||
private val settings: AppSettings,
|
||||
private val shortcutsRepository: ShortcutsRepository
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
val onItemRemoved = SingleLiveEvent<Manga>()
|
||||
@@ -62,9 +60,7 @@ class HistoryListViewModel(
|
||||
fun clearHistory() {
|
||||
launchLoadingJob {
|
||||
repository.clear()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
MangaShortcut.clearAppShortcuts(context)
|
||||
}
|
||||
shortcutsRepository.updateShortcuts()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +68,7 @@ class HistoryListViewModel(
|
||||
launchJob {
|
||||
repository.delete(manga)
|
||||
onItemRemoved.call(manga)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
MangaShortcut(manga).removeAppShortcut(context)
|
||||
}
|
||||
shortcutsRepository.updateShortcuts()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,5 +15,5 @@ val localModule
|
||||
single { LocalMangaRepository(androidContext()) }
|
||||
factory<MangaRepository>(named(MangaSource.LOCAL)) { get<LocalMangaRepository>() }
|
||||
|
||||
viewModel { LocalListViewModel(get(), get(), get(), androidContext()) }
|
||||
viewModel { LocalListViewModel(get(), get(), get(), get(), androidContext()) }
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.local.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -14,6 +13,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
@@ -22,7 +22,6 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.utils.MangaShortcut
|
||||
import org.koitharu.kotatsu.utils.MediaStoreCompat
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.safe
|
||||
@@ -33,6 +32,7 @@ class LocalListViewModel(
|
||||
private val repository: LocalMangaRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val settings: AppSettings,
|
||||
private val shortcutsRepository: ShortcutsRepository,
|
||||
private val context: Context
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
@@ -102,9 +102,7 @@ class LocalListViewModel(
|
||||
historyRepository.deleteOrSwap(manga, original)
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
MangaShortcut(manga).removeAppShortcut(context)
|
||||
}
|
||||
shortcutsRepository.updateShortcuts()
|
||||
onMangaRemoved.call(manga)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.koitharu.kotatsu.main
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.main.ui.MainViewModel
|
||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||
import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel
|
||||
@@ -9,6 +11,7 @@ import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel
|
||||
val mainModule
|
||||
get() = module {
|
||||
single { AppProtectHelper(get()) }
|
||||
single { ShortcutsRepository(androidContext(), get(), get(), get()) }
|
||||
viewModel { MainViewModel(get(), get()) }
|
||||
viewModel { ProtectViewModel(get()) }
|
||||
}
|
||||
@@ -15,6 +15,6 @@ val readerModule
|
||||
single { PagesCache(get()) }
|
||||
|
||||
viewModel { (intent: MangaIntent, state: ReaderState?) ->
|
||||
ReaderViewModel(intent, state, get(), get(), get())
|
||||
ReaderViewModel(intent, state, get(), get(), get(), get())
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.core.model.MangaPage
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
@@ -35,6 +36,7 @@ class ReaderViewModel(
|
||||
state: ReaderState?,
|
||||
private val dataRepository: MangaDataRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val shortcutsRepository: ShortcutsRepository,
|
||||
private val settings: AppSettings
|
||||
) : BaseViewModel() {
|
||||
|
||||
@@ -105,6 +107,11 @@ class ReaderViewModel(
|
||||
|
||||
val pages = loadChapter(requireNotNull(currentState.value).chapterId)
|
||||
content.postValue(ReaderContent(pages, currentState.value))
|
||||
// save state
|
||||
currentState.value?.let {
|
||||
historyRepository.addOrUpdate(manga, it.chapterId, it.page, it.scroll)
|
||||
shortcutsRepository.updateShortcuts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package org.koitharu.kotatsu.utils
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
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 coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.PixelSize
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
import org.koitharu.kotatsu.utils.ext.safe
|
||||
|
||||
class MangaShortcut(private val manga: Manga) : KoinComponent {
|
||||
|
||||
private val shortcutId = manga.id.toString()
|
||||
private val coil by inject<ImageLoader>()
|
||||
private val mangaRepository by inject<MangaDataRepository>()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
suspend fun addAppShortcut(context: Context) {
|
||||
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
|
||||
val limit = manager.maxShortcutCountPerActivity
|
||||
val builder = buildShortcutInfo(context, manga)
|
||||
val shortcuts = manager.dynamicShortcuts
|
||||
for (shortcut in shortcuts) {
|
||||
if (shortcut.id == shortcutId) {
|
||||
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.minByOrNull { it.rank }!!.id))
|
||||
}
|
||||
manager.addDynamicShortcuts(listOf(builder.build().toShortcutInfo()))
|
||||
}
|
||||
|
||||
suspend fun requestPinShortcut(context: Context): Boolean {
|
||||
return ShortcutManagerCompat.requestPinShortcut(
|
||||
context,
|
||||
buildShortcutInfo(context, manga).build(),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
fun removeAppShortcut(context: Context) {
|
||||
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
|
||||
manager.removeDynamicShortcuts(listOf(shortcutId))
|
||||
}
|
||||
|
||||
private suspend fun buildShortcutInfo(
|
||||
context: Context,
|
||||
manga: Manga
|
||||
): ShortcutInfoCompat.Builder {
|
||||
val icon = safe {
|
||||
val size = getIconSize(context)
|
||||
withContext(Dispatchers.IO) {
|
||||
val bmp = coil.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
ThumbnailUtils.extractThumbnail(bmp, size.width, size.height, 0)
|
||||
}
|
||||
}
|
||||
mangaRepository.storeManga(manga)
|
||||
return ShortcutInfoCompat.Builder(context, manga.id.toString())
|
||||
.setShortLabel(manga.title)
|
||||
.setLongLabel(manga.title)
|
||||
.setIcon(icon?.let {
|
||||
IconCompat.createWithAdaptiveBitmap(it)
|
||||
} ?: IconCompat.createWithResource(context, R.drawable.ic_shortcut_default))
|
||||
.setIntent(
|
||||
ReaderActivity.newIntent(context, manga.id, null)
|
||||
.setAction(ReaderActivity.ACTION_MANGA_READ)
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
fun clearAppShortcuts(context: Context) {
|
||||
val manager = context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
|
||||
manager.removeAllDynamicShortcuts()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user