feat: Some QoL features (GH-85)

Co-authored-by: AshbornXS <75546030+AshbornXS@users.noreply.github.com>
This commit is contained in:
Ahmad Ansori Palembani 2024-06-08 08:36:56 +07:00
parent 45caff3ae0
commit b4638017dd
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
17 changed files with 223 additions and 27 deletions

View file

@ -22,4 +22,18 @@ class BasePreferences(private val preferenceStore: PreferenceStore) {
fun hasShownOnboarding() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
fun crashReport() = preferenceStore.getBoolean("pref_crash_report", true)
fun longTapBrowseNavBehaviour() = preferenceStore.getEnum("pref_browser_long_tap", LongTapBrowse.DEFAULT)
enum class LongTapBrowse(@StringRes val titleResId: Int) {
DEFAULT(R.string.browse_long_tap_default),
SEARCH(R.string.browse_long_tap_search),
}
fun longTapRecentsNavBehaviour() = preferenceStore.getEnum("pref_recents_long_tap", LongTapRecents.DEFAULT)
enum class LongTapRecents(@StringRes val titleResId: Int) {
DEFAULT(R.string.recents_long_tap_default),
LAST_READ(R.string.recents_long_tap_last_read)
}
}

View file

@ -356,6 +356,14 @@ class DownloadBottomSheet @JvmOverloads constructor(
?: Pair(listOf<Download>(), listOf<Download>())
presenter.reorder(selectedSeries + otherSeries)
}
R.id.move_to_bottom_series -> {
val (selectedSeries, otherSeries) = adapter?.currentItems
?.filterIsInstance<DownloadItem>()
?.map(DownloadItem::download)
?.partition { item.download.manga.id == it.manga.id }
?: Pair(listOf<Download>(), listOf<Download>())
presenter.reorder(otherSeries + selectedSeries)
}
R.id.cancel_series -> {
val allDownloadsForSeries = adapter?.currentItems
?.filterIsInstance<DownloadItem>()

View file

@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.ui.extension
import android.content.pm.PackageInstaller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.extension.ExtensionInstallerJob
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension
@ -10,12 +13,15 @@ import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.ui.migration.BaseMigrationPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
typealias ExtensionTuple =
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
@ -24,16 +30,19 @@ typealias ExtensionIntallInfo = Pair<InstallStep, PackageInstaller.SessionInfo?>
/**
* Presenter of [ExtensionBottomSheet].
*/
class ExtensionBottomPresenter : BaseMigrationPresenter<ExtensionBottomSheet>() {
class ExtensionBottomPresenter : BaseMigrationPresenter<ExtensionBottomSheet>(), DownloadQueue.DownloadListener {
private var extensions = emptyList<ExtensionItem>()
val downloadManager: DownloadManager = Injekt.get()
private var currentDownloads = hashMapOf<String, ExtensionIntallInfo>()
private var firstLoad = true
override fun onCreate() {
super.onCreate()
downloadManager.addListener(this)
presenterScope.launch {
val extensionJob = async {
extensionManager.findAvailableExtensions()
@ -279,4 +288,11 @@ class ExtensionBottomPresenter : BaseMigrationPresenter<ExtensionBottomSheet>()
extensionManager.trust(pkgName, versionCode, signatureHash)
}
}
override fun updateDownload(download: Download) = updateDownloads()
override fun updateDownloads() {
presenterScope.launchUI {
view?.updateDownloadStatus(!downloadManager.isPaused())
}
}
}

View file

@ -514,4 +514,8 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
return if (index == -1) POSITION_NONE else index
}
}
fun updateDownloadStatus(isRunning: Boolean) {
(controller.activity as? MainActivity)?.downloadStatusChanged(isRunning)
}
}

View file

@ -2172,4 +2172,8 @@ open class LibraryController(
destroyActionModeIfNeeded()
}
}
fun updateDownloadStatus(isRunning: Boolean) {
(activity as? MainActivity)?.downloadStatusChanged(isRunning)
}
}

View file

