From 8bfa495f34db1eace681b39bf2328421b5a89331 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:53:09 +0600 Subject: [PATCH] Assign category positions inside the database A new category's position is computed by its insert, one after the highest, so two categories created at once can't take the same one. Inserts take a NewCategory rather than a Category whose id and position would be ignored. Reordering places any category missing from the given order after the rest instead of leaving it at a stale position. Restore still creates missing categories all or none, in backup order, and the create dialog no longer accepts an empty name. --- .../category/components/CategoryDialogs.kt | 2 +- .../data/backup/models/BackupCategory.kt | 9 +------ .../restore/restorers/CategoriesRestorer.kt | 27 +++++++++---------- .../data/category/CategoryRepositoryImpl.kt | 17 ++++++------ .../sqldelight/tachiyomi/data/categories.sq | 9 ++++++- .../interactor/CreateCategoryWithName.kt | 13 ++------- .../domain/category/model/NewCategory.kt | 6 +++++ .../category/repository/CategoryRepository.kt | 5 ++-- 8 files changed, 42 insertions(+), 46 deletions(-) create mode 100644 domain/src/main/java/tachiyomi/domain/category/model/NewCategory.kt diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt index 38477acd0..96430f85a 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt @@ -110,7 +110,7 @@ fun CategoryRenameDialog( onDismissRequest = onDismissRequest, confirmButton = { TextButton( - enabled = valueHasChanged && !nameAlreadyExists, + enabled = valueHasChanged && name.isNotEmpty() && !nameAlreadyExists, onClick = { onRename(name) onDismissRequest() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt index 942b307c6..2600e22a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt @@ -11,14 +11,7 @@ class BackupCategory( @ProtoNumber(3) var id: Long = 0, // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x @ProtoNumber(100) var flags: Long = 0, -) { - fun toCategory(id: Long) = Category( - id = id, - name = this@BackupCategory.name, - flags = this@BackupCategory.flags, - order = this@BackupCategory.order, - ) -} +) val backupCategoryMapper = { category: Category -> BackupCategory( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index acbdbc94d..f6ad843ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import dev.zacsweers.metro.Inject import eu.kanade.tachiyomi.data.backup.models.BackupCategory import tachiyomi.domain.category.interactor.GetCategories +import tachiyomi.domain.category.model.NewCategory import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.library.service.LibraryPreferences @@ -14,22 +15,20 @@ class CategoriesRestorer( ) { suspend operator fun invoke(backupCategories: List) { - if (backupCategories.isNotEmpty()) { - val dbCategories = getCategories.await() - val dbCategoriesByName = dbCategories.associateBy { it.name } - var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0 + if (backupCategories.isEmpty()) return - val categories = backupCategories - .sortedBy { it.order } - .filter { it.name !in dbCategoriesByName } - .map { it.toCategory(id = 0).copy(order = nextOrder++) } - categoryRepository.insertAll(categories) + val dbCategories = getCategories.await() + val dbCategoryNames = dbCategories.mapTo(HashSet()) { it.name } - libraryPreferences.categorizedDisplaySettings.set( - (dbCategories + categories) - .distinctBy { it.flags } - .size > 1, - ) + val newCategories = backupCategories + .filter { it.name !in dbCategoryNames } + .sortedBy { it.order } + categoryRepository.insertAll(newCategories.map { NewCategory(name = it.name, flags = it.flags) }) + + val flags = buildSet { + dbCategories.mapTo(this) { it.flags } + newCategories.mapTo(this) { it.flags } } + libraryPreferences.categorizedDisplaySettings.set(flags.size > 1) } } diff --git a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt index c60fd9daa..271fc7796 100644 --- a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.Flow import tachiyomi.data.Database import tachiyomi.data.subscribeToList import tachiyomi.domain.category.model.Category +import tachiyomi.domain.category.model.NewCategory import tachiyomi.domain.category.repository.CategoryRepository @Inject @@ -49,15 +50,11 @@ class CategoryRepositoryImpl( .subscribeToList() } - override suspend fun insert(category: Category) { - database.categoriesQueries.insert( - name = category.name, - order = category.order, - flags = category.flags, - ) + override suspend fun insert(category: NewCategory) { + database.categoriesQueries.insert(name = category.name, flags = category.flags) } - override suspend fun insertAll(categories: List) { + override suspend fun insertAll(categories: List) { database.transaction { categories.forEach { insert(it) } } @@ -77,10 +74,12 @@ class CategoryRepositoryImpl( override suspend fun updateAllOrders(orderedIds: List) { database.transaction { - orderedIds.forEachIndexed { index, categoryId -> + val current = database.categoriesQueries.getUserCategoryIds().awaitAsList() + val ids = orderedIds.filter { it in current } + current.filterNot { it in orderedIds } + ids.forEachIndexed { index, categoryId -> database.categoriesQueries.updateOrder(order = -index - 2L, categoryId = categoryId) } - orderedIds.forEachIndexed { index, categoryId -> + ids.forEachIndexed { index, categoryId -> database.categoriesQueries.updateOrder(order = index.toLong(), categoryId = categoryId) } } diff --git a/data/src/main/sqldelight/tachiyomi/data/categories.sq b/data/src/main/sqldelight/tachiyomi/data/categories.sq index a94f38f93..cdc465835 100644 --- a/data/src/main/sqldelight/tachiyomi/data/categories.sq +++ b/data/src/main/sqldelight/tachiyomi/data/categories.sq @@ -44,7 +44,8 @@ WHERE MC.manga_id = :mangaId; insert: INSERT INTO categories(name, sort, flags) -VALUES (:name, :order, :flags); +SELECT :name, coalesce(max(sort), -1) + 1, :flags +FROM categories; delete: DELETE FROM categories @@ -55,6 +56,12 @@ UPDATE categories SET name = :name WHERE _id = :categoryId; +getUserCategoryIds: +SELECT _id +FROM categories +WHERE _id > 0 +ORDER BY sort; + updateOrder: UPDATE categories SET sort = :order diff --git a/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt b/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt index 244d63545..e050cfc8b 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/CreateCategoryWithName.kt @@ -4,7 +4,7 @@ import dev.zacsweers.metro.Inject import logcat.LogPriority import tachiyomi.core.common.util.lang.withNonCancellableContext import tachiyomi.core.common.util.system.logcat -import tachiyomi.domain.category.model.Category +import tachiyomi.domain.category.model.NewCategory import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.library.service.LibraryPreferences @@ -21,17 +21,8 @@ class CreateCategoryWithName( } suspend fun await(name: String): Result = withNonCancellableContext { - val categories = categoryRepository.getAll() - val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0 - val newCategory = Category( - id = 0, - name = name, - order = nextOrder, - flags = initialFlags, - ) - try { - categoryRepository.insert(newCategory) + categoryRepository.insert(NewCategory(name = name, flags = initialFlags)) Result.Success } catch (e: Exception) { logcat(LogPriority.ERROR, e) diff --git a/domain/src/main/java/tachiyomi/domain/category/model/NewCategory.kt b/domain/src/main/java/tachiyomi/domain/category/model/NewCategory.kt new file mode 100644 index 000000000..7ddb7738b --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/category/model/NewCategory.kt @@ -0,0 +1,6 @@ +package tachiyomi.domain.category.model + +data class NewCategory( + val name: String, + val flags: Long, +) diff --git a/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt b/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt index 1ad636427..9c425862f 100644 --- a/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt @@ -2,6 +2,7 @@ package tachiyomi.domain.category.repository import kotlinx.coroutines.flow.Flow import tachiyomi.domain.category.model.Category +import tachiyomi.domain.category.model.NewCategory interface CategoryRepository { @@ -15,9 +16,9 @@ interface CategoryRepository { fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow> - suspend fun insert(category: Category) + suspend fun insert(category: NewCategory) - suspend fun insertAll(categories: List) + suspend fun insertAll(categories: List) suspend fun updateName(categoryId: Long, name: String)