Pin source shortcuts

This commit is contained in:
Koitharu
2023-09-15 12:12:06 +03:00
parent 4ab40566f7
commit 36065ccf6c
10 changed files with 94 additions and 13 deletions

View File

@@ -95,7 +95,12 @@
android:label="@string/search" />
<activity
android:name="org.koitharu.kotatsu.search.ui.MangaListActivity"
android:label="@string/search_manga" />
android:exported="true"
android:label="@string/manga_list">
<intent-filter>
<action android:name="${applicationId}.action.EXPLORE_MANGA" />
</intent-filter>
</activity>
<activity
android:name="org.koitharu.kotatsu.history.ui.HistoryActivity"
android:label="@string/history" />
@@ -138,8 +143,8 @@
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:autoRemoveFromRecents="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity"

View File

@@ -21,6 +21,7 @@ import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.image.ThumbnailTransformation
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
@@ -29,8 +30,10 @@ import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject
import javax.inject.Singleton
@@ -77,6 +80,10 @@ class AppShortcutManager @Inject constructor(
return ShortcutManagerCompat.requestPinShortcut(context, buildShortcutInfo(manga), null)
}
suspend fun requestPinShortcut(source: MangaSource): Boolean {
return ShortcutManagerCompat.requestPinShortcut(context, buildShortcutInfo(source), null)
}
@VisibleForTesting
suspend fun await(): Boolean {
return shortcutsUpdateJob?.join() != null
@@ -86,6 +93,11 @@ class AppShortcutManager @Inject constructor(
ShortcutManagerCompat.reportShortcutUsed(context, mangaId.toString())
}
fun isDynamicShortcutsAvailable(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 &&
context.getSystemService(ShortcutManager::class.java).maxShortcutCountPerActivity > 0
}
private suspend fun updateShortcutsImpl() = runCatchingCancellable {
val maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context).coerceAtLeast(5)
val shortcuts = historyRepository.getList(0, maxShortcuts)
@@ -132,8 +144,25 @@ class AppShortcutManager @Inject constructor(
.build()
}
fun isDynamicShortcutsAvailable(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 &&
context.getSystemService(ShortcutManager::class.java).maxShortcutCountPerActivity > 0
private suspend fun buildShortcutInfo(source: MangaSource): ShortcutInfoCompat {
val icon = runCatchingCancellable {
coil.execute(
ImageRequest.Builder(context)
.data(source.faviconUri())
.size(iconSize)
.scale(Scale.FIT)
.build(),
).getDrawableOrThrow().toBitmap()
}.fold(
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
)
return ShortcutInfoCompat.Builder(context, source.name)
.setShortLabel(source.title)
.setLongLabel(source.title)
.setIcon(icon)
.setLongLived(true)
.setIntent(MangaListActivity.newIntent(context, source))
.build()
}
}

View File

@@ -7,6 +7,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels
@@ -15,9 +16,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.ui.BookmarksActivity
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
@@ -28,6 +31,7 @@ import org.koitharu.kotatsu.core.ui.widgets.TipView
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
@@ -55,6 +59,9 @@ class ExploreFragment :
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var shortcutManager: AppShortcutManager
private val viewModel by viewModels<ExploreViewModel>()
private var exploreAdapter: ExploreAdapter? = null
@@ -141,6 +148,8 @@ class ExploreFragment :
override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {
val menu = PopupMenu(view.context, view)
menu.inflate(R.menu.popup_source)
menu.menu.findItem(R.id.action_shortcut)
?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(view.context)
menu.setOnMenuItemClickListener(SourceMenuListener(item))
menu.show()
return true
@@ -195,6 +204,12 @@ class ExploreFragment :
viewModel.hideSource(sourceItem.source)
}
R.id.action_shortcut -> {
viewLifecycleScope.launch {
shortcutManager.requestPinShortcut(sourceItem.source)
}
}
else -> return false
}
return true

View File

