فهرست منبع

[feature] add ability to set global filter/sort/display for Manga chapters (#3622)

* - [feature] add ability to set global filter/sort/display for Manga chapters

* - move default chapter settings functionality to overflow menu
- code clean up

* - show confirmation dialog when user selects "Set as Default" option in Chapter Settings

* - hide overflow menu in LibrarySettingsSheet

* - apply default chapter settings if manga is added to Library from a Source's browsing screen

Co-authored-by: arkon <[email protected]>
lmj0011 4 سال پیش
والد
کامیت
64050e8266

+ 5 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt

@@ -83,6 +83,11 @@ interface MangaQueries : DbProvider {
         .withPutResolver(MangaFlagsPutResolver())
         .prepare()
 
+    fun updateFlags(mangas: List<Manga>) = db.put()
+        .objects(mangas)
+        .withPutResolver(MangaFlagsPutResolver(true))
+        .prepare()
+
     fun updateLastUpdated(manga: Manga) = db.put()
         .`object`(manga)
         .withPutResolver(MangaLastUpdatedPutResolver())

+ 16 - 6
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt

@@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.inTransactionReturn
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.database.tables.MangaTable
 
-class MangaFlagsPutResolver : PutResolver<Manga>() {
+class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolver<Manga>() {
 
     override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
         val updateQuery = mapToUpdateQuery(manga)
@@ -19,11 +19,21 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
         PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
     }
 
-    fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
-        .table(MangaTable.TABLE)
-        .where("${MangaTable.COL_ID} = ?")
-        .whereArgs(manga.id)
-        .build()
+    fun mapToUpdateQuery(manga: Manga): UpdateQuery {
+        val builder = UpdateQuery.builder()
+
+        return if (updateAll) {
+            builder
+                .table(MangaTable.TABLE)
+                .build()
+        } else {
+            builder
+                .table(MangaTable.TABLE)
+                .where("${MangaTable.COL_ID} = ?")
+                .whereArgs(manga.id)
+                .build()
+        }
+    }
 
     fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
         put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)

+ 12 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -165,6 +165,18 @@ object PreferenceKeys {
 
     const val enableDoh = "enable_doh"
 
+    const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
+
+    const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
+
+    const val defaultChapterFilterByBookmarked = "default_chapter_filter_by_bookmarked"
+
+    const val defaultChapterSortBySourceOrNumber = "default_chapter_sort_by_source_or_number" // and upload date
+
+    const val defaultChapterSortByAscendingOrDescending = "default_chapter_sort_by_ascending_or_descending"
+
+    const val defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
+
     fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
 
     fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"

+ 29 - 2
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -2,11 +2,15 @@ package eu.kanade.tachiyomi.data.preference
 
 import android.content.Context
 import android.os.Environment
+import androidx.core.content.edit
 import androidx.core.net.toUri
 import androidx.preference.PreferenceManager
 import com.tfcporciuncula.flow.FlowSharedPreferences
 import com.tfcporciuncula.flow.Preference
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
 import eu.kanade.tachiyomi.data.track.TrackService
@@ -18,8 +22,6 @@ import java.io.File
 import java.text.DateFormat
 import java.text.SimpleDateFormat
 import java.util.Locale
-import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
-import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 
 @OptIn(ExperimentalCoroutinesApi::class)
 fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
@@ -249,4 +251,29 @@ class PreferencesHelper(val context: Context) {
     fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
 
     fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false)
+
+    fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
+
+    fun filterChapterByDownloaded() = prefs.getInt(Keys.defaultChapterFilterByDownloaded, Manga.SHOW_ALL)
+
+    fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL)
+
+    fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.SORTING_SOURCE)
+
+    fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.DISPLAY_NAME)
+
+    fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_DESC)
+
+    fun setChapterSettingsDefault(m: Manga) {
+        prefs.edit {
+            putInt(Keys.defaultChapterFilterByRead, m.readFilter)
+            putInt(Keys.defaultChapterFilterByDownloaded, m.downloadedFilter)
+            putInt(Keys.defaultChapterFilterByBookmarked, m.bookmarkedFilter)
+            putInt(Keys.defaultChapterSortBySourceOrNumber, m.sorting)
+            putInt(Keys.defaultChapterDisplayByNameOrNumber, m.displayMode)
+        }
+
+        if (m.sortDescending()) prefs.edit { putInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_DESC) }
+        else prefs.edit { putInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_ASC) }
+    }
 }

+ 3 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt

@@ -28,6 +28,7 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TextItem
 import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem
 import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
 import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
+import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
 import eu.kanade.tachiyomi.util.removeCovers
 import kotlinx.coroutines.flow.subscribe
 import rx.Observable
