Set default for chapter sorting method + ui updates + sort by upload date

* New Class: ChapterSort, to handle sorting chapter list with the defaults + filtering if wanted
* Said class is used now to find the next unread chapter, in case sort method is not source order
* Remove close button in chapter filter sheet
* Added "reset" button to sort in filter sheet, to go back to default sort
* New view: SortTextView, like tristatebox but with arrows instead
This commit is contained in:
Jays2Kings 2021-07-11 16:17:36 -04:00
parent 074321865e
commit 469db068e3
16 changed files with 414 additions and 168 deletions

View File

@ -32,12 +32,13 @@ interface Manga : SManga {
fun isBlank() = id == Long.MIN_VALUE fun isBlank() = id == Long.MIN_VALUE
fun isHidden() = status == -1 fun isHidden() = status == -1
fun setChapterOrder(order: Int) { fun setChapterOrder(sorting: Int, order: Int) {
setChapterFlags(sorting, CHAPTER_SORTING_MASK)
setChapterFlags(order, CHAPTER_SORT_MASK) setChapterFlags(order, CHAPTER_SORT_MASK)
setChapterFlags(CHAPTER_SORT_LOCAL, CHAPTER_SORT_SELF_MASK) setChapterFlags(CHAPTER_SORT_LOCAL, CHAPTER_SORT_LOCAL_MASK)
} }
fun setSortToGlobal() = setChapterFlags(CHAPTER_SORT_GLOBAL, CHAPTER_SORT_SELF_MASK) fun setSortToGlobal() = setChapterFlags(CHAPTER_SORT_FILTER_GLOBAL, CHAPTER_SORT_LOCAL_MASK)
private fun setChapterFlags(flag: Int, mask: Int) { private fun setChapterFlags(flag: Int, mask: Int) {
chapter_flags = chapter_flags and mask.inv() or (flag and mask) chapter_flags = chapter_flags and mask.inv() or (flag and mask)
@ -49,11 +50,14 @@ interface Manga : SManga {
fun sortDescending(): Boolean = chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC fun sortDescending(): Boolean = chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC
fun usesLocalSort(): Boolean = chapter_flags and CHAPTER_SORT_SELF_MASK == CHAPTER_SORT_LOCAL fun usesLocalSort(): Boolean = chapter_flags and CHAPTER_SORT_LOCAL_MASK == CHAPTER_SORT_LOCAL
fun sortDescending(defaultDesc: Boolean): Boolean { fun sortDescending(defaultDesc: Boolean): Boolean {
return if (chapter_flags and CHAPTER_SORT_SELF_MASK == CHAPTER_SORT_GLOBAL) defaultDesc return if (usesLocalSort()) sortDescending() else defaultDesc
else sortDescending() }
fun chapterOrder(defaultOrder: Int): Int {
return if (usesLocalSort()) sorting else defaultOrder
} }
fun showChapterTitle(defaultShow: Boolean): Boolean = chapter_flags and CHAPTER_DISPLAY_MASK == CHAPTER_DISPLAY_NUMBER fun showChapterTitle(defaultShow: Boolean): Boolean = chapter_flags and CHAPTER_DISPLAY_MASK == CHAPTER_DISPLAY_NUMBER
@ -228,16 +232,16 @@ interface Manga : SManga {
companion object { companion object {
// Generic filter that does not filter anything
const val SHOW_ALL = 0x00000000
const val CHAPTER_SORT_DESC = 0x00000000 const val CHAPTER_SORT_DESC = 0x00000000
const val CHAPTER_SORT_ASC = 0x00000001 const val CHAPTER_SORT_ASC = 0x00000001
const val CHAPTER_SORT_MASK = 0x00000001 const val CHAPTER_SORT_MASK = 0x00000001
const val CHAPTER_SORT_GLOBAL = 0x00000000 const val CHAPTER_SORT_FILTER_GLOBAL = 0x00000000
const val CHAPTER_SORT_LOCAL = 0x00001000 const val CHAPTER_SORT_LOCAL = 0x00001000
const val CHAPTER_SORT_SELF_MASK = 0x00001000 const val CHAPTER_SORT_LOCAL_MASK = 0x00001000
// Generic filter that does not filter anything
const val SHOW_ALL = 0x00000000
const val CHAPTER_SHOW_UNREAD = 0x00000002 const val CHAPTER_SHOW_UNREAD = 0x00000002
const val CHAPTER_SHOW_READ = 0x00000004 const val CHAPTER_SHOW_READ = 0x00000004
@ -253,7 +257,8 @@ interface Manga : SManga {
const val CHAPTER_SORTING_SOURCE = 0x00000000 const val CHAPTER_SORTING_SOURCE = 0x00000000
const val CHAPTER_SORTING_NUMBER = 0x00000100 const val CHAPTER_SORTING_NUMBER = 0x00000100
const val CHAPTER_SORTING_MASK = 0x00000100 const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200
const val CHAPTER_SORTING_MASK = 0x00000300
const val CHAPTER_DISPLAY_NAME = 0x00000000 const val CHAPTER_DISPLAY_NAME = 0x00000000
const val CHAPTER_DISPLAY_NUMBER = 0x00100000 const val CHAPTER_DISPLAY_NUMBER = 0x00100000

View File

@ -275,8 +275,6 @@ class PreferencesHelper(val context: Context) {
fun uniformGrid() = flowPrefs.getBoolean(Keys.uniformGrid, true) fun uniformGrid() = flowPrefs.getBoolean(Keys.uniformGrid, true)
fun chaptersDescAsDefault() = rxPrefs.getBoolean("chapters_desc_as_default", true)
fun downloadBadge() = rxPrefs.getBoolean(Keys.downloadBadge, false) fun downloadBadge() = rxPrefs.getBoolean(Keys.downloadBadge, false)
fun filterDownloaded() = rxPrefs.getInteger(Keys.filterDownloaded, 0) fun filterDownloaded() = rxPrefs.getInteger(Keys.filterDownloaded, 0)
@ -432,10 +430,12 @@ class PreferencesHelper(val context: Context) {
fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL) fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL)
fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.CHAPTER_SORTING_SOURCE) fun sortChapterOrder() = flowPrefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.CHAPTER_SORTING_SOURCE)
fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.CHAPTER_DISPLAY_NAME) fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.CHAPTER_DISPLAY_NAME)
fun chaptersDescAsDefault() = rxPrefs.getBoolean("chapters_desc_as_default", true)
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.CHAPTER_SORT_DESC) fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.CHAPTER_SORT_DESC)
fun setChapterSettingsDefault(manga: Manga) { fun setChapterSettingsDefault(manga: Manga) {

View File

@ -30,6 +30,8 @@ import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_E
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_IGNORE import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_IGNORE
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_INCLUDE import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_INCLUDE
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.lang.capitalizeWords import eu.kanade.tachiyomi.util.lang.capitalizeWords
import eu.kanade.tachiyomi.util.lang.chopByWords import eu.kanade.tachiyomi.util.lang.chopByWords
import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.lang.removeArticles
@ -59,7 +61,8 @@ class LibraryPresenter(
private val preferences: PreferencesHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get() private val downloadManager: DownloadManager = Injekt.get(),
private val chapterFilter: ChapterFilter = Injekt.get()
) : BaseCoroutinePresenter() { ) : BaseCoroutinePresenter() {
private val context = preferences.context private val context = preferences.context
@ -847,7 +850,7 @@ class LibraryPresenter(
/** Returns first unread chapter of a manga */ /** Returns first unread chapter of a manga */
fun getFirstUnread(manga: Manga): Chapter? { fun getFirstUnread(manga: Manga): Chapter? {
val chapters = db.getChapters(manga).executeAsBlocking() val chapters = db.getChapters(manga).executeAsBlocking()
return chapters.sortedByDescending { it.source_order }.find { !it.read } return ChapterSort(manga, chapterFilter, preferences).getNextUnreadChapter(chapters, false)
} }
/** Update a category's sorting */ /** Update a category's sorting */

View File

@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.ui.setting.SettingsReaderController import eu.kanade.tachiyomi.ui.setting.SettingsReaderController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -118,13 +119,15 @@ class SearchActivity : MainActivity() {
if (mangaId != 0L) { if (mangaId != 0L) {
val db = Injekt.get<DatabaseHelper>() val db = Injekt.get<DatabaseHelper>()
val chapters = db.getChapters(mangaId).executeAsBlocking() val chapters = db.getChapters(mangaId).executeAsBlocking()
val nextUnreadChapter = chapters.sortedByDescending { it.source_order }.find { !it.read } db.getManga(mangaId).executeAsBlocking()?.let { manga ->
val manga = db.getManga(mangaId).executeAsBlocking() val nextUnreadChapter = ChapterSort(manga).getNextUnreadChapter(chapters, false)
if (nextUnreadChapter != null && manga != null) { if (nextUnreadChapter != null) {
val activity = ReaderActivity.newIntent(this, manga, nextUnreadChapter) val activity =
startActivity(activity) ReaderActivity.newIntent(this, manga, nextUnreadChapter)
finish() startActivity(activity)
return true finish()
return true
}
} }
} }
} }

View File

@ -41,6 +41,7 @@ import eu.kanade.tachiyomi.ui.manga.track.SetTrackReadingDatesDialog
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.chapter.ChapterUtil
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
@ -85,6 +86,8 @@ class MangaDetailsPresenter(
private val customMangaManager: CustomMangaManager by injectLazy() private val customMangaManager: CustomMangaManager by injectLazy()
private val mangaShortcutManager: MangaShortcutManager by injectLazy() private val mangaShortcutManager: MangaShortcutManager by injectLazy()
private val chapterSort by lazy { ChapterSort(manga, chapterFilter, preferences) }
var isLockedFromSearch = false var isLockedFromSearch = false
var hasRequested = false var hasRequested = false
var isLoading = false var isLoading = false
@ -223,6 +226,8 @@ class MangaDetailsPresenter(
*/ */
fun sortDescending() = manga.sortDescending(globalSort()) fun sortDescending() = manga.sortDescending(globalSort())
fun sortingOrder() = manga.chapterOrder(globalSorting())
/** /**
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
* @param chapterList the list of chapters from the database * @param chapterList the list of chapters from the database
@ -232,22 +237,8 @@ class MangaDetailsPresenter(
if (isLockedFromSearch) { if (isLockedFromSearch) {
return chapterList return chapterList
} }
getScrollType(chapterList)
val chapters = chapterFilter.filterChapters(chapterList, manga) return chapterSort.getChaptersSorted(chapterList)
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
Manga.CHAPTER_SORTING_SOURCE -> when (sortDescending()) {
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
}
Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending()) {
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
}
else -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
}
getScrollType(chapters)
return chapters.sortedWith(Comparator(sortFunction))
} }
private fun getScrollType(chapters: List<ChapterItem>) { private fun getScrollType(chapters: List<ChapterItem>) {
@ -263,7 +254,7 @@ class MangaDetailsPresenter(
* Returns the next unread chapter or null if everything is read. * Returns the next unread chapter or null if everything is read.
*/ */
fun getNextUnreadChapter(): ChapterItem? { fun getNextUnreadChapter(): ChapterItem? {
return chapters.sortedByDescending { it.source_order }.find { !it.read } return chapterSort.getNextUnreadChapter(chapters)
} }
fun anyRead(): Boolean = allChapters.any { it.read } fun anyRead(): Boolean = allChapters.any { it.read }
@ -272,7 +263,7 @@ class MangaDetailsPresenter(
fun getUnreadChaptersSorted() = fun getUnreadChaptersSorted() =
allChapters.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }.distinctBy { it.name } allChapters.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }.distinctBy { it.name }
.sortedByDescending { it.source_order } .sortedWith(chapterSort.sortComparator(true))
fun startDownloadingNow(chapter: Chapter) { fun startDownloadingNow(chapter: Chapter) {
downloadManager.startDownloadNow(chapter) downloadManager.startDownloadNow(chapter)
@ -508,24 +499,31 @@ class MangaDetailsPresenter(
/** /**
* Sets the sorting order and requests an UI update. * Sets the sorting order and requests an UI update.
*/ */
fun setSortOrder(descend: Boolean) { fun setSortOrder(sort: Int, descend: Boolean) {
manga.setChapterOrder(if (descend) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC) manga.setChapterOrder(sort, if (descend) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
if (mangaSortMatchesDefault()) {
manga.setSortToGlobal()
}
asyncUpdateMangaAndChapters() asyncUpdateMangaAndChapters()
} }
fun globalSort(): Boolean = preferences.chaptersDescAsDefault().getOrDefault() private fun globalSort(): Boolean = preferences.chaptersDescAsDefault().getOrDefault()
fun setGlobalChapterSort(descend: Boolean) { private fun globalSorting(): Int = preferences.sortChapterOrder().get()
fun mangaSortMatchesDefault(): Boolean {
return (manga.sortDescending() == globalSort() && manga.sorting == globalSorting()) || !manga.usesLocalSort()
}
fun setGlobalChapterSort(sort: Int, descend: Boolean) {
preferences.sortChapterOrder().set(sort)
preferences.chaptersDescAsDefault().set(descend) preferences.chaptersDescAsDefault().set(descend)
manga.setSortToGlobal() manga.setSortToGlobal()
asyncUpdateMangaAndChapters() asyncUpdateMangaAndChapters()
} }
/** fun resetSortingToDefault() {
* Sets the sorting method and requests an UI update. manga.setSortToGlobal()
*/
fun setSortMethod(bySource: Boolean) {
manga.sorting = if (bySource) Manga.CHAPTER_SORTING_SOURCE else Manga.CHAPTER_SORTING_NUMBER
asyncUpdateMangaAndChapters() asyncUpdateMangaAndChapters()
} }

View File

@ -4,15 +4,14 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.ChapterSortBottomSheetBinding import eu.kanade.tachiyomi.databinding.ChapterSortBottomSheetBinding
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.setBottomEdge import eu.kanade.tachiyomi.util.view.setBottomEdge
import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog
import eu.kanade.tachiyomi.widget.SortTextView
import kotlin.math.max import kotlin.math.max
class ChaptersSortBottomSheet(controller: MangaDetailsController) : class ChaptersSortBottomSheet(controller: MangaDetailsController) :
@ -58,12 +57,10 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
initGeneralPreferences() initGeneralPreferences()
setBottomEdge(binding.hideTitles, activity) setBottomEdge(binding.hideTitles, activity)
binding.closeButton.setOnClickListener { dismiss() }
binding.settingsScrollView.viewTreeObserver.addOnGlobalLayoutListener { binding.settingsScrollView.viewTreeObserver.addOnGlobalLayoutListener {
val isScrollable = val isScrollable =
binding.settingsScrollView.height < binding.sortLayout.height + binding.settingsScrollView.height < binding.sortLayout.height +
binding.settingsScrollView.paddingTop + binding.settingsScrollView.paddingBottom binding.settingsScrollView.paddingTop + binding.settingsScrollView.paddingBottom
binding.closeButton.isVisible = isScrollable
// making the view gone somehow breaks the layout so lets make it invisible // making the view gone somehow breaks the layout so lets make it invisible
binding.pill.isInvisible = isScrollable binding.pill.isInvisible = isScrollable
} }
@ -80,43 +77,89 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) :
private fun initGeneralPreferences() { private fun initGeneralPreferences() {
binding.chapterFilterLayout.root.setCheckboxes(presenter.manga) binding.chapterFilterLayout.root.setCheckboxes(presenter.manga)
var defPref = presenter.globalSort() binding.byChapterNumber.state = SortTextView.State.NONE
binding.sortGroup.check( binding.byUploadDate.state = SortTextView.State.NONE
if (presenter.manga.sortDescending(defPref)) R.id.sort_newest else { binding.bySource.state = SortTextView.State.NONE
R.id.sort_oldest
} val sortItem = when (presenter.sortingOrder()) {
) Manga.CHAPTER_SORTING_NUMBER -> binding.byChapterNumber
Manga.CHAPTER_SORTING_UPLOAD_DATE -> binding.byUploadDate
else -> binding.bySource
}
sortItem.state = if (presenter.sortDescending()) {
SortTextView.State.DESCENDING
} else {
SortTextView.State.ASCENDING
}
checkIfSortMatchesDefault()
binding.byChapterNumber.setOnSortChangeListener(::sortChanged)
binding.byUploadDate.setOnSortChangeListener(::sortChanged)
binding.bySource.setOnSortChangeListener(::sortChanged)
binding.hideTitles.isChecked = presenter.manga.displayMode != Manga.CHAPTER_DISPLAY_NAME binding.hideTitles.isChecked = presenter.manga.displayMode != Manga.CHAPTER_DISPLAY_NAME
binding.sortMethodGroup.check(
if (presenter.manga.sorting == Manga.CHAPTER_SORTING_SOURCE) R.id.sort_by_source else {
R.id.sort_by_number
}
)
binding.setAsDefaultSort.isInvisible = defPref == presenter.manga.sortDescending() ||
!presenter.manga.usesLocalSort()
binding.sortGroup.setOnCheckedChangeListener { _, checkedId ->
presenter.setSortOrder(checkedId == R.id.sort_newest)
binding.setAsDefaultSort.isInvisible = (
defPref == presenter.manga.sortDescending() ||
!presenter.manga.usesLocalSort()
)
}
binding.setAsDefaultSort.setOnClickListener { binding.setAsDefaultSort.setOnClickListener {
val desc = binding.sortGroup.checkedRadioButtonId == R.id.sort_newest presenter.setGlobalChapterSort(
presenter.setGlobalChapterSort(desc) presenter.manga.sorting,
defPref = desc presenter.manga.sortDescending()
)
binding.setAsDefaultSort.isInvisible = true binding.setAsDefaultSort.isInvisible = true
binding.resetAsDefaultSort.isInvisible = true
} }
binding.sortMethodGroup.setOnCheckedChangeListener { _, checkedId -> binding.resetAsDefaultSort.setOnClickListener {
presenter.setSortMethod(checkedId == R.id.sort_by_source) presenter.resetSortingToDefault()
binding.byChapterNumber.state = SortTextView.State.NONE
binding.byUploadDate.state = SortTextView.State.NONE
binding.bySource.state = SortTextView.State.NONE
val sortItemNew = when (presenter.sortingOrder()) {
Manga.CHAPTER_SORTING_NUMBER -> binding.byChapterNumber
Manga.CHAPTER_SORTING_UPLOAD_DATE -> binding.byUploadDate
else -> binding.bySource
}
sortItemNew.state = if (presenter.sortDescending()) {
SortTextView.State.DESCENDING
} else {
SortTextView.State.ASCENDING
}
binding.setAsDefaultSort.isInvisible = true
binding.resetAsDefaultSort.isInvisible = true
} }
binding.hideTitles.setOnCheckedChangeListener { _, isChecked -> binding.hideTitles.setOnCheckedChangeListener { _, isChecked ->
presenter.hideTitle(isChecked) presenter.hideTitle(isChecked)
} }
} }
private fun checkIfSortMatchesDefault() {
val matches = presenter.mangaSortMatchesDefault()
binding.setAsDefaultSort.isInvisible = matches
binding.resetAsDefaultSort.isInvisible = matches
}
private fun sortChanged(sortTextView: SortTextView, state: SortTextView.State) {
if (sortTextView != binding.byChapterNumber) {
binding.byChapterNumber.state = SortTextView.State.NONE
}
if (sortTextView != binding.byUploadDate) {
binding.byUploadDate.state = SortTextView.State.NONE
}
if (sortTextView != binding.bySource) {
binding.bySource.state = SortTextView.State.NONE
}
presenter.setSortOrder(
when (sortTextView) {
binding.byChapterNumber -> Manga.CHAPTER_SORTING_NUMBER
binding.byUploadDate -> Manga.CHAPTER_SORTING_UPLOAD_DATE
else -> Manga.CHAPTER_SORTING_SOURCE
},
state == SortTextView.State.DESCENDING
)
checkIfSortMatchesDefault()
}
} }

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.reader package eu.kanade.tachiyomi.ui.reader
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import uy.kohesive.injekt.api.get
/** /**
* Load strategy using the source order. This is the default ordering. * Load strategy using the source order. This is the default ordering.
@ -35,3 +36,12 @@ class ChapterLoadByNumber {
return chapters.sortedBy { it.chapter_number } return chapters.sortedBy { it.chapter_number }
} }
} }
/**
* Load strategy using the source order. This is the default ordering.
*/
class ChapterLoadByDate {
fun get(allChapters: List<Chapter>): List<Chapter> {
return allChapters.sortedBy { it.date_upload }
}
}

View File

@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.track.DelayedTrackingUpdateJob import eu.kanade.tachiyomi.data.track.DelayedTrackingUpdateJob
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
@ -33,6 +32,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.settings.OrientationType import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
@ -118,9 +118,10 @@ class ReaderPresenter(
val chaptersForReader = val chaptersForReader =
chapterFilter.filterChaptersForReader(dbChapters, manga, selectedChapter) chapterFilter.filterChaptersForReader(dbChapters, manga, selectedChapter)
when (manga.sorting) { when (manga.chapterOrder(preferences.sortChapterOrder().get())) {
Manga.CHAPTER_SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader) Manga.CHAPTER_SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader)
Manga.CHAPTER_SORTING_NUMBER -> ChapterLoadByNumber().get(chaptersForReader, selectedChapter) Manga.CHAPTER_SORTING_NUMBER -> ChapterLoadByNumber().get(chaptersForReader, selectedChapter)
Manga.CHAPTER_SORTING_UPLOAD_DATE -> ChapterLoadByDate().get(chaptersForReader)
else -> error("Unknown sorting method") else -> error("Unknown sorting method")
}.map(::ReaderChapter) }.map(::ReaderChapter)
} }
@ -212,25 +213,14 @@ class ReaderPresenter(
suspend fun getChapters(): List<ReaderChapterItem> { suspend fun getChapters(): List<ReaderChapterItem> {
val manga = manga ?: return emptyList() val manga = manga ?: return emptyList()
chapterItems = withContext(Dispatchers.IO) { chapterItems = withContext(Dispatchers.IO) {
val chapterSort = ChapterSort(manga, chapterFilter, preferences)
val dbChapters = db.getChapters(manga).executeAsBlocking() val dbChapters = db.getChapters(manga).executeAsBlocking()
val list = chapterSort.getChaptersSorted(dbChapters, filterForReader = true, currentChapter = getCurrentChapter()?.chapter).map {
chapterFilter.filterChaptersForReader(dbChapters, manga, getCurrentChapter()?.chapter) ReaderChapterItem(
.sortedBy { it,
when (manga.sorting) { manga,
Manga.CHAPTER_SORTING_NUMBER -> it.chapter_number it.id == getCurrentChapter()?.chapter?.id ?: chapterId
else -> it.source_order.toFloat() )
}
}.map {
ReaderChapterItem(
it,
manga,
it.id == getCurrentChapter()?.chapter?.id ?: chapterId
)
}
if (!manga.sortDescending(preferences.chaptersDescAsDefault().getOrDefault())) {
list.reversed()
} else {
list
} }
} }

View File

@ -16,6 +16,8 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
@ -39,7 +41,8 @@ class RecentsPresenter(
val controller: RecentsController?, val controller: RecentsController?,
val preferences: PreferencesHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(), val downloadManager: DownloadManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get() private val db: DatabaseHelper = Injekt.get(),
private val chapterFilter: ChapterFilter = Injekt.get()
) : BaseCoroutinePresenter(), DownloadQueue.DownloadListener, LibraryServiceListener, DownloadServiceListener { ) : BaseCoroutinePresenter(), DownloadQueue.DownloadListener, LibraryServiceListener, DownloadServiceListener {
private var recentsJob: Job? = null private var recentsJob: Job? = null
@ -319,12 +322,12 @@ class RecentsPresenter(
private fun getNextChapter(manga: Manga): Chapter? { private fun getNextChapter(manga: Manga): Chapter? {
val chapters = db.getChapters(manga).executeAsBlocking() val chapters = db.getChapters(manga).executeAsBlocking()
return chapters.sortedByDescending { it.source_order }.find { !it.read } return ChapterSort(manga, chapterFilter, preferences).getNextUnreadChapter(chapters, false)
} }
private fun getFirstUpdatedChapter(manga: Manga, chapter: Chapter): Chapter? { private fun getFirstUpdatedChapter(manga: Manga, chapter: Chapter): Chapter? {
val chapters = db.getChapters(manga).executeAsBlocking() val chapters = db.getChapters(manga).executeAsBlocking()
return chapters.sortedByDescending { it.source_order }.find { return chapters.sortedWith(ChapterSort(manga, chapterFilter, preferences).sortComparator(true)).find {
!it.read && abs(it.date_fetch - chapter.date_fetch) <= TimeUnit.HOURS.toMillis(12) !it.read && abs(it.date_fetch - chapter.date_fetch) <= TimeUnit.HOURS.toMillis(12)
} }
} }

View File

@ -38,7 +38,7 @@ class ChapterFilter(val preferences: PreferencesHelper = Injekt.get(), val downl
} }
// filter chapters for the reader // filter chapters for the reader
fun filterChaptersForReader(chapters: List<Chapter>, manga: Manga, selectedChapter: Chapter? = null): List<Chapter> { fun <T : Chapter> filterChaptersForReader(chapters: List<T>, manga: Manga, selectedChapter: T? = null): List<T> {
// if neither preference is enabled don't even filter // if neither preference is enabled don't even filter
if (!preferences.skipRead() && !preferences.skipFiltered()) { if (!preferences.skipRead() && !preferences.skipFiltered()) {
return chapters return chapters

View File

@ -0,0 +1,62 @@
package eu.kanade.tachiyomi.util.chapter
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ChapterSort(val manga: Manga, val chapterFilter: ChapterFilter = Injekt.get(), val preferences: PreferencesHelper = Injekt.get()) {
fun <T : Chapter> getChaptersSorted(
rawChapters: List<T>,
andFiltered: Boolean = true,
filterForReader: Boolean = false,
currentChapter: T? = null
): List<T> {
val chapters = when {
filterForReader -> chapterFilter.filterChaptersForReader(
rawChapters,
manga,
currentChapter
)
andFiltered -> chapterFilter.filterChapters(rawChapters, manga)
else -> rawChapters
}
val sortDescending =
manga.sortDescending(preferences.chaptersDescAsDefault().getOrDefault())
return chapters.sortedWith(sortComparator())
}
fun <T : Chapter> getNextUnreadChapter(rawChapters: List<T>, andFiltered: Boolean = true,): T? {
val chapters = when {
andFiltered -> chapterFilter.filterChapters(rawChapters, manga)
else -> rawChapters
}
return chapters.sortedWith(sortComparator(true)).find { !it.read }
}
fun <T : Chapter> sortComparator(ignoreAsc: Boolean = false): Comparator<T> {
val sortDescending = !ignoreAsc &&
manga.sortDescending(preferences.chaptersDescAsDefault().getOrDefault())
val sortFunction: (T, T) -> Int =
when (manga.chapterOrder(preferences.sortChapterOrder().get())) {
Manga.CHAPTER_SORTING_SOURCE -> when (sortDescending) {
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
}
Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending) {
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
}
Manga.CHAPTER_SORTING_UPLOAD_DATE -> when (sortDescending) {
true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) }
false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
}
else -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
}
return Comparator(sortFunction)
}
}

View File

@ -0,0 +1,106 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SortTextViewBinding
import eu.kanade.tachiyomi.util.view.setVectorCompat
class SortTextView constructor(context: Context, attrs: AttributeSet?) :
FrameLayout(context, attrs) {
var text: CharSequence
get() {
return binding.textView.text
}
set(value) {
binding.textView.text = value
}
var state: State = State.NONE
set(value) {
field = value
updateDrawable()
}
val isSorting: Boolean
get() = state != State.NONE
private val binding = SortTextViewBinding.inflate(
LayoutInflater.from(context),
this,
false
)
private var mOnSortChangeListener: OnSortChangeListener? = null
init {
addView(binding.root)
val a = context.obtainStyledAttributes(attrs, R.styleable.SortTextView, 0, 0)
val str = a.getString(R.styleable.SortTextView_android_text) ?: ""
text = str
val maxLines = a.getInt(R.styleable.SortTextView_android_maxLines, Int.MAX_VALUE)
binding.textView.maxLines = maxLines
a.recycle()
setOnClickListener {
state =
when (state) {
State.DESCENDING -> State.ASCENDING
else -> State.DESCENDING
}
mOnSortChangeListener?.onSortChanged(this, state)
}
}
/**
* Register a callback to be invoked when the checked state of this button
* changes.
*
* @param listener the callback to call on checked state change
*/
fun setOnSortChangeListener(listener: OnSortChangeListener?) {
mOnSortChangeListener = listener
}
fun updateDrawable() {
with(binding.sortImageView) {
when (state) {
State.ASCENDING -> {
setVectorCompat(R.drawable.ic_arrow_upward_24dp, R.attr.colorAccent)
}
State.DESCENDING -> {
setVectorCompat(R.drawable.ic_arrow_downward_24dp, R.attr.colorAccent)
}
State.NONE -> {
setVectorCompat(R.drawable.ic_blank_24dp, R.attr.colorAccentText)
}
}
}
}
enum class State {
ASCENDING,
DESCENDING,
NONE,
;
}
/**
* Interface definition for a callback to be invoked when the checked state
* of a compound button changed.
*/
fun interface OnSortChangeListener {
/**
* Called when the checked state of a compound button has changed.
*
* @param buttonView The compound button view whose state has changed.
* @param state The new checked state of buttonView.
*/
fun onSortChanged(buttonView: SortTextView, state: State)
}
}

View File

@ -16,20 +16,24 @@
style="@style/BottomSheetDialogTheme" style="@style/BottomSheetDialogTheme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="12dp" android:paddingTop="12dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sort_title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/sort_title"
style="@style/TextAppearance.MaterialComponents.Headline6" style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/sort" /> android:text="@string/sort" />
@ -38,33 +42,44 @@
android:id="@+id/set_as_default_sort" android:id="@+id/set_as_default_sort"
style="@style/Theme.Widget.Button.TextButton" style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="38sp"
android:text="@string/set_as_default_for_all" /> android:padding="4dp"
</LinearLayout> android:layout_marginStart="6dp"
android:layout_gravity="center_vertical"
app:layout_constraintBaseline_toBaselineOf="@id/sort_title"
app:layout_constraintStart_toEndOf="@id/sort_title"
android:text="@string/set_as_default" />
<RadioGroup <com.google.android.material.button.MaterialButton
android:id="@+id/sort_group" android:id="@+id/reset_as_default_sort"
android:layout_width="wrap_content" style="@style/Theme.Widget.Button.TextButton"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_newest"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="38sp"
android:layout_weight="1" android:padding="4dp"
android:text="@string/newest_first" /> android:layout_marginEnd="6dp"
android:layout_gravity="center_vertical"
app:layout_constraintBaseline_toBaselineOf="@id/sort_title"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/reset" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.radiobutton.MaterialRadioButton <eu.kanade.tachiyomi.widget.SortTextView
android:id="@+id/sort_oldest" android:id="@+id/by_source"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="@string/by_source_order"
android:layout_marginStart="12dp" android:layout_height="wrap_content"/>
android:layout_weight="1"
android:text="@string/oldest_first" /> <eu.kanade.tachiyomi.widget.SortTextView
</RadioGroup> android:id="@+id/by_chapter_number"
android:layout_width="match_parent"
android:text="@string/by_chapter_number"
android:layout_height="wrap_content"/>
<eu.kanade.tachiyomi.widget.SortTextView
android:id="@+id/by_upload_date"
android:layout_width="match_parent"
android:text="@string/by_update_date"
android:layout_height="wrap_content"/>
<include layout="@layout/chapter_filter_layout" <include layout="@layout/chapter_filter_layout"
android:id="@+id/chapter_filter_layout" android:id="@+id/chapter_filter_layout"
@ -80,27 +95,6 @@
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/more" /> android:text="@string/more" />
<RadioGroup
android:id="@+id/sort_method_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_by_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sort_by_source_s_order" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_by_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sort_by_chapter_number" />
</RadioGroup>
<com.google.android.material.checkbox.MaterialCheckBox <com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/hide_titles" android:id="@+id/hide_titles"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -122,18 +116,4 @@
android:contentDescription="@string/drag_handle" android:contentDescription="@string/drag_handle"
android:src="@drawable/draggable_pill" android:src="@drawable/draggable_pill"
app:tint="?android:attr/textColorPrimary" /> app:tint="?android:attr/textColorPrimary" />
<ImageView
android:id="@+id/close_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/round_ripple"
android:clickable="true"
android:contentDescription="@string/close"
android:focusable="true"
android:src="@drawable/ic_close_24dp"
app:tint="@color/gray_button" />
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:background="?selectableItemBackground"
android:paddingBottom="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/sort_image_view"
android:contentDescription="@string/sort"
android:layout_marginStart="12dp"
android:padding="4dp"
tools:srcCompat="@drawable/ic_arrow_upward_24dp"
app:srcCompat="@drawable/ic_blank_24dp"
app:tint="?colorAccent"
style="@style/MD_ListItem_Control" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_view"
android:layout_marginStart="6dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="14sp"
style="@style/TextAppearance.Regular"
tools:text="Item" />
</LinearLayout>

View File

@ -31,6 +31,12 @@
<attr name="android:maxLines"/> <attr name="android:maxLines"/>
</declare-styleable> </declare-styleable>
<declare-styleable name="SortTextView">
<attr name="android:text" format="reference|string"/>
<attr name="android:maxLines"/>
</declare-styleable>
<declare-styleable name="MenuSheetItemView"> <declare-styleable name="MenuSheetItemView">
<attr name="android:text"/> <attr name="android:text"/>
<attr name="android:maxLines"/> <attr name="android:maxLines"/>

View File

@ -59,6 +59,9 @@
<string name="no_pages_found">No pages found</string> <string name="no_pages_found">No pages found</string>
<string name="remove_all_downloads">Remove all downloads?</string> <string name="remove_all_downloads">Remove all downloads?</string>
<string name="no_chapters_to_delete">No chapters to delete</string> <string name="no_chapters_to_delete">No chapters to delete</string>
<string name="by_source_order">By source\'s order</string>
<string name="by_chapter_number">By chapter number</string>
<string name="by_update_date">By upload date</string>
<plurals name="remove_n_chapters"> <plurals name="remove_n_chapters">
<item quantity="one">Remove %1$d downloaded chapter?</item> <item quantity="one">Remove %1$d downloaded chapter?</item>
@ -508,6 +511,7 @@
<string name="error_saving_cover">Error saving cover</string> <string name="error_saving_cover">Error saving cover</string>
<string name="error_sharing_cover">Error sharing cover</string> <string name="error_sharing_cover">Error sharing cover</string>
<string name="custom_manga_info">Custom manga info</string> <string name="custom_manga_info">Custom manga info</string>
<string name="set_as_default">Set as default</string>
<plurals name="deleted_chapters"> <plurals name="deleted_chapters">
<item quantity="one">A chapter has been removed from the source:\n%2$s\nDelete <item quantity="one">A chapter has been removed from the source:\n%2$s\nDelete
its download?</item> its download?</item>