@@ -17,14 +17,15 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
@@ -64,7 +65,7 @@ class MangaListActivity :
if (viewBinding.containerFilterHeader != null) {
viewBinding.appbar.addOnOffsetChangedListener(this)
}
val source = intent.getSerializableExtraCompat(EXTRA_SOURCE) ?: tags?.firstOrNull()?.source
val source = intent.getStringExtra(EXTRA_SOURCE)?.let(::MangaSource) ?: tags?.firstOrNull()?.source
if (source == null) {
finishAfterTransition()
return
@@ -186,11 +187,14 @@ class MangaListActivity :
private const val EXTRA_TAGS = "tags"
private const val EXTRA_SOURCE = "source"
const val ACTION_MANGA_EXPLORE = "${BuildConfig.APPLICATION_ID}.action.EXPLORE_MANGA"
fun newIntent(context: Context, tags: Set<MangaTag>) = Intent(context, MangaListActivity::class.java)
.setAction(ACTION_MANGA_EXPLORE)
.putExtra(EXTRA_TAGS, ParcelableMangaTags(tags))
fun newIntent(context: Context, source: MangaSource) = Intent(context, MangaListActivity::class.java)
.putExtra(EXTRA_SOURCE, source)
.setAction(ACTION_MANGA_EXPLORE)
.putExtra(EXTRA_SOURCE, source.name)
}
}

View File

@@ -59,6 +59,8 @@ class NewSourcesDialogFragment :
override fun onItemLiftClick(item: SourceConfigItem.SourceItem) = Unit
override fun onItemShortcutClick(item: SourceConfigItem.SourceItem) = Unit
override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
viewModel.onItemEnabledChanged(item, isEnabled)
}

View File

@@ -15,7 +15,9 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
@@ -24,6 +26,7 @@ import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.getItem
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.settings.SettingsActivity
@@ -44,6 +47,9 @@ class SourcesManageFragment :
@Inject
lateinit var settings: AppSettings
@Inject
lateinit var shortcutManager: AppShortcutManager
private var reorderHelper: ItemTouchHelper? = null
private val viewModel by viewModels<SourcesManageViewModel>()
@@ -103,6 +109,12 @@ class SourcesManageFragment :
viewModel.bringToTop(item.source)
}
override fun onItemShortcutClick(item: SourceConfigItem.SourceItem) {
viewLifecycleScope.launch {
shortcutManager.requestPinShortcut(item.source)
}
}
override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
viewModel.setEnabled(item.source, isEnabled)
}

View File

@@ -8,6 +8,7 @@ import android.text.style.RelativeSizeSpan
import android.text.style.SuperscriptSpan
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.isGone
@@ -39,7 +40,7 @@ fun sourceConfigHeaderDelegate() =
ItemFilterHeaderBinding.inflate(
layoutInflater,
parent,
false
false,
)
},
) {
@@ -76,7 +77,7 @@ fun sourceConfigItemCheckableDelegate(
ItemSourceConfigCheckableBinding.inflate(
layoutInflater,
parent,
false
false,
)
},
) {
@@ -121,7 +122,7 @@ fun sourceConfigItemDelegate2(
ItemSourceConfigBinding.inflate(
layoutInflater,
parent,
false
false,
)
},
) {
@@ -189,8 +190,8 @@ fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
ForegroundColorSpan(
context.getThemeColor(
com.google.android.material.R.attr.colorError,
Color.RED
)
Color.RED,
),
),
RelativeSizeSpan(0.74f),
SuperscriptSpan(),
@@ -205,10 +206,13 @@ private fun showSourceMenu(
) {
val menu = PopupMenu(anchor.context, anchor)
menu.inflate(R.menu.popup_source_config)
menu.menu.findItem(R.id.action_shortcut)
?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(anchor.context)
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_settings -> listener.onItemSettingsClick(item)
R.id.action_lift -> listener.onItemLiftClick(item)
R.id.action_shortcut -> listener.onItemShortcutClick(item)
}
true
}

View File

@@ -9,6 +9,8 @@ interface SourceConfigListener : OnTipCloseListener<SourceConfigItem.Tip> {
fun onItemLiftClick(item: SourceConfigItem.SourceItem)
fun onItemShortcutClick(item: SourceConfigItem.SourceItem)
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean)
fun onHeaderClick(header: SourceConfigItem.LocaleGroup)

View File

@@ -6,6 +6,10 @@
android:id="@+id/action_settings"
android:title="@string/settings" />
<item
android:id="@+id/action_shortcut"
android:title="@string/create_shortcut" />
<item
android:id="@+id/action_hide"
android:title="@string/hide" />

View File

@@ -6,6 +6,10 @@
android:id="@+id/action_lift"
android:title="@string/to_top" />
<item
android:id="@+id/action_shortcut"
android:title="@string/create_shortcut" />
<item
android:id="@+id/action_settings"
android:title="@string/settings" />