[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 <arkon@users.noreply.github.com>
This commit is contained in:
lmj0011 2020-09-14 14:58:34 -05:00 committed by GitHub
parent 791a7d5a01
commit 64050e8266
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 252 additions and 19 deletions

View file

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

View file

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

View file

@ -165,6 +165,18 @@ object PreferenceKeys {
const val enableDoh = "enable_doh" 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 trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId" fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"

View file

@ -2,11 +2,15 @@ package eu.kanade.tachiyomi.data.preference
import android.content.Context import android.content.Context
import android.os.Environment import android.os.Environment
import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R 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.DisplayMode
import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -18,8 +22,6 @@ import java.io.File
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale 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) @OptIn(ExperimentalCoroutinesApi::class)
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> { 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 trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false) 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) }
}
} }

View file

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

View file

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

View file

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

View file

@ -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()
}

View file

@ -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()
}
}
}

View file

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

View file

@ -4,21 +4,49 @@ import android.app.Activity
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialog 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.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 { init {
val binding: CommonTabbedSheetBinding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
val adapter = LibrarySettingsSheetAdapter() val adapter = LibrarySettingsSheetAdapter()
binding.pager.offscreenPageLimit = 2 binding.pager.offscreenPageLimit = 2
binding.pager.adapter = adapter binding.pager.adapter = adapter
binding.tabs.setupWithViewPager(binding.pager) 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) 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 getTabViews(): List<View>
abstract fun getTabTitles(): List<Int> abstract fun getTabTitles(): List<Int>

View file

@ -1,17 +1,42 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.tabs.TabLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tabs"
style="@style/Theme.Widget.Tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
app:tabGravity="fill"
app:tabMode="fixed" /> <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 <eu.kanade.tachiyomi.widget.MaxHeightViewPager
android:id="@+id/pager" android:id="@+id/pager"

View file

@ -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>

View file

@ -30,6 +30,7 @@
<!-- Actions --> <!-- Actions -->
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<string name="action_chapter_settings">Chapter Settings</string>
<string name="action_menu">Menu</string> <string name="action_menu">Menu</string>
<string name="action_filter">Filter</string> <string name="action_filter">Filter</string>
<string name="action_filter_downloaded">Downloaded</string> <string name="action_filter_downloaded">Downloaded</string>
@ -524,6 +525,9 @@
<string name="download_unread">Unread</string> <string name="download_unread">Unread</string>
<string name="confirm_delete_chapters">Are you sure you want to delete the selected chapters?</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="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> <string name="no_chapters_error">No chapters found</string>
<!-- Tracking Screen --> <!-- Tracking Screen -->
@ -678,6 +682,7 @@
<string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string> <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_required">WebView is required for Tachiyomi</string>
<string name="information_webview_outdated">Please update the WebView app for better compatibility</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 --> <!-- Download Notification -->
<string name="download_notifier_downloader_title">Downloader</string> <string name="download_notifier_downloader_title">Downloader</string>
@ -705,4 +710,5 @@
<string name="tapping_inverted_horizontal">Horizontal</string> <string name="tapping_inverted_horizontal">Horizontal</string>
<string name="tapping_inverted_vertical">Vertical</string> <string name="tapping_inverted_vertical">Vertical</string>
<string name="tapping_inverted_both">Both</string> <string name="tapping_inverted_both">Both</string>
</resources> </resources>