Explorar o código

Move default category into database (#7676)

Andreas %!s(int64=2) %!d(string=hai) anos
pai
achega
914831d51f
Modificáronse 23 ficheiros con 269 adicións e 216 borrados
  1. 6 0
      app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt
  2. 6 0
      app/src/main/java/eu/kanade/domain/DomainModule.kt
  3. 1 1
      app/src/main/java/eu/kanade/domain/category/interactor/ReorderCategory.kt
  4. 25 0
      app/src/main/java/eu/kanade/domain/category/interactor/ResetCategoryFlags.kt
  5. 28 0
      app/src/main/java/eu/kanade/domain/category/interactor/SetDisplayModeForCategory.kt
  6. 49 0
      app/src/main/java/eu/kanade/domain/category/interactor/SetSortModeForCategory.kt
  7. 4 19
      app/src/main/java/eu/kanade/domain/category/model/Category.kt
  8. 2 0
      app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt
  9. 20 0
      app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt
  10. 2 1
      app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt
  11. 33 26
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
  12. 12 11
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt
  13. 0 44
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
  14. 0 24
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
  15. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt
  16. 2 7
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
  17. 7 14
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
  18. 15 45
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt
  19. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/SortModeSetting.kt
  20. 5 7
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
  21. 25 15
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt
  22. 15 0
      app/src/main/sqldelight/data/categories.sq
  23. 10 0
      app/src/main/sqldelight/migrations/19.sqm

+ 6 - 0
app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt

@@ -64,6 +64,12 @@ class CategoryRepositoryImpl(
         )
     }
 
