diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index 1a4d69d326..09660caf87 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -32,12 +32,13 @@ interface Manga : SManga { fun isBlank() = id == Long.MIN_VALUE 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(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) { 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 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 { - return if (chapter_flags and CHAPTER_SORT_SELF_MASK == CHAPTER_SORT_GLOBAL) defaultDesc - else sortDescending() + return if (usesLocalSort()) sortDescending() else defaultDesc + } + + 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 @@ -228,16 +232,16 @@ interface Manga : SManga { companion object { + // Generic filter that does not filter anything + const val SHOW_ALL = 0x00000000 + const val CHAPTER_SORT_DESC = 0x00000000 const val CHAPTER_SORT_ASC = 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_SELF_MASK = 0x00001000 - - // Generic filter that does not filter anything - const val SHOW_ALL = 0x00000000 + const val CHAPTER_SORT_LOCAL_MASK = 0x00001000 const val CHAPTER_SHOW_UNREAD = 0x00000002 const val CHAPTER_SHOW_READ = 0x00000004 @@ -253,7 +257,8 @@ interface Manga : SManga { const val CHAPTER_SORTING_SOURCE = 0x00000000 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_NUMBER = 0x00100000 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 7f6e071fc0..eb8d051bb0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -275,8 +275,6 @@ class PreferencesHelper(val context: Context) { fun uniformGrid() = flowPrefs.getBoolean(Keys.uniformGrid, true) - fun chaptersDescAsDefault() = rxPrefs.getBoolean("chapters_desc_as_default", true) - fun downloadBadge() = rxPrefs.getBoolean(Keys.downloadBadge, false) 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 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 chaptersDescAsDefault() = rxPrefs.getBoolean("chapters_desc_as_default", true) + fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.CHAPTER_SORT_DESC) fun setChapterSettingsDefault(manga: Manga) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 320870a7a0..b87316ba2f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -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_INCLUDE 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.chopByWords import eu.kanade.tachiyomi.util.lang.removeArticles @@ -59,7 +61,8 @@ class LibraryPresenter( private val preferences: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = 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() { private val context = preferences.context @@ -847,7 +850,7 @@ class LibraryPresenter( /** Returns first unread chapter of a manga */ fun getFirstUnread(manga: Manga): Chapter? { 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 */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index 8c8bacec73..63787fac94 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.ui.setting.SettingsController import eu.kanade.tachiyomi.ui.setting.SettingsReaderController import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController +import eu.kanade.tachiyomi.util.chapter.ChapterSort import eu.kanade.tachiyomi.util.view.withFadeTransaction import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -118,13 +119,15 @@ class SearchActivity : MainActivity() { if (mangaId != 0L) { val db = Injekt.get() val chapters = db.getChapters(mangaId).executeAsBlocking() - val nextUnreadChapter = chapters.sortedByDescending { it.source_order }.find { !it.read } - val manga = db.getManga(mangaId).executeAsBlocking() - if (nextUnreadChapter != null && manga != null) { - val activity = ReaderActivity.newIntent(this, manga, nextUnreadChapter) - startActivity(activity) - finish() - return true + db.getManga(mangaId).executeAsBlocking()?.let { manga -> + val nextUnreadChapter = ChapterSort(manga).getNextUnreadChapter(chapters, false) + if (nextUnreadChapter != null) { + val activity = + ReaderActivity.newIntent(this, manga, nextUnreadChapter) + startActivity(activity) + finish() + return true + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index 0472cb57cc..390a9ee308 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -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.security.SecureActivityDelegate 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.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay @@ -85,6 +86,8 @@ class MangaDetailsPresenter( private val customMangaManager: CustomMangaManager by injectLazy() private val mangaShortcutManager: MangaShortcutManager by injectLazy() + private val chapterSort by lazy { ChapterSort(manga, chapterFilter, preferences) } + var isLockedFromSearch = false var hasRequested = false var isLoading = false @@ -223,6 +226,8 @@ class MangaDetailsPresenter( */ fun sortDescending() = manga.sortDescending(globalSort()) + fun sortingOrder() = manga.chapterOrder(globalSorting()) + /** * Applies the view filters to the list of chapters obtained from the database. * @param chapterList the list of chapters from the database @@ -232,22 +237,8 @@ class MangaDetailsPresenter( if (isLockedFromSearch) { return chapterList } - - val chapters = chapterFilter.filterChapters(chapterList, manga) - - 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)) + getScrollType(chapterList) + return chapterSort.getChaptersSorted(chapterList) } private fun getScrollType(chapters: List) { @@ -263,7 +254,7 @@ class MangaDetailsPresenter( * Returns the next unread chapter or null if everything is read. */ fun getNextUnreadChapter(): ChapterItem? { - return chapters.sortedByDescending { it.source_order }.find { !it.read } + return chapterSort.getNextUnreadChapter(chapters) } fun anyRead(): Boolean = allChapters.any { it.read } @@ -272,7 +263,7 @@ class MangaDetailsPresenter( fun getUnreadChaptersSorted() = 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) { downloadManager.startDownloadNow(chapter) @@ -508,24 +499,31 @@ class MangaDetailsPresenter( /** * Sets the sorting order and requests an UI update. */ - fun setSortOrder(descend: Boolean) { - manga.setChapterOrder(if (descend) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC) + fun setSortOrder(sort: Int, descend: Boolean) { + manga.setChapterOrder(sort, if (descend) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC) + if (mangaSortMatchesDefault()) { + manga.setSortToGlobal() + } 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) manga.setSortToGlobal() asyncUpdateMangaAndChapters() } - /** - * Sets the sorting method and requests an UI update. - */ - fun setSortMethod(bySource: Boolean) { - manga.sorting = if (bySource) Manga.CHAPTER_SORTING_SOURCE else Manga.CHAPTER_SORTING_NUMBER + fun resetSortingToDefault() { + manga.setSortToGlobal() asyncUpdateMangaAndChapters() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt index 909ed17760..acb6ec918c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt @@ -4,15 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import androidx.core.view.isInvisible -import androidx.core.view.isVisible import com.google.android.material.bottomsheet.BottomSheetBehavior -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.ChapterSortBottomSheetBinding import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.view.setBottomEdge import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog +import eu.kanade.tachiyomi.widget.SortTextView import kotlin.math.max class ChaptersSortBottomSheet(controller: MangaDetailsController) : @@ -58,12 +57,10 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : super.onCreate(savedInstanceState) initGeneralPreferences() setBottomEdge(binding.hideTitles, activity) - binding.closeButton.setOnClickListener { dismiss() } binding.settingsScrollView.viewTreeObserver.addOnGlobalLayoutListener { val isScrollable = binding.settingsScrollView.height < binding.sortLayout.height + binding.settingsScrollView.paddingTop + binding.settingsScrollView.paddingBottom - binding.closeButton.isVisible = isScrollable // making the view gone somehow breaks the layout so lets make it invisible binding.pill.isInvisible = isScrollable } @@ -80,43 +77,89 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : private fun initGeneralPreferences() { binding.chapterFilterLayout.root.setCheckboxes(presenter.manga) - var defPref = presenter.globalSort() - binding.sortGroup.check( - if (presenter.manga.sortDescending(defPref)) R.id.sort_newest else { - R.id.sort_oldest - } - ) + binding.byChapterNumber.state = SortTextView.State.NONE + binding.byUploadDate.state = SortTextView.State.NONE + binding.bySource.state = SortTextView.State.NONE + + 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.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 { - val desc = binding.sortGroup.checkedRadioButtonId == R.id.sort_newest - presenter.setGlobalChapterSort(desc) - defPref = desc + presenter.setGlobalChapterSort( + presenter.manga.sorting, + presenter.manga.sortDescending() + ) binding.setAsDefaultSort.isInvisible = true + binding.resetAsDefaultSort.isInvisible = true } - binding.sortMethodGroup.setOnCheckedChangeListener { _, checkedId -> - presenter.setSortMethod(checkedId == R.id.sort_by_source) + binding.resetAsDefaultSort.setOnClickListener { + 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 -> 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() + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt index b07c803f23..e170288e5b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.reader 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. @@ -35,3 +36,12 @@ class ChapterLoadByNumber { return chapters.sortedBy { it.chapter_number } } } + +/** + * Load strategy using the source order. This is the default ordering. + */ +class ChapterLoadByDate { + fun get(allChapters: List): List { + return allChapters.sortedBy { it.date_upload } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 70817dcf8f..116f22ab0f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications 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.TrackManager 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.ReadingModeType 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.storage.DiskUtil import eu.kanade.tachiyomi.util.system.ImageUtil @@ -118,9 +118,10 @@ class ReaderPresenter( val chaptersForReader = chapterFilter.filterChaptersForReader(dbChapters, manga, selectedChapter) - when (manga.sorting) { + when (manga.chapterOrder(preferences.sortChapterOrder().get())) { Manga.CHAPTER_SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader) Manga.CHAPTER_SORTING_NUMBER -> ChapterLoadByNumber().get(chaptersForReader, selectedChapter) + Manga.CHAPTER_SORTING_UPLOAD_DATE -> ChapterLoadByDate().get(chaptersForReader) else -> error("Unknown sorting method") }.map(::ReaderChapter) } @@ -212,25 +213,14 @@ class ReaderPresenter( suspend fun getChapters(): List { val manga = manga ?: return emptyList() chapterItems = withContext(Dispatchers.IO) { + val chapterSort = ChapterSort(manga, chapterFilter, preferences) val dbChapters = db.getChapters(manga).executeAsBlocking() - val list = - chapterFilter.filterChaptersForReader(dbChapters, manga, getCurrentChapter()?.chapter) - .sortedBy { - when (manga.sorting) { - Manga.CHAPTER_SORTING_NUMBER -> it.chapter_number - 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 + chapterSort.getChaptersSorted(dbChapters, filterForReader = true, currentChapter = getCurrentChapter()?.chapter).map { + ReaderChapterItem( + it, + manga, + it.id == getCurrentChapter()?.chapter?.id ?: chapterId + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index 2d383c20f5..c3639967ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -16,6 +16,8 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.SourceManager 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.launchIO import eu.kanade.tachiyomi.util.system.launchUI @@ -39,7 +41,8 @@ class RecentsPresenter( val controller: RecentsController?, val preferences: PreferencesHelper = 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 { private var recentsJob: Job? = null @@ -319,12 +322,12 @@ class RecentsPresenter( private fun getNextChapter(manga: Manga): Chapter? { 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? { 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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt index db686fe549..0bbf77039e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt @@ -38,7 +38,7 @@ class ChapterFilter(val preferences: PreferencesHelper = Injekt.get(), val downl } // filter chapters for the reader - fun filterChaptersForReader(chapters: List, manga: Manga, selectedChapter: Chapter? = null): List { + fun filterChaptersForReader(chapters: List, manga: Manga, selectedChapter: T? = null): List { // if neither preference is enabled don't even filter if (!preferences.skipRead() && !preferences.skipFiltered()) { return chapters diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSort.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSort.kt new file mode 100644 index 0000000000..f41013718d --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSort.kt @@ -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 getChaptersSorted( + rawChapters: List, + andFiltered: Boolean = true, + filterForReader: Boolean = false, + currentChapter: T? = null + ): List { + 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 getNextUnreadChapter(rawChapters: List, andFiltered: Boolean = true,): T? { + val chapters = when { + andFiltered -> chapterFilter.filterChapters(rawChapters, manga) + else -> rawChapters + } + return chapters.sortedWith(sortComparator(true)).find { !it.read } + } + + fun sortComparator(ignoreAsc: Boolean = false): Comparator { + 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) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SortTextView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SortTextView.kt new file mode 100644 index 0000000000..e23c153505 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SortTextView.kt @@ -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) + } +} diff --git a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml index b64172932d..9b68bc36c0 100644 --- a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml +++ b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml @@ -16,20 +16,24 @@ style="@style/BottomSheetDialogTheme" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@android:color/transparent" android:orientation="vertical" android:paddingTop="12dp" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> - + android:layout_height="wrap_content"> @@ -38,33 +42,44 @@ android:id="@+id/set_as_default_sort" style="@style/Theme.Widget.Button.TextButton" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/set_as_default_for_all" /> - + android:layout_height="38sp" + android:padding="4dp" + 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" /> - - - + android:layout_height="38sp" + android:padding="4dp" + 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" /> + - - + + + + + - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/sort_text_view.xml b/app/src/main/res/layout/sort_text_view.xml new file mode 100644 index 0000000000..9acd4f34da --- /dev/null +++ b/app/src/main/res/layout/sort_text_view.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index eca1430ae3..c4b6c709aa 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -31,6 +31,12 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 62320fb848..0dd8c711f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,9 @@ No pages found Remove all downloads? No chapters to delete + By source\'s order + By chapter number + By upload date Remove %1$d downloaded chapter? @@ -508,6 +511,7 @@ Error saving cover Error sharing cover Custom manga info + Set as default A chapter has been removed from the source:\n%2$s\nDelete its download?