浏览代码

Sort per category (#5408)

* Add flags for sorting per category

* Add logic to LibrarySettingsSheet

* Add  logic to LibraryPresenter

* Minor tweaks

* Use enum instead of variables

Also deprecates LibrarySort in favour of the new enum classes

* Remove debug log and suppress deprecation

* Convert DisplayMode setting to enum

Also fix bug were adapter would get de-synced with the current per category setting

* Fix migration crashing app due to values being access before migration
Andreas 3 年之前
父节点
当前提交
60890147c3

+ 1 - 1
app/build.gradle.kts

@@ -30,7 +30,7 @@ android {
         minSdkVersion(AndroidConfig.minSdk)
         targetSdkVersion(AndroidConfig.targetSdk)
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        versionCode = 63
+        versionCode = 64
         versionName = "0.11.1"
 
         buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

+ 44 - 2
app/src/main/java/eu/kanade/tachiyomi/Migrations.kt

@@ -12,6 +12,8 @@ import eu.kanade.tachiyomi.data.updater.UpdaterJob
 import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
 import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
 import eu.kanade.tachiyomi.ui.library.LibrarySort
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
 import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
@@ -96,9 +98,15 @@ object Migrations {
             }
             if (oldVersion < 44) {
                 // Reset sorting preference if using removed sort by source
+                val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+
+                val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
+
                 @Suppress("DEPRECATION")
-                if (preferences.librarySortingMode().get() == LibrarySort.SOURCE) {
-                    preferences.librarySortingMode().set(LibrarySort.ALPHA)
+                if (oldSortingMode == LibrarySort.SOURCE) {
+                    prefs.edit {
+                        putInt(PreferenceKeys.librarySortingMode, LibrarySort.ALPHA)
+                    }
                 }
             }
             if (oldVersion < 52) {
@@ -190,6 +198,40 @@ object Migrations {
                     LibraryUpdateJob.setupTask(context, 3)
                 }
             }
+            if (oldVersion < 64) {
+                val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+
+                val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
+                val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
+
+                @Suppress("DEPRECATION")
+                val newSortingMode = when (oldSortingMode) {
+                    LibrarySort.ALPHA -> SortModeSetting.ALPHABETICAL
+                    LibrarySort.LAST_READ -> SortModeSetting.LAST_READ
+                    LibrarySort.LAST_CHECKED -> SortModeSetting.LAST_CHECKED
+                    LibrarySort.UNREAD -> SortModeSetting.UNREAD
+                    LibrarySort.TOTAL -> SortModeSetting.TOTAL_CHAPTERS
+                    LibrarySort.LATEST_CHAPTER -> SortModeSetting.LATEST_CHAPTER
+                    LibrarySort.CHAPTER_FETCH_DATE -> SortModeSetting.DATE_FETCHED
+                    LibrarySort.DATE_ADDED -> SortModeSetting.DATE_ADDED
+                    else -> SortModeSetting.ALPHABETICAL
+                }
+
+                val newSortingDirection = when (oldSortingDirection) {
+                    true -> SortDirectionSetting.ASCENDING
+                    else -> SortDirectionSetting.DESCENDING
+                }
+
+                prefs.edit(commit = true) {
+                    remove(PreferenceKeys.librarySortingMode)
+                    remove(PreferenceKeys.librarySortingDirection)
+                }
+
+                prefs.edit {
+                    putString(PreferenceKeys.librarySortingMode, newSortingMode.name)
+                    putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name)
+                }
+            }
             return true
         }
 

+ 13 - 7
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt

@@ -1,5 +1,8 @@
 package eu.kanade.tachiyomi.data.database.models
 
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
 import java.io.Serializable
 
 interface Category : Serializable {
@@ -17,15 +20,18 @@ interface Category : Serializable {
     }
 
     var displayMode: Int
-        get() = flags and MASK
-        set(mode) = setFlags(mode, MASK)
+        get() = flags and DisplayModeSetting.MASK
+        set(mode) = setFlags(mode, DisplayModeSetting.MASK)
 
-    companion object {
+    var sortMode: Int
+        get() = flags and SortModeSetting.MASK
+        set(mode) = setFlags(mode, SortModeSetting.MASK)
+
+    var sortDirection: Int
+        get() = flags and SortDirectionSetting.MASK
+        set(mode) = setFlags(mode, SortDirectionSetting.MASK)
 
-        const val COMPACT_GRID = 0b00000000
-        const val COMFORTABLE_GRID = 0b00000001
-        const val LIST = 0b00000010
-        const val MASK = 0b00000011
+    companion object {
 
         fun create(name: String): Category = CategoryImpl().apply {
             this.name = name

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

@@ -147,6 +147,7 @@ object PreferenceKeys {
     const val filterTracked = "pref_filter_library_tracked"
 
     const val librarySortingMode = "library_sorting_mode"
+    const val librarySortingDirection = "library_sorting_ascending"
 
     const val automaticExtUpdates = "automatic_ext_updates"
 

+ 0 - 6
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt

@@ -37,12 +37,6 @@ object PreferenceValues {
 
     /* ktlint-enable experimental:enum-entry-name-case */
 
-    enum class DisplayMode {
-        COMPACT_GRID,
-        COMFORTABLE_GRID,
-        LIST,
-    }
-
     enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) {
         NONE,
         HORIZONTAL(shouldInvertHorizontal = true),

+ 7 - 6
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -10,10 +10,12 @@ import com.tfcporciuncula.flow.FlowSharedPreferences
 import com.tfcporciuncula.flow.Preference
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.ThemeMode.*
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.data.track.anilist.Anilist
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
 import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
@@ -184,7 +186,7 @@ class PreferencesHelper(val context: Context) {
 
     fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
 
-    fun sourceDisplayMode() = flowPrefs.getEnum(Keys.sourceDisplayMode, DisplayMode.COMPACT_GRID)
+    fun sourceDisplayMode() = flowPrefs.getEnum(Keys.sourceDisplayMode, DisplayModeSetting.COMPACT_GRID)
 
     fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
 
@@ -235,7 +237,7 @@ class PreferencesHelper(val context: Context) {
 
     fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
 
-    fun libraryDisplayMode() = flowPrefs.getEnum(Keys.libraryDisplayMode, DisplayMode.COMPACT_GRID)
+    fun libraryDisplayMode() = flowPrefs.getEnum(Keys.libraryDisplayMode, DisplayModeSetting.COMPACT_GRID)
 
     fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)
 
@@ -257,9 +259,8 @@ class PreferencesHelper(val context: Context) {
 
     fun filterTracking(name: Int) = flowPrefs.getInt("${Keys.filterTracked}_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
 
-    fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
-
-    fun librarySortingAscending() = flowPrefs.getBoolean("library_sorting_ascending", true)
+    fun librarySortingMode() = flowPrefs.getEnum(Keys.librarySortingMode, SortModeSetting.ALPHABETICAL)
+    fun librarySortingAscending() = flowPrefs.getEnum(Keys.librarySortingDirection, SortDirectionSetting.ASCENDING)
 
     fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
 

+ 9 - 9
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt

@@ -24,7 +24,6 @@ import eu.davidea.flexibleadapter.items.IFlexible
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.preference.asImmediateFlow
 import eu.kanade.tachiyomi.databinding.SourceControllerBinding
@@ -37,6 +36,7 @@ import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
 import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
 import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.more.MoreController
@@ -205,7 +205,7 @@ open class BrowseSourceController(bundle: Bundle) :
             binding.catalogueView.removeView(oldRecycler)
         }
 
-        val recycler = if (preferences.sourceDisplayMode().get() == DisplayMode.LIST) {
+        val recycler = if (preferences.sourceDisplayMode().get() == DisplayModeSetting.LIST) {
             RecyclerView(view.context).apply {
                 id = R.id.recycler
                 layoutManager = LinearLayoutManager(context)
@@ -273,9 +273,9 @@ open class BrowseSourceController(bundle: Bundle) :
         )
 
         val displayItem = when (preferences.sourceDisplayMode().get()) {
-            DisplayMode.COMPACT_GRID -> R.id.action_compact_grid
-            DisplayMode.COMFORTABLE_GRID -> R.id.action_comfortable_grid
-            DisplayMode.LIST -> R.id.action_list
+            DisplayModeSetting.COMPACT_GRID -> R.id.action_compact_grid
+            DisplayModeSetting.COMFORTABLE_GRID -> R.id.action_comfortable_grid
+            DisplayModeSetting.LIST -> R.id.action_list
         }
         menu.findItem(displayItem).isChecked = true
     }
@@ -297,9 +297,9 @@ open class BrowseSourceController(bundle: Bundle) :
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         when (item.itemId) {
             R.id.action_search -> expandActionViewFromInteraction = true
-            R.id.action_compact_grid -> setDisplayMode(DisplayMode.COMPACT_GRID)
-            R.id.action_comfortable_grid -> setDisplayMode(DisplayMode.COMFORTABLE_GRID)
-            R.id.action_list -> setDisplayMode(DisplayMode.LIST)
+            R.id.action_compact_grid -> setDisplayMode(DisplayModeSetting.COMPACT_GRID)
+            R.id.action_comfortable_grid -> setDisplayMode(DisplayModeSetting.COMFORTABLE_GRID)
+            R.id.action_list -> setDisplayMode(DisplayModeSetting.LIST)
             R.id.action_open_in_web_view -> openInWebView()
             R.id.action_local_source_help -> openLocalSourceHelpGuide()
         }
@@ -446,7 +446,7 @@ open class BrowseSourceController(bundle: Bundle) :
      *
      * @param mode the mode to change to
      */
-    private fun setDisplayMode(mode: DisplayMode) {
+    private fun setDisplayMode(mode: DisplayModeSetting) {
         val view = view ?: return
         val adapter = adapter ?: return
 

+ 8 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceItem.kt

@@ -12,19 +12,19 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 import eu.davidea.flexibleadapter.items.IFlexible
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
 import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
 import eu.kanade.tachiyomi.widget.AutofitRecyclerView
 
-class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMode>) :
+class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayModeSetting>) :
     AbstractFlexibleItem<SourceHolder<*>>() {
 
     override fun getLayoutRes(): Int {
         return when (displayMode.get()) {
-            DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
-            DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
-            DisplayMode.LIST -> R.layout.source_list_item
+            DisplayModeSetting.COMPACT_GRID -> R.layout.source_compact_grid_item
+            DisplayModeSetting.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
+            DisplayModeSetting.LIST -> R.layout.source_list_item
         }
     }
 
@@ -33,7 +33,7 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
         adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
     ): SourceHolder<*> {
         return when (displayMode.get()) {
-            DisplayMode.COMPACT_GRID -> {
+            DisplayModeSetting.COMPACT_GRID -> {
                 val binding = SourceCompactGridItemBinding.bind(view)
                 val parent = adapter.recyclerView as AutofitRecyclerView
                 val coverHeight = parent.itemWidth / 3 * 4
@@ -50,7 +50,7 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
                 }
                 SourceGridHolder(view, adapter)
             }
-            DisplayMode.COMFORTABLE_GRID -> {
+            DisplayModeSetting.COMFORTABLE_GRID -> {
                 val binding = SourceComfortableGridItemBinding.bind(view)
                 val parent = adapter.recyclerView as AutofitRecyclerView
                 val coverHeight = parent.itemWidth / 3 * 4
@@ -62,7 +62,7 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
                 }
                 SourceComfortableGridHolder(view, adapter)
             }
-            DisplayMode.LIST -> {
+            DisplayModeSetting.LIST -> {
                 SourceListHolder(view, adapter)
             }
         }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt

@@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.library.LibraryUpdateService
-import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding
 import eu.kanade.tachiyomi.util.lang.plusAssign
@@ -28,6 +27,7 @@ import reactivecircus.flowbinding.swiperefreshlayout.refreshes
 import rx.subscriptions.CompositeSubscription
 import uy.kohesive.injekt.injectLazy
 import java.util.ArrayDeque
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting as DisplayMode
 
 /**
  * Fragment containing the library manga for a certain category.
@@ -125,7 +125,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
 
         // If displayMode should be set from category adjust manga count per row
         if (preferences.categorisedDisplaySettings().get()) {
-            recycler.spanCount = if (category.displayMode == Category.LIST || (preferences.libraryDisplayMode().get() == DisplayMode.LIST && category.id == 0)) {
+            recycler.spanCount = if (DisplayMode.fromFlag(category.displayMode) == DisplayMode.LIST || (preferences.libraryDisplayMode().get() == DisplayMode.LIST && category.id == 0)) {
                 1
             } else {
                 controller.mangaPerRow

+ 5 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt

@@ -295,6 +295,11 @@ class LibraryController(
             .map { (it.id ?: -1) to (mangaMap[it.id]?.size ?: 0) }
             .toMap()
 
+        if (preferences.categorisedDisplaySettings().get()) {
+            // Reattach adapter so it doesn't get de-synced
+            reattachAdapter()
+        }
+
         // Restore active category.
         binding.libraryPager.setCurrentItem(activeCat, false)
 

+ 10 - 14
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt

@@ -13,10 +13,10 @@ import eu.davidea.flexibleadapter.items.IFilterable
 import eu.davidea.flexibleadapter.items.IFlexible
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.LibraryManga
-import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
 import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding
 import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
 import eu.kanade.tachiyomi.widget.AutofitRecyclerView
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -24,7 +24,7 @@ import uy.kohesive.injekt.api.get
 class LibraryItem(
     val manga: LibraryManga,
     private val shouldSetFromCategory: Preference<Boolean>,
-    private val defaultLibraryDisplayMode: Preference<DisplayMode>
+    private val defaultLibraryDisplayMode: Preference<DisplayModeSetting>
 ) :
     AbstractFlexibleItem<LibraryHolder<*>>(), IFilterable<String> {
 
@@ -35,13 +35,9 @@ class LibraryItem(
     var unreadCount = -1
     var isLocal = false
 
-    private fun getDisplayMode(): DisplayMode {
+    private fun getDisplayMode(): DisplayModeSetting {
         return if (shouldSetFromCategory.get() && manga.category != 0) {
-            if (displayMode != -1) {
-                DisplayMode.values()[displayMode]
-            } else {
-                DisplayMode.COMPACT_GRID
-            }
+            DisplayModeSetting.fromFlag(displayMode)
         } else {
             defaultLibraryDisplayMode.get()
         }
@@ -49,15 +45,15 @@ class LibraryItem(
 
     override fun getLayoutRes(): Int {
         return when (getDisplayMode()) {
-            DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
-            DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
-            DisplayMode.LIST -> R.layout.source_list_item
+            DisplayModeSetting.COMPACT_GRID -> R.layout.source_compact_grid_item
+            DisplayModeSetting.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
+            DisplayModeSetting.LIST -> R.layout.source_list_item
         }
     }
 
     override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder<*> {
         return when (getDisplayMode()) {
-            DisplayMode.COMPACT_GRID -> {
+            DisplayModeSetting.COMPACT_GRID -> {
                 val binding = SourceCompactGridItemBinding.bind(view)
                 val parent = adapter.recyclerView as AutofitRecyclerView
                 val coverHeight = parent.itemWidth / 3 * 4
@@ -71,7 +67,7 @@ class LibraryItem(
                 }
                 LibraryCompactGridHolder(view, adapter)
             }
-            DisplayMode.COMFORTABLE_GRID -> {
+            DisplayModeSetting.COMFORTABLE_GRID -> {
                 val binding = SourceComfortableGridItemBinding.bind(view)
                 val parent = adapter.recyclerView as AutofitRecyclerView
                 val coverHeight = parent.itemWidth / 3 * 4
@@ -83,7 +79,7 @@ class LibraryItem(
                 }
                 LibraryComfortableGridHolder(view, adapter)
             }
-            DisplayMode.LIST -> {
+            DisplayModeSetting.LIST -> {
                 LibraryListHolder(view, adapter)
             }
         }

+ 32 - 20
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt

@@ -15,6 +15,8 @@ import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.model.SManga
 import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
 import eu.kanade.tachiyomi.util.isLocal
 import eu.kanade.tachiyomi.util.lang.combineLatest
 import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
@@ -98,7 +100,7 @@ class LibraryPresenter(
                     lib.copy(mangaMap = applyFilters(lib.mangaMap, tracks))
                 }
                 .combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
-                    lib.copy(mangaMap = applySort(lib.mangaMap))
+                    lib.copy(mangaMap = applySort(lib.categories, lib.mangaMap))
                 }
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribeLatestCache({ view, (categories, mangaMap) ->
@@ -228,9 +230,7 @@ class LibraryPresenter(
      *
      * @param map the map to sort.
      */
-    private fun applySort(map: LibraryMap): LibraryMap {
-        val sortingMode = preferences.librarySortingMode().get()
-
+    private fun applySort(categories: List<Category>, map: LibraryMap): LibraryMap {
         val lastReadManga by lazy {
             var counter = 0
             db.getLastReadManga().executeAsBlocking().associate { it.id!! to counter++ }
@@ -248,55 +248,67 @@ class LibraryPresenter(
             db.getChapterFetchDateManga().executeAsBlocking().associate { it.id!! to counter++ }
         }
 
-        val sortAscending = preferences.librarySortingAscending().get()
+        val sortingModes = categories.associate { category ->
+            (category.id ?: 0) to SortModeSetting.get(preferences, category)
+        }
+
+        val sortAscending = categories.associate { category ->
+            (category.id ?: 0) to SortDirectionSetting.get(preferences, category)
+        }
+
         val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
+            val sortingMode = sortingModes[i1.manga.category]!!
+            val sortAscending = sortAscending[i1.manga.category]!! == SortDirectionSetting.ASCENDING
             when (sortingMode) {
-                LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title, true)
-                LibrarySort.LAST_READ -> {
+                SortModeSetting.ALPHABETICAL -> i1.manga.title.compareTo(i2.manga.title, true)
+                SortModeSetting.LAST_READ -> {
                     // Get index of manga, set equal to list if size unknown.
                     val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
                     val manga2LastRead = lastReadManga[i2.manga.id!!] ?: lastReadManga.size
                     manga1LastRead.compareTo(manga2LastRead)
                 }
-                LibrarySort.LAST_CHECKED -> i2.manga.last_update.compareTo(i1.manga.last_update)
-                LibrarySort.UNREAD -> when {
+                SortModeSetting.LAST_CHECKED -> i2.manga.last_update.compareTo(i1.manga.last_update)
+                SortModeSetting.UNREAD -> when {
                     // Ensure unread content comes first
                     i1.manga.unread == i2.manga.unread -> 0
                     i1.manga.unread == 0 -> if (sortAscending) 1 else -1
                     i2.manga.unread == 0 -> if (sortAscending) -1 else 1
                     else -> i1.manga.unread.compareTo(i2.manga.unread)
                 }
-                LibrarySort.TOTAL -> {
+                SortModeSetting.TOTAL_CHAPTERS -> {
                     val manga1TotalChapter = totalChapterManga[i1.manga.id!!] ?: 0
                     val mange2TotalChapter = totalChapterManga[i2.manga.id!!] ?: 0
                     manga1TotalChapter.compareTo(mange2TotalChapter)
                 }
-                LibrarySort.LATEST_CHAPTER -> {
+                SortModeSetting.LATEST_CHAPTER -> {
                     val manga1latestChapter = latestChapterManga[i1.manga.id!!]
                         ?: latestChapterManga.size
                     val manga2latestChapter = latestChapterManga[i2.manga.id!!]
                         ?: latestChapterManga.size
                     manga1latestChapter.compareTo(manga2latestChapter)
                 }
-                LibrarySort.CHAPTER_FETCH_DATE -> {
+                SortModeSetting.DATE_FETCHED -> {
                     val manga1chapterFetchDate = chapterFetchDateManga[i1.manga.id!!]
                         ?: chapterFetchDateManga.size
                     val manga2chapterFetchDate = chapterFetchDateManga[i2.manga.id!!]
                         ?: chapterFetchDateManga.size
                     manga1chapterFetchDate.compareTo(manga2chapterFetchDate)
                 }
-                LibrarySort.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added)
-                else -> throw Exception("Unknown sorting mode")
+                SortModeSetting.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added)
             }
         }
 
-        val comparator = if (sortAscending) {
-            Comparator(sortFn)
-        } else {
-            Collections.reverseOrder(sortFn)
-        }
+        return map.mapValues { entry ->
+            val sortAscending = sortAscending[entry.key]!! == SortDirectionSetting.ASCENDING
 
-        return map.mapValues { entry -> entry.value.sortedWith(comparator) }
+            val comparator = if (sortAscending) {
+                Comparator(sortFn)
+            } else {
+                Collections.reverseOrder(sortFn)
+            }
+
+            entry.value.sortedWith(comparator)
+        }
     }
 
     /**

+ 80 - 48
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt

@@ -7,10 +7,12 @@ import com.bluelinelabs.conductor.Router
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.database.models.Category
-import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.TrackService
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
 import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
@@ -45,6 +47,8 @@ class LibrarySettingsSheet(
      * @param currentCategory ID of currently shown category
      */
     fun show(currentCategory: Category) {
+        sort.currentCategory = currentCategory
+        sort.adjustDisplaySelection()
         display.currentCategory = currentCategory
         display.adjustDisplaySelection()
         super.show()
@@ -158,8 +162,16 @@ class LibrarySettingsSheet(
     inner class Sort @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
         Settings(context, attrs) {
 
+        private val sort = SortGroup()
+
         init {
-            setGroups(listOf(SortGroup()))
+            setGroups(listOf(sort))
+        }
+
+        // Refreshes Display Setting selections
+        fun adjustDisplaySelection() {
+            sort.initModels()
+            sort.items.forEach { adapter.notifyItemChanged(it) }
         }
 
         inner class SortGroup : Group {
@@ -179,29 +191,29 @@ class LibrarySettingsSheet(
             override val footer = null
 
             override fun initModels() {
-                val sorting = preferences.librarySortingMode().get()
-                val order = if (preferences.librarySortingAscending().get()) {
+                val sorting = SortModeSetting.get(preferences, currentCategory)
+                val order = if (SortDirectionSetting.get(preferences, currentCategory) == SortDirectionSetting.ASCENDING) {
                     Item.MultiSort.SORT_ASC
                 } else {
                     Item.MultiSort.SORT_DESC
                 }
 
                 alphabetically.state =
-                    if (sorting == LibrarySort.ALPHA) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.ALPHABETICAL) order else Item.MultiSort.SORT_NONE
                 lastRead.state =
-                    if (sorting == LibrarySort.LAST_READ) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.LAST_READ) order else Item.MultiSort.SORT_NONE
                 lastChecked.state =
-                    if (sorting == LibrarySort.LAST_CHECKED) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.LAST_CHECKED) order else Item.MultiSort.SORT_NONE
                 unread.state =
-                    if (sorting == LibrarySort.UNREAD) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.UNREAD) order else Item.MultiSort.SORT_NONE
                 total.state =
-                    if (sorting == LibrarySort.TOTAL) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.TOTAL_CHAPTERS) order else Item.MultiSort.SORT_NONE
                 latestChapter.state =
-                    if (sorting == LibrarySort.LATEST_CHAPTER) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.LATEST_CHAPTER) order else Item.MultiSort.SORT_NONE
                 chapterFetchDate.state =
-                    if (sorting == LibrarySort.CHAPTER_FETCH_DATE) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.DATE_FETCHED) order else Item.MultiSort.SORT_NONE
                 dateAdded.state =
-                    if (sorting == LibrarySort.DATE_ADDED) order else Item.MultiSort.SORT_NONE
+                    if (sorting == SortModeSetting.DATE_ADDED) order else Item.MultiSort.SORT_NONE
             }
 
             override fun onItemClicked(item: Item) {
@@ -219,23 +231,50 @@ class LibrarySettingsSheet(
                     else -> throw Exception("Unknown state")
                 }
 
-                preferences.librarySortingMode().set(
-                    when (item) {
-                        alphabetically -> LibrarySort.ALPHA
-                        lastRead -> LibrarySort.LAST_READ
-                        lastChecked -> LibrarySort.LAST_CHECKED
-                        unread -> LibrarySort.UNREAD
-                        total -> LibrarySort.TOTAL
-                        latestChapter -> LibrarySort.LATEST_CHAPTER
-                        chapterFetchDate -> LibrarySort.CHAPTER_FETCH_DATE
-                        dateAdded -> LibrarySort.DATE_ADDED
-                        else -> throw Exception("Unknown sorting")
-                    }
-                )
-                preferences.librarySortingAscending().set(item.state == Item.MultiSort.SORT_ASC)
+                setSortModePreference(item)
+
+                setSortDirectionPrefernece(item)
 
                 item.group.items.forEach { adapter.notifyItemChanged(it) }
             }
+
+            private fun setSortDirectionPrefernece(item: Item.MultiStateGroup) {
+                val flag = if (item.state == Item.MultiSort.SORT_ASC) {
+                    SortDirectionSetting.ASCENDING
+                } else {
+                    SortDirectionSetting.DESCENDING
+                }
+
+                if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
+                    currentCategory?.sortDirection = flag.flag
+
+                    db.insertCategory(currentCategory!!).executeAsBlocking()
+                } else {
+                    preferences.librarySortingAscending().set(flag)
+                }
+            }
+
+            private fun setSortModePreference(item: Item) {
+                val flag = when (item) {
+                    alphabetically -> SortModeSetting.ALPHABETICAL
+                    lastRead -> SortModeSetting.LAST_READ
+                    lastChecked -> SortModeSetting.LAST_CHECKED
+                    unread -> SortModeSetting.UNREAD
+                    total -> SortModeSetting.TOTAL_CHAPTERS
+                    latestChapter -> SortModeSetting.LATEST_CHAPTER
+                    chapterFetchDate -> SortModeSetting.DATE_FETCHED
+                    dateAdded -> SortModeSetting.DATE_ADDED
+                    else -> throw NotImplementedError("Unknown display mode")
+                }
+
+                if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
+                    currentCategory?.sortMode = flag.flag
+
+                    db.insertCategory(currentCategory!!).executeAsBlocking()
+                } else {
+                    preferences.librarySortingMode().set(flag)
+                }
+            }
         }
     }
 
@@ -264,9 +303,9 @@ class LibrarySettingsSheet(
         }
 
         // Gets user preference of currently selected display mode at current category
-        private fun getDisplayModePreference(): DisplayMode {
+        private fun getDisplayModePreference(): DisplayModeSetting {
             return if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
-                DisplayMode.values()[currentCategory?.displayMode ?: 0]
+                DisplayModeSetting.fromFlag(currentCategory?.displayMode)
             } else {
                 preferences.libraryDisplayMode().get()
             }
@@ -300,33 +339,26 @@ class LibrarySettingsSheet(
             }
 
             // Sets display group selections based on given mode
-            fun setGroupSelections(mode: DisplayMode) {
-                compactGrid.checked = mode == DisplayMode.COMPACT_GRID
-                comfortableGrid.checked = mode == DisplayMode.COMFORTABLE_GRID
-                list.checked = mode == DisplayMode.LIST
+            fun setGroupSelections(mode: DisplayModeSetting) {
+                compactGrid.checked = mode == DisplayModeSetting.COMPACT_GRID
+                comfortableGrid.checked = mode == DisplayModeSetting.COMFORTABLE_GRID
+                list.checked = mode == DisplayModeSetting.LIST
             }
 
             private fun setDisplayModePreference(item: Item) {
-                if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
-                    val flag = when (item) {
-                        compactGrid -> Category.COMPACT_GRID
-                        comfortableGrid -> Category.COMFORTABLE_GRID
-                        list -> Category.LIST
-                        else -> throw NotImplementedError("Unknown display mode")
-                    }
+                val flag = when (item) {
+                    compactGrid -> DisplayModeSetting.COMPACT_GRID
+                    comfortableGrid -> DisplayModeSetting.COMFORTABLE_GRID
+                    list -> DisplayModeSetting.LIST
+                    else -> throw NotImplementedError("Unknown display mode")
+                }
 
-                    currentCategory?.displayMode = flag
+                if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
+                    currentCategory?.displayMode = flag.flag
 
                     db.insertCategory(currentCategory!!).executeAsBlocking()
                 } else {
-                    preferences.libraryDisplayMode().set(
-                        when (item) {
-                            compactGrid -> DisplayMode.COMPACT_GRID
-                            comfortableGrid -> DisplayMode.COMFORTABLE_GRID
-                            list -> DisplayMode.LIST
-                            else -> throw NotImplementedError("Unknown display mode")
-                        }
-                    )
+                    preferences.libraryDisplayMode().set(flag)
                 }
             }
         }

+ 1 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt

@@ -1,5 +1,6 @@
 package eu.kanade.tachiyomi.ui.library
 
+@Deprecated("Deprecated in favor for SortModeSetting")
 object LibrarySort {
 
     const val ALPHA = 0

+ 16 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/DisplayModeSetting.kt

@@ -0,0 +1,16 @@
+package eu.kanade.tachiyomi.ui.library.setting
+
+enum class DisplayModeSetting(val flag: Int) {
+    COMPACT_GRID(0b00000000),
+    COMFORTABLE_GRID(0b00000001),
+    LIST(0b00000010);
+
+    companion object {
+        const val MASK = 0b00000011
+
+        fun fromFlag(flag: Int?): DisplayModeSetting {
+            return values()
+                .find { mode -> mode.flag == flag } ?: COMPACT_GRID
+        }
+    }
+}

+ 25 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/SortDirectionSetting.kt

@@ -0,0 +1,25 @@
+package eu.kanade.tachiyomi.ui.library.setting
+
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+
+enum class SortDirectionSetting(val flag: Int) {
+    ASCENDING(0b01000000),
+    DESCENDING(0b00000000);
+
+    companion object {
+        const val MASK = 0b01000000
+
+        fun fromFlag(flag: Int?): SortDirectionSetting {
+            return values().find { mode -> mode.flag == flag } ?: ASCENDING
+        }
+
+        fun get(preferences: PreferencesHelper, category: Category?): SortDirectionSetting {
+            return if (preferences.categorisedDisplaySettings().get() && category != null && category.id != 0) {
+                fromFlag(category.sortDirection)
+            } else {
+                preferences.librarySortingAscending().get()
+            }
+        }
+    }
+}

+ 32 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/SortModeSetting.kt

@@ -0,0 +1,32 @@
+package eu.kanade.tachiyomi.ui.library.setting
+
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+
+enum class SortModeSetting(val flag: Int) {
+    ALPHABETICAL(0b00000000),
+    LAST_READ(0b00000100),
+    LAST_CHECKED(0b00001000),
+    UNREAD(0b00001100),
+    TOTAL_CHAPTERS(0b00010000),
+    LATEST_CHAPTER(0b00010100),
+    DATE_FETCHED(0b00011000),
+    DATE_ADDED(0b00011100);
+
+    companion object {
+        // Mask supports for more sorting flags if necessary
+        const val MASK = 0b00111100
+
+        fun fromFlag(flag: Int?): SortModeSetting {
+            return values().find { mode -> mode.flag == flag } ?: ALPHABETICAL
+        }
+
+        fun get(preferences: PreferencesHelper, category: Category?): SortModeSetting {
+            return if (preferences.categorisedDisplaySettings().get() && category != null && category.id != 0) {
+                fromFlag(category.sortMode)
+            } else {
+                preferences.librarySortingMode().get()
+            }
+        }
+    }
+}

+ 3 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -84,6 +84,8 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        val didMigration = if (savedInstanceState == null) Migrations.upgrade(preferences) else false
+
         binding = MainActivityBinding.inflate(layoutInflater)
 
         // Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
@@ -224,7 +226,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
             preferences.incognitoMode().set(false)
 
             // Show changelog prompt on update
-            if (Migrations.upgrade(preferences) && !BuildConfig.DEBUG) {
+            if (didMigration && !BuildConfig.DEBUG) {
                 WhatsNewDialogController().showDialog(router)
             }
         }