@@ -268,6 +269,8 @@ open class BrowseSourcePresenter(
 
         if (!manga.favorite) {
             manga.removeCovers(coverCache)
+        } else {
+            ChapterSettingsHelper.applySettingDefaultsFromPreferences(manga)
         }
 
         db.insertManga(manga).executeAsBlocking()

+ 5 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.LocalSource
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
+import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
 import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
 import eu.kanade.tachiyomi.util.isLocal
 import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
@@ -82,6 +83,10 @@ class MangaPresenter(
     override fun onCreate(savedState: Bundle?) {
         super.onCreate(savedState)
 
+        if (!manga.favorite) {
+            ChapterSettingsHelper.applySettingDefaultsFromPreferences(manga)
+        }
+
         // Manga info - start
 
         getMangaObservable()

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSettingsSheet.kt

@@ -14,7 +14,7 @@ class ChaptersSettingsSheet(
     activity: Activity,
     private val presenter: MangaPresenter,
     onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
-) : TabbedBottomSheetDialog(activity) {
+) : TabbedBottomSheetDialog(activity, presenter.manga) {
 
     val filters: Filter
     private val sort: Sort

+ 46 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/SetChapterSettingsDialog.kt

@@ -0,0 +1,46 @@
+package eu.kanade.tachiyomi.ui.manga.chapter
+
+import android.app.Dialog
+import android.content.Context
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.customview.customView
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
+import eu.kanade.tachiyomi.util.system.toast
+import eu.kanade.tachiyomi.widget.DialogCheckboxView
+
+class SetChapterSettingsDialog(val context: Context, val manga: Manga) {
+    private var dialog: Dialog
+
+    init {
+        this.dialog = buildDialog()
+    }
+
+    private fun buildDialog(): Dialog {
+        val view = DialogCheckboxView(context).apply {
+            setDescription(R.string.confirm_set_chapter_settings)
+            setOptionDescription(R.string.also_set_chapter_settings_for_library)
+        }
+
+        return MaterialDialog(context)
+            .title(R.string.action_chapter_settings)
+            .customView(
+                view = view,
+                horizontalPadding = true
+            )
+            .positiveButton(android.R.string.ok) {
+                ChapterSettingsHelper.setNewSettingDefaults(manga)
+                if (view.isChecked()) {
+                    ChapterSettingsHelper.updateAllMangasWithDefaultsFromPreferences()
+                }
+
+                context.toast(context.getString(R.string.chapter_settings_updated))
+            }
+            .negativeButton(android.R.string.cancel)
+    }
+
+    fun showDialog() = dialog.show()
+
+    fun dismissDialog() = dialog.dismiss()
+}

+ 57 - 0
app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt

@@ -0,0 +1,57 @@
+package eu.kanade.tachiyomi.util.chapter
+
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.util.lang.launchIO
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+object ChapterSettingsHelper {
+    private val prefs = Injekt.get<PreferencesHelper>()
+    private val db: DatabaseHelper = Injekt.get()
+
+    /**
+     * updates the Chapter Settings in Preferences
+     */
+    fun setNewSettingDefaults(m: Manga?) {
+        m?.let {
+            prefs.setChapterSettingsDefault(it)
+            db.updateFlags(it).executeAsBlocking()
+        }
+    }
+
+    /**
+     * updates a single manga's Chapter Settings to match what's set in Preferences
+     */
+    fun applySettingDefaultsFromPreferences(m: Manga) {
+        m.readFilter = prefs.filterChapterByRead()
+        m.downloadedFilter = prefs.filterChapterByDownloaded()
+        m.bookmarkedFilter = prefs.filterChapterByBookmarked()
+        m.sorting = prefs.sortChapterBySourceOrNumber()
+        m.displayMode = prefs.displayChapterByNameOrNumber()
+        m.setChapterOrder(prefs.sortChapterByAscendingOrDescending())
+        db.updateFlags(m).executeAsBlocking()
+    }
+
+    /**
+     * updates all mangas in database Chapter Settings to match what's set in Preferences
+     */
+    fun updateAllMangasWithDefaultsFromPreferences() {
+        launchIO {
+            val dbMangas = db.getMangas().executeAsBlocking().toMutableList()
+
+            val updatedMangas = dbMangas.map { m ->
+                m.readFilter = prefs.filterChapterByRead()
+                m.downloadedFilter = prefs.filterChapterByDownloaded()
+                m.bookmarkedFilter = prefs.filterChapterByBookmarked()
+                m.sorting = prefs.sortChapterBySourceOrNumber()
+                m.displayMode = prefs.displayChapterByNameOrNumber()
+                m.setChapterOrder(prefs.sortChapterByAscendingOrDescending())
+                m
+            }.toList()
+
+            db.updateFlags(updatedMangas).executeAsBlocking()
+        }
+    }
+}

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt

@@ -64,7 +64,7 @@ inline fun View.popupMenu(
     @MenuRes menuRes: Int,
     noinline initMenu: (Menu.() -> Unit)? = null,
     noinline onMenuItemClick: MenuItem.() -> Boolean
-) {
+): PopupMenu {
     val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
     popup.menuInflater.inflate(menuRes, popup.menu)
 
@@ -74,6 +74,7 @@ inline fun View.popupMenu(
     popup.setOnMenuItemClickListener { it.onMenuItemClick() }
 
     popup.show()
+    return popup
 }
 
 /**

+ 31 - 3
app/src/main/java/eu/kanade/tachiyomi/widget/TabbedBottomSheetDialog.kt

@@ -4,21 +4,49 @@ import android.app.Activity
 import android.view.View
 import android.view.ViewGroup
 import com.google.android.material.bottomsheet.BottomSheetDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding
+import eu.kanade.tachiyomi.ui.manga.chapter.SetChapterSettingsDialog
+import eu.kanade.tachiyomi.util.view.popupMenu
 
-abstract class TabbedBottomSheetDialog(private val activity: Activity) : BottomSheetDialog(activity) {
+abstract class TabbedBottomSheetDialog(private val activity: Activity, private val manga: Manga? = null) : BottomSheetDialog(activity) {
+    val binding: CommonTabbedSheetBinding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
 
     init {
-        val binding: CommonTabbedSheetBinding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
-
         val adapter = LibrarySettingsSheetAdapter()
         binding.pager.offscreenPageLimit = 2
         binding.pager.adapter = adapter
         binding.tabs.setupWithViewPager(binding.pager)
 
+        // currently, we only need to show the overflow menu if this is a ChaptersSettingsSheet
+        if (manga != null) {
+            binding.menu.visibility = View.VISIBLE
+            binding.menu.setOnClickListener { it.post { showPopupMenu(it) } }
+        } else {
+            binding.menu.visibility = View.GONE
+        }
+
         setContentView(binding.root)
     }
 
+    private fun showPopupMenu(view: View) {
+        view.popupMenu(
+            R.menu.default_chapter_filter,
+            {
+            },
+            {
+                when (this.itemId) {
+                    R.id.save_as_default -> {
+                        manga?.let { SetChapterSettingsDialog(context, it).showDialog() }
+                        true
+                    }
+                    else -> true
+                }
+            }
+        )
+    }
+
     abstract fun getTabViews(): List<View>
 
     abstract fun getTabTitles(): List<Int>

+ 31 - 6
app/src/main/res/layout/common_tabbed_sheet.xml

@@ -1,17 +1,42 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
-    <com.google.android.material.tabs.TabLayout
-        android:id="@+id/tabs"
-        style="@style/Theme.Widget.Tabs"
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:tabGravity="fill"
-        app:tabMode="fixed" />
+        android:layout_height="wrap_content">
+
+        <com.google.android.material.tabs.TabLayout
+            android:id="@+id/tabs"
+            style="@style/Theme.Widget.Tabs"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/menu"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:tabGravity="fill"
+            app:tabMode="fixed" />
+
+        <ImageButton
+            android:id="@+id/menu"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:background="?selectableItemBackgroundBorderless"
+            android:contentDescription="@string/action_menu"
+            android:paddingStart="10dp"
+            android:paddingEnd="10dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/ic_more_vert_24dp"
+            app:tint="?attr/colorOnBackground" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <eu.kanade.tachiyomi.widget.MaxHeightViewPager
         android:id="@+id/pager"

+ 8 - 0
app/src/main/res/menu/default_chapter_filter.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/save_as_default"
+        android:title="@string/set_chapter_settings_as_default" />
+
+</menu>

+ 6 - 0
app/src/main/res/values/strings.xml

@@ -30,6 +30,7 @@
 
     <!-- Actions -->
     <string name="action_settings">Settings</string>
+    <string name="action_chapter_settings">Chapter Settings</string>
     <string name="action_menu">Menu</string>
     <string name="action_filter">Filter</string>
     <string name="action_filter_downloaded">Downloaded</string>
@@ -524,6 +525,9 @@
     <string name="download_unread">Unread</string>
     <string name="confirm_delete_chapters">Are you sure you want to delete the selected chapters?</string>
     <string name="invalid_download_dir">Invalid download location</string>
+    <string name="confirm_set_chapter_settings">Are you sure you want to save these settings as default?</string>
+    <string name="also_set_chapter_settings_for_library">Also apply to all manga in my Library</string>
+    <string name="set_chapter_settings_as_default">Set as Default</string>
     <string name="no_chapters_error">No chapters found</string>
 
     <!-- Tracking Screen -->
@@ -678,6 +682,7 @@
     <string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
     <string name="information_webview_required">WebView is required for Tachiyomi</string>
     <string name="information_webview_outdated">Please update the WebView app for better compatibility</string>
+    <string name="chapter_settings_updated">Updated default chapter settings</string>
 
     <!-- Download Notification -->
     <string name="download_notifier_downloader_title">Downloader</string>
@@ -705,4 +710,5 @@
     <string name="tapping_inverted_horizontal">Horizontal</string>
     <string name="tapping_inverted_vertical">Vertical</string>
     <string name="tapping_inverted_both">Both</string>
+ 
 </resources>