From 914831d51fbb915aea5cbb409b1da552862c380c Mon Sep 17 00:00:00 2001 From: Andreas Date: Fri, 5 Aug 2022 15:32:10 +0200 Subject: [PATCH] Move default category into database (#7676) --- .../data/category/CategoryRepositoryImpl.kt | 6 ++ .../java/eu/kanade/domain/DomainModule.kt | 6 ++ .../category/interactor/ReorderCategory.kt | 2 +- .../category/interactor/ResetCategoryFlags.kt | 25 ++++++++ .../interactor/SetDisplayModeForCategory.kt | 28 +++++++++ .../interactor/SetSortModeForCategory.kt | 49 +++++++++++++++ .../kanade/domain/category/model/Category.kt | 23 ++----- .../category/repository/CategoryRepository.kt | 2 + .../category/CategoryExtensions.kt | 20 +++++++ .../library/components/LibraryTabs.kt | 3 +- .../data/backup/full/FullBackupManager.kt | 59 ++++++++++-------- .../data/backup/full/models/BackupCategory.kt | 23 +++---- .../data/database/models/Category.kt | 44 -------------- .../data/database/models/CategoryImpl.kt | 24 -------- .../ui/category/CategoryPresenter.kt | 2 +- .../tachiyomi/ui/library/LibraryController.kt | 9 +-- .../tachiyomi/ui/library/LibraryPresenter.kt | 21 +++---- .../ui/library/LibrarySettingsSheet.kt | 60 +++++-------------- .../ui/library/setting/SortModeSetting.kt | 2 +- .../ui/setting/SettingsDownloadController.kt | 12 ++-- .../ui/setting/SettingsLibraryController.kt | 40 ++++++++----- app/src/main/sqldelight/data/categories.sq | 15 +++++ app/src/main/sqldelight/migrations/19.sqm | 10 ++++ 23 files changed, 269 insertions(+), 216 deletions(-) create mode 100644 app/src/main/java/eu/kanade/domain/category/interactor/ResetCategoryFlags.kt create mode 100644 app/src/main/java/eu/kanade/domain/category/interactor/SetDisplayModeForCategory.kt create mode 100644 app/src/main/java/eu/kanade/domain/category/interactor/SetSortModeForCategory.kt create mode 100644 app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt create mode 100644 app/src/main/sqldelight/migrations/19.sqm diff --git a/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt index 9c2c1283b0..e64bb0c6eb 100644 --- a/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt +++ b/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( diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 6c6b1a7634..0c2777dbd1 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/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 { 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()) } diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/ReorderCategory.kt b/app/src/main/java/eu/kanade/domain/category/interactor/ReorderCategory.kt index bfaaf174ac..a286103a68 100644 --- a/app/src/main/java/eu/kanade/domain/category/interactor/ReorderCategory.kt +++ b/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) { diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/ResetCategoryFlags.kt b/app/src/main/java/eu/kanade/domain/category/interactor/ResetCategoryFlags.kt new file mode 100644 index 0000000000..21504eddd7 --- /dev/null +++ b/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) + } +} diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/SetDisplayModeForCategory.kt b/app/src/main/java/eu/kanade/domain/category/interactor/SetDisplayModeForCategory.kt new file mode 100644 index 0000000000..d1174db34d --- /dev/null +++ b/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) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/SetSortModeForCategory.kt b/app/src/main/java/eu/kanade/domain/category/interactor/SetSortModeForCategory.kt new file mode 100644 index 0000000000..94a9bf6202 --- /dev/null +++ b/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) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/category/model/Category.kt b/app/src/main/java/eu/kanade/domain/category/model/Category.kt index 74666136e0..1b8b1d14ba 100644 --- a/app/src/main/java/eu/kanade/domain/category/model/Category.kt +++ b/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.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() -} diff --git a/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt b/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt index 11055d5598..76bb5cfab1 100644 --- a/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt +++ b/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt @@ -20,5 +20,7 @@ interface CategoryRepository { suspend fun updatePartial(updates: List) + suspend fun updateAllFlags(flags: Long?) + suspend fun delete(categoryId: Long) } diff --git a/app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt b/app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt new file mode 100644 index 0000000000..a9a5a64e5b --- /dev/null +++ b/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 + } diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt index c2cd117878..43a84226b0 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt +++ b/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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index e5c76789f0..d629c82e9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/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 { // 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) { // Get categories from file and from db - val dbCategories = handler.awaitList { categoriesQueries.getCategories() } + val dbCategories = handler.awaitList { categoriesQueries.getCategories(categoryMapper) } - // 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 - } - } - // 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() + 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 (!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, + ) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt index 88bc7ec208..dde26daaa6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt +++ b/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 = this@BackupCategory.name - flags = this@BackupCategory.flags.toInt() - order = this@BackupCategory.order.toInt() - } + fun getCategory(): Category { + return Category( + id = 0, + name = this@BackupCategory.name, + flags = this@BackupCategory.flags, + order = this@BackupCategory.order, + ) } } -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, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt deleted file mode 100644 index 806a36671f..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt +++ /dev/null @@ -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(), - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt deleted file mode 100644 index af40baecb8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index b24c81ec9f..f0809a9fe7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/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) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index cec1a6f076..b4fa87fd9e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/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) } } 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 995cdf3d73..9ad503ff38 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 @@ -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(), LibraryState by state { - private val context = preferences.context - var loadedManga by mutableStateOf(emptyMap>()) 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 { 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 { val category = categories[index] return derivedStateOf { - if (isPerCategory.not() || category.id == 0L) { - currentDisplayMode - } else { - DisplayModeSetting.fromFlag(category.displayMode) - } + DisplayModeSetting.fromFlag(category.displayMode) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt index 68424de80b..998a7b4fd0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt +++ b/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) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/SortModeSetting.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/SortModeSetting.kt index ee93e81a71..ee9664aad4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/SortModeSetting.kt +++ b/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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index 8b550b9b70..7c89ae6c6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/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()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index 8107c35cab..ced54c2c2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/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()) { diff --git a/app/src/main/sqldelight/data/categories.sq b/app/src/main/sqldelight/data/categories.sq index e75cdaa85d..33bf3da0ae 100644 --- a/app/src/main/sqldelight/data/categories.sq +++ b/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(); \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/19.sqm b/app/src/main/sqldelight/migrations/19.sqm new file mode 100644 index 0000000000..253bdb3ab0 --- /dev/null +++ b/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; \ No newline at end of file