+    override suspend fun updateAllFlags(flags: Long?) {
+        handler.await {
+            categoriesQueries.updateAllFlags(flags)
+        }
+    }
+
     override suspend fun delete(categoryId: Long) {
         handler.await {
             categoriesQueries.delete(

+ 6 - 0
app/src/main/java/eu/kanade/domain/DomainModule.kt

@@ -13,7 +13,10 @@ import eu.kanade.domain.category.interactor.DeleteCategory
 import eu.kanade.domain.category.interactor.GetCategories
 import eu.kanade.domain.category.interactor.RenameCategory
 import eu.kanade.domain.category.interactor.ReorderCategory
+import eu.kanade.domain.category.interactor.ResetCategoryFlags
+import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
 import eu.kanade.domain.category.interactor.SetMangaCategories
+import eu.kanade.domain.category.interactor.SetSortModeForCategory
 import eu.kanade.domain.category.interactor.UpdateCategory
 import eu.kanade.domain.category.repository.CategoryRepository
 import eu.kanade.domain.chapter.interactor.GetChapter
@@ -73,6 +76,9 @@ class DomainModule : InjektModule {
     override fun InjektRegistrar.registerInjectables() {
         addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
         addFactory { GetCategories(get()) }
+        addFactory { ResetCategoryFlags(get(), get()) }
+        addFactory { SetDisplayModeForCategory(get(), get()) }
+        addFactory { SetSortModeForCategory(get(), get()) }
         addFactory { CreateCategoryWithName(get()) }
         addFactory { RenameCategory(get()) }
         addFactory { ReorderCategory(get()) }

+ 1 - 1
app/src/main/java/eu/kanade/domain/category/interactor/ReorderCategory.kt

@@ -13,7 +13,7 @@ class ReorderCategory(
 ) {
 
     suspend fun await(categoryId: Long, newPosition: Int) = withContext(NonCancellable) await@{
-        val categories = categoryRepository.getAll()
+        val categories = categoryRepository.getAll().filterNot(Category::isSystemCategory)
 
         val currentIndex = categories.indexOfFirst { it.id == categoryId }
         if (currentIndex == newPosition) {

+ 25 - 0
app/src/main/java/eu/kanade/domain/category/interactor/ResetCategoryFlags.kt

@@ -0,0 +1,25 @@
+package eu.kanade.domain.category.interactor
+
+import eu.kanade.domain.category.repository.CategoryRepository
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
+
+class ResetCategoryFlags(
+    private val preferences: PreferencesHelper,
+    private val categoryRepository: CategoryRepository,
+) {
+
+    suspend fun await() {
+        val display = preferences.libraryDisplayMode().get()
+        val sort = preferences.librarySortingMode().get()
+        val sortDirection = preferences.librarySortingAscending().get()
+
+        var flags = 0L
+        flags = flags and DisplayModeSetting.MASK.inv() or (display.flag and DisplayModeSetting.MASK)
+        flags = flags and SortModeSetting.MASK.inv() or (sort.flag and SortModeSetting.MASK)
+        flags = flags and SortDirectionSetting.MASK.inv() or (sortDirection.flag and SortDirectionSetting.MASK)
+        categoryRepository.updateAllFlags(flags)
+    }
+}

+ 28 - 0
app/src/main/java/eu/kanade/domain/category/interactor/SetDisplayModeForCategory.kt

@@ -0,0 +1,28 @@
+package eu.kanade.domain.category.interactor
+
+import eu.kanade.domain.category.model.Category
+import eu.kanade.domain.category.model.CategoryUpdate
+import eu.kanade.domain.category.repository.CategoryRepository
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
+
+class SetDisplayModeForCategory(
+    private val preferences: PreferencesHelper,
+    private val categoryRepository: CategoryRepository,
+) {
+
+    suspend fun await(category: Category, displayModeSetting: DisplayModeSetting) {
+        val flags = category.flags and DisplayModeSetting.MASK.inv() or (displayModeSetting.flag and DisplayModeSetting.MASK)
+        if (preferences.categorizedDisplaySettings().get()) {
+            categoryRepository.updatePartial(
+                CategoryUpdate(
+                    id = category.id,
+                    flags = flags,
+                ),
+            )
+        } else {
+            preferences.libraryDisplayMode().set(displayModeSetting)
+            categoryRepository.updateAllFlags(flags)
+        }
+    }
+}

+ 49 - 0
app/src/main/java/eu/kanade/domain/category/interactor/SetSortModeForCategory.kt

@@ -0,0 +1,49 @@
+package eu.kanade.domain.category.interactor
+
+import eu.kanade.domain.category.model.Category
+import eu.kanade.domain.category.model.CategoryUpdate
+import eu.kanade.domain.category.repository.CategoryRepository
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
+import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
+
+class SetSortModeForCategory(
+    private val preferences: PreferencesHelper,
+    private val categoryRepository: CategoryRepository,
+) {
+
+    suspend fun await(category: Category, sortDirectionSetting: SortDirectionSetting) {
+        val sort = if (preferences.categorizedDisplaySettings().get()) {
+            SortModeSetting.fromFlag(category.flags)
+        } else {
+            preferences.librarySortingMode().get()
+        }
+        await(category, sort, sortDirectionSetting)
+    }
+
+    suspend fun await(category: Category, sortModeSetting: SortModeSetting) {
+        val direction = if (preferences.categorizedDisplaySettings().get()) {
+            SortDirectionSetting.fromFlag(category.flags)
+        } else {
+            preferences.librarySortingAscending().get()
+        }
+        await(category, sortModeSetting, direction)
+    }
+
+    suspend fun await(category: Category, sortModeSetting: SortModeSetting, sortDirectionSetting: SortDirectionSetting) {
+        var flags = category.flags and SortModeSetting.MASK.inv() or (sortModeSetting.flag and SortModeSetting.MASK)
+        flags = flags and SortDirectionSetting.MASK.inv() or (sortDirectionSetting.flag and SortDirectionSetting.MASK)
+        if (preferences.categorizedDisplaySettings().get()) {
+            categoryRepository.updatePartial(
+                CategoryUpdate(
+                    id = category.id,
+                    flags = flags,
+                ),
+            )
+        } else {
+            preferences.librarySortingMode().set(sortModeSetting)
+            preferences.librarySortingAscending().set(sortDirectionSetting)
+            categoryRepository.updateAllFlags(flags)
+        }
+    }
+}

+ 4 - 19
app/src/main/java/eu/kanade/domain/category/model/Category.kt

@@ -1,13 +1,9 @@
 package eu.kanade.domain.category.model
 
-import android.content.Context
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.models.CategoryImpl
 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
-import eu.kanade.tachiyomi.data.database.models.Category as DbCategory
 
 data class Category(
     val id: Long,
@@ -16,6 +12,8 @@ data class Category(
     val flags: Long,
 ) : Serializable {
 
+    val isSystemCategory: Boolean = id == UNCATEGORIZED_ID
+
     val displayMode: Long
         get() = flags and DisplayModeSetting.MASK
 
@@ -26,24 +24,11 @@ data class Category(
         get() = flags and SortDirectionSetting.MASK
 
     companion object {
-        val default = { context: Context ->
-            Category(
-                id = 0,
-                name = context.getString(R.string.label_default),
-                order = 0,
-                flags = 0,
-            )
-        }
+
+        const val UNCATEGORIZED_ID = 0L
     }
 }
 
 internal fun List<Category>.anyWithName(name: String): Boolean {
     return any { name.equals(it.name, ignoreCase = true) }
 }
-
-fun Category.toDbCategory(): DbCategory = CategoryImpl().also {
-    it.name = name
-    it.id = id.toInt()
-    it.order = order.toInt()
-    it.flags = flags.toInt()
-}

+ 2 - 0
app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt

@@ -20,5 +20,7 @@ interface CategoryRepository {
 
     suspend fun updatePartial(updates: List<CategoryUpdate>)
 
+    suspend fun updateAllFlags(flags: Long?)
+
     suspend fun delete(categoryId: Long)
 }

+ 20 - 0
app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt

@@ -0,0 +1,20 @@
+package eu.kanade.presentation.category
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import eu.kanade.domain.category.model.Category
+import eu.kanade.tachiyomi.R
+
+val Category.visualName: String
+    @Composable
+    get() = when (id) {
+        Category.UNCATEGORIZED_ID -> stringResource(id = R.string.label_default)
+        else -> name
+    }
+
+fun Category.visualName(context: Context): String =
+    when (id) {
+        Category.UNCATEGORIZED_ID -> context.getString(R.string.label_default)
+        else -> name
+    }

+ 2 - 1
app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt

@@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.google.accompanist.pager.PagerState
 import eu.kanade.domain.category.model.Category
+import eu.kanade.presentation.category.visualName
 import eu.kanade.presentation.components.DownloadedOnlyModeBanner
 import eu.kanade.presentation.components.IncognitoModeBanner
 import eu.kanade.presentation.components.Pill
@@ -67,7 +68,7 @@ fun LibraryTabs(
                             verticalAlignment = Alignment.CenterVertically,
                         ) {
                             Text(
-                                text = category.name,
+                                text = category.visualName,
                                 color = if (state.currentPage == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
                             )
                             if (count != null) {

+ 33 - 26
app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt

@@ -5,6 +5,8 @@ import android.net.Uri
 import com.hippo.unifile.UniFile
 import data.Manga_sync
 import data.Mangas
+import eu.kanade.data.category.categoryMapper
+import eu.kanade.domain.category.model.Category
 import eu.kanade.domain.history.model.HistoryUpdate
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
@@ -138,7 +140,9 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
     private suspend fun backupCategories(options: Int): List<BackupCategory> {
         // Check if user wants category information in backup
         return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
-            handler.awaitList { categoriesQueries.getCategories(backupCategoryMapper) }
+            handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
+                .filterNot(Category::isSystemCategory)
+                .map(backupCategoryMapper)
         } else {
             emptyList()
         }
@@ -224,34 +228,37 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
      */
     internal suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
         // Get categories from file and from db
-        val dbCategories = handler.awaitList { categoriesQueries.getCategories() }
-
-        // Iterate over them
-        backupCategories
-            .map { it.getCategoryImpl() }
-            .forEach { category ->
-                // Used to know if the category is already in the db
-                var found = false
-                for (dbCategory in dbCategories) {
-                    // If the category is already in the db, assign the id to the file's category
-                    // and do nothing
-                    if (category.name == dbCategory.name) {
-                        category.id = dbCategory.id.toInt()
-                        found = true
-                        break
-                    }
+        val dbCategories = handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
+
+        val categories = backupCategories.map {
+            var category = it.getCategory()
+            var found = false
+            for (dbCategory in dbCategories) {
+                // If the category is already in the db, assign the id to the file's category
+                // and do nothing
+                if (category.name == dbCategory.name) {
+                    category = category.copy(id = dbCategory.id)
+                    found = true
+                    break
                 }
-                // If the category isn't in the db, remove the id and insert a new category
-                // Store the inserted id in the category
-                if (!found) {
-                    // Let the db assign the id
-                    category.id = null
-                    category.id = handler.awaitOne {
-                        categoriesQueries.insert(category.name, category.order.toLong(), category.flags.toLong())
-                        categoriesQueries.selectLastInsertedRowId()
-                    }.toInt()
+            }
+            if (!found) {
+                // Let the db assign the id
+                val id = handler.awaitOne {
+                    categoriesQueries.insert(category.name, category.order, category.flags)
+                    categoriesQueries.selectLastInsertedRowId()
                 }
+                category = category.copy(id = id)
             }
+
+            category
+        }
+
+        preferences.categorizedDisplaySettings().set(
+            (dbCategories + categories)
+                .distinctBy { it.flags }
+                .size > 1,
+        )
     }
 
     /**

+ 12 - 11
app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt

@@ -1,6 +1,6 @@
 package eu.kanade.tachiyomi.data.backup.full.models
 
-import eu.kanade.tachiyomi.data.database.models.CategoryImpl
+import eu.kanade.domain.category.model.Category
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.protobuf.ProtoNumber
 
@@ -12,19 +12,20 @@ class BackupCategory(
     // Bump by 100 to specify this is a 0.x value
     @ProtoNumber(100) var flags: Long = 0,
 ) {
-    fun getCategoryImpl(): CategoryImpl {
-        return CategoryImpl().apply {
-            name = [email protected]
-            flags = [email protected]()
-            order = [email protected]()
-        }
+    fun getCategory(): Category {
+        return Category(
+            id = 0,
+            name = [email protected],
+            flags = [email protected],
+            order = [email protected],
+        )
     }
 }
 
-val backupCategoryMapper = { _: Long, name: String, order: Long, flags: Long ->
+val backupCategoryMapper = { category: Category ->
     BackupCategory(
-        name = name,
-        order = order,
-        flags = flags,
+        name = category.name,
+        order = category.order,
+        flags = category.flags,
     )
 }

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

@@ -1,44 +0,0 @@
-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
-import eu.kanade.domain.category.model.Category as DomainCategory
-
-interface Category : Serializable {
-
-    var id: Int?
-
-    var name: String
-
-    var order: Int
-
-    var flags: Int
-
-    private fun setFlags(flag: Int, mask: Int) {
-        flags = flags and mask.inv() or (flag and mask)
-    }
-
-    var displayMode: Int
-        get() = flags and DisplayModeSetting.MASK.toInt()
-        set(mode) = setFlags(mode, DisplayModeSetting.MASK.toInt())
-
-    var sortMode: Int
-        get() = flags and SortModeSetting.MASK.toInt()
-        set(mode) = setFlags(mode, SortModeSetting.MASK.toInt())
-
-    var sortDirection: Int
-        get() = flags and SortDirectionSetting.MASK.toInt()
-        set(mode) = setFlags(mode, SortDirectionSetting.MASK.toInt())
-}
-
-fun Category.toDomainCategory(): DomainCategory? {
-    val categoryId = id ?: return null
-    return DomainCategory(
-        id = categoryId.toLong(),
-        name = this.name,
-        order = this.order.toLong(),
-        flags = this.flags.toLong(),
-    )
-}

+ 0 - 24
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt

@@ -1,24 +0,0 @@
-package eu.kanade.tachiyomi.data.database.models
-
-class CategoryImpl : Category {
-
-    override var id: Int? = null
-
-    override lateinit var name: String
-
-    override var order: Int = 0
-
-    override var flags: Int = 0
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other == null || javaClass != other.javaClass) return false
-
-        val category = other as Category
-        return name == category.name
-    }
-
-    override fun hashCode(): Int {
-        return name.hashCode()
-    }
-}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt

@@ -35,7 +35,7 @@ class CategoryPresenter(
             getCategories.subscribe()
                 .collectLatest {
                     state.isLoading = false
-                    state.categories = it
+                    state.categories = it.filterNot(Category::isSystemCategory)
                 }
         }
     }

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

@@ -9,7 +9,6 @@ import androidx.compose.ui.platform.LocalContext
 import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.ControllerChangeType
 import eu.kanade.domain.category.model.Category
-import eu.kanade.domain.category.model.toDbCategory
 import eu.kanade.domain.manga.model.Manga
 import eu.kanade.domain.manga.model.toDbManga
 import eu.kanade.presentation.library.LibraryScreen
@@ -119,12 +118,8 @@ class LibraryController(
     }
 
     fun showSettingsSheet() {
-        if (presenter.categories.isNotEmpty()) {
-            presenter.categories[presenter.activeCategory].let { category ->
-                settingsSheet?.show(category.toDbCategory())
-            }
-        } else {
-            settingsSheet?.show()
+        presenter.categories[presenter.activeCategory].let { category ->
+            settingsSheet?.show(category)
         }
     }
 

+ 7 - 14
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt

@@ -27,6 +27,7 @@ import eu.kanade.domain.manga.model.Manga
 import eu.kanade.domain.manga.model.MangaUpdate
 import eu.kanade.domain.manga.model.isLocal
 import eu.kanade.domain.track.interactor.GetTracks
+import eu.kanade.presentation.category.visualName
 import eu.kanade.presentation.library.LibraryState
 import eu.kanade.presentation.library.LibraryStateImpl
 import eu.kanade.presentation.library.components.LibraryToolbarTitle
@@ -94,15 +95,9 @@ class LibraryPresenter(
     private val trackManager: TrackManager = Injekt.get(),
 ) : BasePresenter<LibraryController>(), LibraryState by state {
 
-    private val context = preferences.context
-
     var loadedManga by mutableStateOf(emptyMap<Long, List<LibraryItem>>())
         private set
 
-    val isPerCategory by preferences.categorizedDisplaySettings().asState()
-
-    var currentDisplayMode by preferences.libraryDisplayMode().asState()
-
     val tabVisibility by preferences.categoryTabs().asState()
 
     val mangaCountVisibility by preferences.categoryNumberOfItems().asState()
@@ -412,8 +407,8 @@ class LibraryPresenter(
      */
     private fun getLibraryObservable(): Observable<Library> {
         return combine(getCategoriesFlow(), getLibraryMangasFlow()) { dbCategories, libraryManga ->
-            val categories = if (libraryManga.containsKey(0) || libraryManga.isEmpty()) {
-                arrayListOf(Category.default(context)) + dbCategories
+            val categories = if (libraryManga.isNotEmpty() && libraryManga.containsKey(0).not()) {
+                dbCategories.filterNot { it.id == Category.UNCATEGORIZED_ID }
             } else {
                 dbCategories
             }
@@ -642,10 +637,12 @@ class LibraryPresenter(
         val category = categories.getOrNull(activeCategory)
 
         val defaultTitle = stringResource(id = R.string.label_library)
+        val categoryName = category?.visualName ?: defaultTitle
+
         val default = remember { LibraryToolbarTitle(defaultTitle) }
 
         return produceState(initialValue = default, category, loadedManga, mangaCountVisibility, tabVisibility) {
-            val title = if (tabVisibility.not()) category?.name ?: defaultTitle else defaultTitle
+            val title = if (tabVisibility.not()) categoryName else defaultTitle
 
             value = when {
                 category == null -> default
@@ -681,11 +678,7 @@ class LibraryPresenter(
     fun getDisplayMode(index: Int): androidx.compose.runtime.State<DisplayModeSetting> {
         val category = categories[index]
         return derivedStateOf {
-            if (isPerCategory.not() || category.id == 0L) {
-                currentDisplayMode
-            } else {
-                DisplayModeSetting.fromFlag(category.displayMode)
-            }
+            DisplayModeSetting.fromFlag(category.displayMode)
         }
     }
 

+ 15 - 45
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt

@@ -4,11 +4,10 @@ import android.content.Context
 import android.util.AttributeSet
 import android.view.View
 import com.bluelinelabs.conductor.Router
-import eu.kanade.domain.category.interactor.UpdateCategory
-import eu.kanade.domain.category.model.CategoryUpdate
+import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
+import eu.kanade.domain.category.interactor.SetSortModeForCategory
+import eu.kanade.domain.category.model.Category
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.models.Category
-import eu.kanade.tachiyomi.data.database.models.toDomainCategory
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.TrackService
@@ -29,7 +28,8 @@ import uy.kohesive.injekt.injectLazy
 class LibrarySettingsSheet(
     router: Router,
     private val trackManager: TrackManager = Injekt.get(),
-    private val updateCategory: UpdateCategory = Injekt.get(),
+    private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
+    private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
     onGroupClickListener: (ExtendedNavigationView.Group) -> Unit,
 ) : TabbedBottomSheetDialog(router.activity!!) {
 
@@ -202,8 +202,8 @@ class LibrarySettingsSheet(
             override val footer = null
 
             override fun initModels() {
-                val sorting = SortModeSetting.get(preferences, currentCategory?.toDomainCategory())
-                val order = if (SortDirectionSetting.get(preferences, currentCategory?.toDomainCategory()) == SortDirectionSetting.ASCENDING) {
+                val sorting = SortModeSetting.get(preferences, currentCategory)
+                val order = if (SortDirectionSetting.get(preferences, currentCategory) == SortDirectionSetting.ASCENDING) {
                     Item.MultiSort.SORT_ASC
                 } else {
                     Item.MultiSort.SORT_DESC
@@ -256,18 +256,8 @@ class LibrarySettingsSheet(
                     SortDirectionSetting.DESCENDING
                 }
 
-                if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
-                    currentCategory?.sortDirection = flag.flag.toInt()
-                    sheetScope.launchIO {
-                        updateCategory.await(
-                            CategoryUpdate(
-                                id = currentCategory!!.id?.toLong()!!,
-                                flags = currentCategory!!.flags.toLong(),
-                            ),
-                        )
-                    }
-                } else {
-                    preferences.librarySortingAscending().set(flag)
+                sheetScope.launchIO {
+                    setSortModeForCategory.await(currentCategory!!, flag)
                 }
             }
 
@@ -284,18 +274,8 @@ class LibrarySettingsSheet(
                     else -> throw NotImplementedError("Unknown display mode")
                 }
 
-                if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
-                    currentCategory?.sortMode = flag.flag.toInt()
-                    sheetScope.launchIO {
-                        updateCategory.await(
-                            CategoryUpdate(
-                                id = currentCategory!!.id?.toLong()!!,
-                                flags = currentCategory!!.flags.toLong(),
-                            ),
-                        )
-                    }
-                } else {
-                    preferences.librarySortingMode().set(flag)
+                sheetScope.launchIO {
+                    setSortModeForCategory.await(currentCategory!!, flag)
                 }
             }
         }
@@ -327,8 +307,8 @@ class LibrarySettingsSheet(
 
         // Gets user preference of currently selected display mode at current category
         private fun getDisplayModePreference(): DisplayModeSetting {
-            return if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
-                DisplayModeSetting.fromFlag(currentCategory?.displayMode?.toLong())
+            return if (currentCategory != null && preferences.categorizedDisplaySettings().get()) {
+                DisplayModeSetting.fromFlag(currentCategory!!.displayMode)
             } else {
                 preferences.libraryDisplayMode().get()
             }
@@ -379,18 +359,8 @@ class LibrarySettingsSheet(
                     else -> throw NotImplementedError("Unknown display mode")
                 }
 
-                if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
-                    currentCategory?.displayMode = flag.flag.toInt()
-                    sheetScope.launchIO {
-                        updateCategory.await(
-                            CategoryUpdate(
-                                id = currentCategory!!.id?.toLong()!!,
-                                flags = currentCategory!!.flags.toLong(),
-                            ),
-                        )
-                    }
-                } else {
-                    preferences.libraryDisplayMode().set(flag)
+                sheetScope.launchIO {
+                    setDisplayModeForCategory.await(currentCategory!!, flag)
                 }
             }
         }

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

@@ -32,7 +32,7 @@ enum class SortModeSetting(val flag: Long) {
         }
 
         fun get(preferences: PreferencesHelper, category: Category?): SortModeSetting {
-            return if (preferences.categorizedDisplaySettings().get() && category != null && category.id != 0L) {
+            return if (category != null && preferences.categorizedDisplaySettings().get()) {
                 fromFlag(category.sortMode)
             } else {
                 preferences.librarySortingMode().get()

+ 5 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt

@@ -12,7 +12,7 @@ import androidx.preference.PreferenceScreen
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.hippo.unifile.UniFile
 import eu.kanade.domain.category.interactor.GetCategories
-import eu.kanade.domain.category.model.Category
+import eu.kanade.presentation.category.visualName
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
@@ -46,8 +46,7 @@ class SettingsDownloadController : SettingsController() {
     override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
         titleRes = R.string.pref_category_downloads
 
-        val dbCategories = runBlocking { getCategories.await() }
-        val categories = listOf(Category.default(context)) + dbCategories
+        val categories = runBlocking { getCategories.await() }
 
         preference {
             bindTo(preferences.downloadsDirectory())
@@ -111,7 +110,7 @@ class SettingsDownloadController : SettingsController() {
             multiSelectListPreference {
                 bindTo(preferences.removeExcludeCategories())
                 titleRes = R.string.pref_remove_exclude_categories
-                entries = categories.map { it.name }.toTypedArray()
+                entries = categories.map { it.visualName(context) }.toTypedArray()
                 entryValues = categories.map { it.id.toString() }.toTypedArray()
 
                 preferences.removeExcludeCategories().asFlow()
@@ -255,10 +254,9 @@ class SettingsDownloadController : SettingsController() {
         private val getCategories: GetCategories = Injekt.get()
 
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            val dbCategories = runBlocking { getCategories.await() }
-            val categories = listOf(Category.default(activity!!)) + dbCategories
+            val categories = runBlocking { getCategories.await() }
 
-            val items = categories.map { it.name }
+            val items = categories.map { it.visualName(activity!!) }
             var selected = categories
                 .map {
                     when (it.id.toString()) {

+ 25 - 15
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt

@@ -8,7 +8,9 @@ import androidx.core.text.buildSpannedString
 import androidx.preference.PreferenceScreen
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.domain.category.interactor.GetCategories
+import eu.kanade.domain.category.interactor.ResetCategoryFlags
 import eu.kanade.domain.category.model.Category
+import eu.kanade.presentation.category.visualName
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW
@@ -51,12 +53,13 @@ class SettingsLibraryController : SettingsController() {
 
     private val getCategories: GetCategories by injectLazy()
     private val trackManager: TrackManager by injectLazy()
+    private val resetCategoryFlags: ResetCategoryFlags by injectLazy()
 
     override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
         titleRes = R.string.pref_category_library
 
-        val dbCategories = runBlocking { getCategories.await() }
-        val categories = listOf(Category.default(context)) + dbCategories
+        val allCategories = runBlocking { getCategories.await() }
+        val userCategories = allCategories.filterNot(Category::isSystemCategory)
 
         preferenceCategory {
             titleRes = R.string.pref_category_display
@@ -94,7 +97,7 @@ class SettingsLibraryController : SettingsController() {
                 key = "pref_action_edit_categories"
                 titleRes = R.string.action_edit_categories
 
-                val catCount = dbCategories.size
+                val catCount = userCategories.size
                 summary = context.resources.getQuantityString(R.plurals.num_categories, catCount, catCount)
 
                 onClick {
@@ -107,15 +110,15 @@ class SettingsLibraryController : SettingsController() {
                 titleRes = R.string.default_category
 
                 entries = arrayOf(context.getString(R.string.default_category_summary)) +
-                    categories.map { it.name }.toTypedArray()
-                entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray()
+                    allCategories.map { it.visualName(context) }.toTypedArray()
+                entryValues = arrayOf("-1") + allCategories.map { it.id.toString() }.toTypedArray()
                 defaultValue = "-1"
 
-                val selectedCategory = categories.find { it.id == preferences.defaultCategory().toLong() }
+                val selectedCategory = allCategories.find { it.id == preferences.defaultCategory().toLong() }
                 summary = selectedCategory?.name
                     ?: context.getString(R.string.default_category_summary)
                 onChange { newValue ->
-                    summary = categories.find {
+                    summary = allCategories.find {
                         it.id == (newValue as String).toLong()
                     }?.name ?: context.getString(R.string.default_category_summary)
                     true
@@ -125,6 +128,14 @@ class SettingsLibraryController : SettingsController() {
             switchPreference {
                 bindTo(preferences.categorizedDisplaySettings())
                 titleRes = R.string.categorized_display_settings
+
+                preferences.categorizedDisplaySettings().asFlow()
+                    .onEach {
+                        if (it.not()) {
+                            resetCategoryFlags.await()
+                        }
+                    }
+                    .launchIn(viewScope)
             }
         }
 
@@ -229,19 +240,19 @@ class SettingsLibraryController : SettingsController() {
 
                 fun updateSummary() {
                     val includedCategories = preferences.libraryUpdateCategories().get()
-                        .mapNotNull { id -> categories.find { it.id == id.toLong() } }
+                        .mapNotNull { id -> allCategories.find { it.id == id.toLong() } }
                         .sortedBy { it.order }
                     val excludedCategories = preferences.libraryUpdateCategoriesExclude().get()
-                        .mapNotNull { id -> categories.find { it.id == id.toLong() } }
+                        .mapNotNull { id -> allCategories.find { it.id == id.toLong() } }
                         .sortedBy { it.order }
 
-                    val allExcluded = excludedCategories.size == categories.size
+                    val allExcluded = excludedCategories.size == allCategories.size
 
                     val includedItemsText = when {
                         // Some selected, but not all
-                        includedCategories.isNotEmpty() && includedCategories.size != categories.size -> includedCategories.joinToString { it.name }
+                        includedCategories.isNotEmpty() && includedCategories.size != allCategories.size -> includedCategories.joinToString { it.name }
                         // All explicitly selected
-                        includedCategories.size == categories.size -> context.getString(R.string.all)
+                        includedCategories.size == allCategories.size -> context.getString(R.string.all)
                         allExcluded -> context.getString(R.string.none)
                         else -> context.getString(R.string.all)
                     }
@@ -331,10 +342,9 @@ class SettingsLibraryController : SettingsController() {
         private val getCategories: GetCategories = Injekt.get()
 
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            val dbCategories = runBlocking { getCategories.await() }
-            val categories = listOf(Category.default(activity!!)) + dbCategories
+            val categories = runBlocking { getCategories.await() }
 
-            val items = categories.map { it.name }
+            val items = categories.map { it.visualName(activity!!) }
             var selected = categories
                 .map {
                     when (it.id.toString()) {

+ 15 - 0
app/src/main/sqldelight/data/categories.sq

@@ -5,6 +5,17 @@ CREATE TABLE categories(
     flags INTEGER NOT NULL
 );
 
+-- Insert system category
+INSERT OR IGNORE INTO categories(_id, name, sort, flags) VALUES (0, "", -1, 0);
+-- Disallow deletion of default category
+CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE
+ON categories
+BEGIN SELECT CASE
+    WHEN old._id <= 0 THEN
+        RAISE(ABORT, "System category can't be deleted")
+    END;
+END;
+
 getCategories:
 SELECT
 _id AS id,
@@ -40,5 +51,9 @@ SET name = coalesce(:name, name),
     flags = coalesce(:flags, flags)
 WHERE _id = :categoryId;
 
+updateAllFlags:
+UPDATE categories SET
+flags = coalesce(?, flags);
+
 selectLastInsertedRowId:
 SELECT last_insert_rowid();

+ 10 - 0
app/src/main/sqldelight/migrations/19.sqm

@@ -0,0 +1,10 @@
+-- Insert Default category
+INSERT OR IGNORE INTO categories(_id, name, sort, flags) VALUES (0, "", -1, 0);
+-- Disallow deletion of default category
+CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE
+ON categories
+BEGIN SELECT CASE
+    WHEN old._id <= 0 THEN
+        RAISE(ABORT, "System category can't be deleted")
+    END;
+END;