@ -15,6 +15,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.preference.DelayedLibrarySuggestionsJob
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
@ -45,6 +47,7 @@ import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import eu.kanade.tachiyomi.util.mapStatus
import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -69,7 +72,7 @@ class LibraryPresenter(
private val downloadManager: DownloadManager = Injekt.get(),
private val chapterFilter: ChapterFilter = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(),
) : BaseCoroutinePresenter<LibraryController>() {
) : BaseCoroutinePresenter<LibraryController>(), DownloadQueue.DownloadListener {
private val getLibraryManga: GetLibraryManga by injectLazy()
private val context = preferences.context
@ -147,6 +150,7 @@ class LibraryPresenter(
override fun onCreate() {
super.onCreate()
downloadManager.addListener(this)
if (!controllerIsSubClass) {
lastLibraryItems?.let { libraryItems = it }
lastCategories?.let { categories = it }
@ -1493,4 +1497,11 @@ class LibraryPresenter(
}
}
}
override fun updateDownload(download: Download) = updateDownloads()
override fun updateDownloads() {
presenterScope.launchUI {
view?.updateDownloadStatus(!downloadManager.isPaused())
}
}
}

View file

@ -75,6 +75,7 @@ import dev.yokai.presentation.onboarding.OnboardingController
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadJob
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
@ -99,6 +100,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.more.AboutController
import eu.kanade.tachiyomi.ui.more.OverflowDialog
import eu.kanade.tachiyomi.ui.more.stats.StatsController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recents.RecentsController
import eu.kanade.tachiyomi.ui.recents.RecentsViewType
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
@ -106,10 +108,12 @@ import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
import eu.kanade.tachiyomi.ui.setting.controllers.SettingsMainController
import eu.kanade.tachiyomi.ui.source.BrowseController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import eu.kanade.tachiyomi.util.system.contextCompatDrawable
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.hasSideNavBar
import eu.kanade.tachiyomi.util.system.ignoredSystemInsets
@ -167,6 +171,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
private val downloadManager: DownloadManager by injectLazy()
private val mangaShortcutManager: MangaShortcutManager by injectLazy()
private val extensionManager: ExtensionManager by injectLazy()
private val db: DatabaseHelper by injectLazy()
private val hideBottomNav
get() = router.backstackSize > 1 && router.backstack[1].controller !is DialogController
private val hideAppBar
@ -390,16 +395,48 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
}
true
}
for (id in listOf(R.id.nav_recents, R.id.nav_browse)) {
nav.getItemView(id)?.setOnLongClickListener {
nav.selectedItemId = id
nav.post {
val controller =
router.backstack.firstOrNull()?.controller as? BottomSheetController
controller?.showSheet()
}
true
nav.getItemView(R.id.nav_recents)?.setOnLongClickListener {
if (nav.selectedItemId != R.id.nav_recents) {
nav.selectedItemId = R.id.nav_recents
}
when (basePreferences.longTapRecentsNavBehaviour().get()) {
BasePreferences.LongTapRecents.DEFAULT -> {
nav.post {
val controller =
router.backstack.firstOrNull()?.controller as? BottomSheetController
controller?.showSheet()
}
}
BasePreferences.LongTapRecents.LAST_READ -> {
lifecycleScope.launchUI {
val lastReadChapter =
db.getHistoryUngrouped("", 0, true).executeOnIO().maxByOrNull { it.history.last_read }
lastReadChapter ?: return@launchUI
val manga = lastReadChapter.manga
val chapter = lastReadChapter.chapter
startActivity(ReaderActivity.newIntent(this@MainActivity, manga, chapter))
}
}
}
true
}
nav.getItemView(R.id.nav_browse)?.setOnLongClickListener {
if (nav.selectedItemId != R.id.nav_browse) {
nav.selectedItemId = R.id.nav_browse
}
when (basePreferences.longTapBrowseNavBehaviour().get()) {
BasePreferences.LongTapBrowse.DEFAULT -> {
nav.post {
val controller =
router.backstack.firstOrNull()?.controller as? BottomSheetController
controller?.showSheet()
}
}
BasePreferences.LongTapBrowse.SEARCH ->
router.pushController(GlobalSearchController().withFadeTransaction())
}
true
}
val container: ViewGroup = binding.controllerContainer
@ -1486,11 +1523,13 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
}
}
private fun downloadStatusChanged(downloading: Boolean) {
fun downloadStatusChanged(downloading: Boolean) {
lifecycleScope.launchUI {
val hasQueue = downloading || downloadManager.hasQueue()
if (hasQueue) {
nav.getOrCreateBadge(R.id.nav_recents)
val badge = nav.getOrCreateBadge(R.id.nav_recents)
badge.number = downloadManager.queue.size
if (downloading) badge.backgroundColor = -870219 else badge.backgroundColor = Color.GRAY
showDLQueueTutorial()
} else {
nav.removeBadge(R.id.nav_recents)

View file

@ -9,11 +9,13 @@ import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.preference.PreferenceScreen
import dev.yokai.domain.base.BasePreferences
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
import eu.kanade.tachiyomi.ui.setting.ThemePreference
import eu.kanade.tachiyomi.ui.setting.bindTo
import eu.kanade.tachiyomi.ui.setting.defaultValue
import eu.kanade.tachiyomi.ui.setting.infoPreference
import eu.kanade.tachiyomi.ui.setting.intListPreference
@ -230,6 +232,28 @@ class SettingsGeneralController : SettingsLegacyController() {
}
}
}
preferenceCategory {
titleRes = R.string.navigation
listPreference(activity) {
bindTo(basePreferences.longTapRecentsNavBehaviour())
titleRes = R.string.recents_long_tap
val values = BasePreferences.LongTapRecents.entries.toList()
entriesRes = values.map { it.titleResId }.toTypedArray()
entryValues = values.map { it.name }.toTypedArray().toList()
}
listPreference(activity) {
bindTo(basePreferences.longTapBrowseNavBehaviour())
titleRes = R.string.browse_long_tap
val values = BasePreferences.LongTapBrowse.entries.toList()
entriesRes = values.map { it.titleResId }.toTypedArray()
entryValues = values.map { it.name }.toTypedArray().toList()
}
}
}
override fun onDestroyView(view: View) {

View file

@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.ui.main.SearchControllerInterface
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.system.extensionIntentForText
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.isControllerVisible
@ -163,7 +164,15 @@ open class GlobalSearchController(
activityBinding?.searchToolbar?.searchItem?.expandActionView()
activityBinding?.searchToolbar?.searchView?.setQuery(presenter.query, false)
if (presenter.query.isBlank()) {
activityBinding?.searchToolbar?.searchView?.requestFocus()
}
setOnQueryTextChangeListener(activityBinding?.searchToolbar?.searchView, onlyOnSubmit = true, hideKbOnSubmit = true) {
// try to handle the query as a manga URL
applicationContext?.extensionIntentForText(it ?: "")?.let {
startActivity(it)
}
presenter.search(it ?: "")
setTitle() // Update toolbar title
true
@ -177,7 +186,9 @@ open class GlobalSearchController(
val searchItem = activityBinding?.searchToolbar?.searchItem ?: return
searchItem.expandActionView()
searchView.setQuery(presenter.query, false)
searchView.clearFocus()
if (presenter.query.isNotBlank()) {
searchView.clearFocus()
}
}
if (type == ControllerChangeType.POP_ENTER && lastPosition > -1) {
val holder = binding.recycler.findViewHolderForAdapterPosition(lastPosition) as? GlobalSearchHolder
@ -221,6 +232,11 @@ open class GlobalSearchController(
customTitle = view.context?.getString(R.string.loading)
setTitle()
}
// try to handle the query as a manga URL
applicationContext?.extensionIntentForText(presenter.query)?.let {
startActivity(it)
}
}
override fun onDestroyView(view: View) {

View file

@ -134,6 +134,9 @@ open class BaseWebViewActivity : BaseActivity<WebviewActivityBinding>() {
override fun onReceivedTitle(view: WebView?, title: String?) {
super.onReceivedTitle(view, title)
this@BaseWebViewActivity.title = title
binding.toolbarTitle.text = title
binding.toolbarSubtitle.text = view?.url
binding.toolbarSubtitle.isSelected = true
}
}
val marginB = binding.webview.marginBottom

View file

@ -12,6 +12,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.core.graphics.ColorUtils
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
@ -19,6 +20,7 @@ import eu.kanade.tachiyomi.util.system.extensionIntentForText
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
@ -27,6 +29,8 @@ open class WebViewActivity : BaseWebViewActivity() {
private val sourceManager by injectLazy<SourceManager>()
private var bundle: Bundle? = null
private val network: NetworkHelper by injectLazy()
private var backPressedCallback: OnBackPressedCallback? = null
private val backCallback = {
if (binding.webview.canGoBack()) binding.webview.goBack()
@ -166,10 +170,17 @@ open class WebViewActivity : BaseWebViewActivity() {
R.id.action_web_share -> shareWebpage()
R.id.action_web_browser -> openInBrowser()
R.id.action_open_in_app -> openUrlInApp()
R.id.action_web_clear_cookies -> clearCookies()
}
return super.onOptionsItemSelected(item)
}
private fun clearCookies() {
val url = binding.webview.url ?: return
val cleared = network.cookieJar.remove(url.toHttpUrl())
toast("Cleared $cleared cookies for: $url")
}
private fun openUrlInApp() {
val url = binding.webview.url ?: return
extensionIntentForText(url)?.let { startActivity(it) }

View file

@ -37,6 +37,8 @@ fun WebView.setDefaultSettings() {
databaseEnabled = true
useWideViewPort = true
loadWithOverviewMode = true
builtInZoomControls = true
displayZoomControls = false
cacheMode = WebSettings.LOAD_DEFAULT
}
}

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/web_view_layout"
android:layout_width="match_parent"
@ -10,11 +9,11 @@
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:orientation="vertical"
android:id="@+id/web_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:layout_height="wrap_content">
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
@ -23,9 +22,39 @@
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorSurface"
android:theme="?attr/actionBarTheme"
app:titleTextColor="?actionBarTintColor"
app:titleTextColor="?attr/actionBarTintColor"
app:navigationIcon="@drawable/ic_close_24dp"
app:layout_scrollFlags="scroll|enterAlways" />
app:layout_scrollFlags="scroll|enterAlways">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/actionBarTintColor"
android:textSize="20sp"
android:singleLine="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<TextView
android:id="@+id/toolbar_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?attr/actionBarTintColor"
android:layout_below="@+id/toolbar_title"
android:layout_alignStart="@+id/toolbar_title" />
</RelativeLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
@ -36,20 +65,19 @@
<WebView
android:id="@+id/webview"
android:nestedScrollingEnabled="true"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:layout_height="match_parent"
android:nestedScrollingEnabled="true">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="2dp"
android:progressTint="?attr/colorSecondary"
android:progressBackgroundTint="?attr/colorSurface"
/>
android:progressTint="?attr/colorSecondary" />
</WebView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
</LinearLayout>

View file

@ -9,6 +9,9 @@
<item android:id="@+id/move_to_top_series"
android:title="@string/move_series_to_top" />
<item android:id="@+id/move_to_bottom_series"
android:title="@string/move_series_to_bottom" />
<item android:id="@+id/cancel_series"
android:title="@string/cancel_all_for_series" />
</menu>
</menu>

View file

@ -29,4 +29,9 @@
android:title="@string/open_in_browser"
app:showAsAction="never" />
<item
android:id="@+id/action_web_clear_cookies"
android:title="@string/clear_cookies"
app:showAsAction="never" />
</menu>

View file

@ -972,6 +972,7 @@
<string name="pref_inverted_colors">Invertido</string>
<string name="empty_backup_error">Nenhuma entrada na biblioteca para fazer backup</string>
<string name="move_series_to_top">Mover série para o topo</string>
<string name="move_series_to_bottom">Mover série para o fundo</string>
<string name="ascending">Crescente</string>
<string name="date">Data</string>
<string name="descending">Decrescente</string>
@ -1135,4 +1136,4 @@
<string name="apply">Aplicar</string>
<string name="pref_debug_info">Informações de depuração</string>
<string name="label_background_activity">Atividade em segundo plano</string>
</resources>
</resources>

View file

@ -311,6 +311,9 @@
<string name="only_downloaded">Only downloaded</string>
<string name="unread_or_downloaded">Unread or downloaded</string>
<string name="clear_history">Clear history</string>
<string name="recents_long_tap_default">Show download queue</string>
<string name="recents_long_tap_last_read">Open last read chapter</string>
<string name="recents_long_tap">Long tap Recents behaviour</string>
<!-- Browse -->
<string name="search_filters">Search filters</string>
@ -331,6 +334,9 @@
<string name="last_used">Last used</string>
<string name="local_source_help_guide">Local source guide</string>
<string name="check_site_in_web">Check website in WebView</string>
<string name="browse_long_tap_default">Open extensions / migration menu</string>
<string name="browse_long_tap_search">Open global search</string>
<string name="browse_long_tap">Long tap Browse behaviour</string>
<!-- Other Screens -->
@ -990,6 +996,7 @@
<string name="downloading_">Downloading: %1$s</string>
<string name="cancel_all">Cancel all</string>
<string name="move_series_to_top">Move series to top</string>
<string name="move_series_to_bottom">Move series to bottom</string>
<string name="downloads_swipe_tutorial">Downloads can be cancelled by swiping them away\nSwipe this tip to dismiss it</string>
<string name="cancel_all_for_series">Cancel all for this series</string>
<string name="downloaded">Downloaded</string>