Move default category into database (#7676)

(cherry picked from commit 914831d51fbb915aea5cbb409b1da552862c380c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt
#	app/src/main/sqldelight/migrations/19.sqm
This commit is contained in:
Andreas 2022-08-05 15:32:10 +02:00 committed by Jobobby04
parent af1ee662ed
commit 394e9b3fe6
27 changed files with 379 additions and 279 deletions

View File

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

View File

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

View File

@ -13,7 +13,7 @@ class ReorderCategory(
) { ) {
suspend fun await(categoryId: Long, newPosition: Int) = withContext(NonCancellable) await@{ 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 } val currentIndex = categories.indexOfFirst { it.id == categoryId }
if (currentIndex == newPosition) { if (currentIndex == newPosition) {

View File

@ -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)
}
}

View File

@ -0,0 +1,38 @@
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.LibraryGroup
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)
// SY -->
val isDefaultGroup = preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT
// SY <--
if (preferences.categorizedDisplaySettings().get() /* SY --> */ && isDefaultGroup/* SY <-- */) {
categoryRepository.updatePartial(
CategoryUpdate(
id = category.id,
flags = flags,
),
)
} else {
preferences.libraryDisplayMode().set(displayModeSetting)
// SY -->
if (isDefaultGroup) {
// SY <--
categoryRepository.updateAllFlags(flags)
// SY -->
}
// SY <--
}
}
}

View File

@ -0,0 +1,59 @@
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.LibraryGroup
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() /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT/* SY <-- */) {
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() /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT/* SY <-- */) {
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)
// SY -->
val isDefaultGroup = preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT
// SY <--
if (preferences.categorizedDisplaySettings().get() /* SY --> */ && isDefaultGroup/* SY <-- */) {
categoryRepository.updatePartial(
CategoryUpdate(
id = category.id,
flags = flags,
),
)
} else {
preferences.librarySortingMode().set(sortModeSetting)
preferences.librarySortingAscending().set(sortDirectionSetting)
// SY -->
if (isDefaultGroup) {
// SY <--
categoryRepository.updateAllFlags(flags)
// SY -->
}
// SY <--
}
}
}

View File

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

View File

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

View File

@ -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
}

View File

@ -109,6 +109,7 @@ fun LibraryScreen(
isDownloadOnly = presenter.isDownloadOnly, isDownloadOnly = presenter.isDownloadOnly,
// SY --> // SY -->
onOpenReader = onOpenReader, onOpenReader = onOpenReader,
getCategoryName = presenter::getCategoryName,
// SY <-- // SY <--
) )
} }

View File

@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.ui.library.LibraryGroup
import exh.source.PERV_EDEN_EN_SOURCE_ID import exh.source.PERV_EDEN_EN_SOURCE_ID
import exh.source.PERV_EDEN_IT_SOURCE_ID import exh.source.PERV_EDEN_IT_SOURCE_ID
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
@ -24,6 +25,7 @@ interface LibraryState {
// SY --> // SY -->
val ogCategories: List<Category> val ogCategories: List<Category>
val groupType: Int
val showSyncExh: Boolean val showSyncExh: Boolean
val showCleanTitles: Boolean val showCleanTitles: Boolean
val showAddToMangadex: Boolean val showAddToMangadex: Boolean
@ -43,6 +45,8 @@ class LibraryStateImpl : LibraryState {
override var hasActiveFilters: Boolean by mutableStateOf(false) override var hasActiveFilters: Boolean by mutableStateOf(false)
// SY --> // SY -->
override var groupType: Int by mutableStateOf(LibraryGroup.BY_DEFAULT)
override var ogCategories: List<Category> by mutableStateOf(emptyList()) override var ogCategories: List<Category> by mutableStateOf(emptyList())
override var showSyncExh: Boolean by mutableStateOf(true) override var showSyncExh: Boolean by mutableStateOf(true)

View File

@ -1,13 +1,16 @@
package eu.kanade.presentation.library.components package eu.kanade.presentation.library.components
import android.content.Context
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.SwipeRefresh
@ -44,6 +47,7 @@ fun LibraryContent(
getLibraryForPage: @Composable (Int) -> State<List<LibraryItem>>, getLibraryForPage: @Composable (Int) -> State<List<LibraryItem>>,
// SY --> // SY -->
onOpenReader: (LibraryManga) -> Unit, onOpenReader: (LibraryManga) -> Unit,
getCategoryName: (Context, Category, Int, String) -> String,
// SY <-- // SY <--
) { ) {
Column( Column(
@ -61,6 +65,14 @@ fun LibraryContent(
getNumberOfMangaForCategory = getNumberOfMangaForCategory, getNumberOfMangaForCategory = getNumberOfMangaForCategory,
isDownloadOnly = isDownloadOnly, isDownloadOnly = isDownloadOnly,
isIncognitoMode = isIncognitoMode, isIncognitoMode = isIncognitoMode,
// SY -->
getCategoryName = { category, name ->
val context = LocalContext.current
derivedStateOf {
getCategoryName(context, category, state.groupType, name)
}.value
},
// SY <--
) )
} }

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.PagerState
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.DownloadedOnlyModeBanner import eu.kanade.presentation.components.DownloadedOnlyModeBanner
import eu.kanade.presentation.components.IncognitoModeBanner import eu.kanade.presentation.components.IncognitoModeBanner
import eu.kanade.presentation.components.Pill import eu.kanade.presentation.components.Pill
@ -36,6 +37,9 @@ fun LibraryTabs(
isDownloadOnly: Boolean, isDownloadOnly: Boolean,
isIncognitoMode: Boolean, isIncognitoMode: Boolean,
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>, getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
// SY -->
getCategoryName: @Composable (Category, String) -> String,
// SY <--
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -43,12 +47,12 @@ fun LibraryTabs(
Column { Column {
ScrollableTabRow( ScrollableTabRow(
selectedTabIndex = state.currentPage, selectedTabIndex = state.currentPage.coerceAtMost(categories.lastIndex),
edgePadding = 0.dp, edgePadding = 0.dp,
indicator = { tabPositions -> indicator = { tabPositions ->
TabRowDefaults.Indicator( TabRowDefaults.Indicator(
Modifier Modifier
.tabIndicatorOffset(tabPositions[state.currentPage]) .tabIndicatorOffset(tabPositions[state.currentPage.coerceAtMost(categories.lastIndex)])
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)), .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
) )
}, },
@ -67,7 +71,9 @@ fun LibraryTabs(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Text(
text = category.name, // SY -->
text = getCategoryName(category, category.visualName),
// SY <--
color = if (state.currentPage == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground, color = if (state.currentPage == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
) )
if (count != null) { if (count != null) {

View File

@ -5,8 +5,10 @@ import android.net.Uri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import data.Manga_sync import data.Manga_sync
import data.Mangas import data.Mangas
import eu.kanade.data.category.categoryMapper
import eu.kanade.data.exh.mergedMangaReferenceMapper import eu.kanade.data.exh.mergedMangaReferenceMapper
import eu.kanade.data.manga.mangaMapper import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.history.model.HistoryUpdate import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
@ -160,7 +162,9 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
private suspend fun backupCategories(options: Int): List<BackupCategory> { private suspend fun backupCategories(options: Int): List<BackupCategory> {
// Check if user wants category information in backup // Check if user wants category information in backup
return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { 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 { } else {
emptyList() emptyList()
} }
@ -270,34 +274,37 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
*/ */
internal suspend fun restoreCategories(backupCategories: List<BackupCategory>) { internal suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
// Get categories from file and from db // Get categories from file and from db
val dbCategories = handler.awaitList { categoriesQueries.getCategories() } val dbCategories = handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
// Iterate over them val categories = backupCategories.map {
backupCategories var category = it.getCategory()
.map { it.getCategoryImpl() } var found = false
.forEach { category -> for (dbCategory in dbCategories) {
// Used to know if the category is already in the db // If the category is already in the db, assign the id to the file's category
var found = false // and do nothing
for (dbCategory in dbCategories) { if (category.name == dbCategory.name) {
// If the category is already in the db, assign the id to the file's category category = category.copy(id = dbCategory.id)
// and do nothing found = true
if (category.name == dbCategory.name) { break
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()
} }
} }
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,
)
} }
/** /**

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.backup.full.models 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.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@ -14,20 +14,21 @@ class BackupCategory(
// SY specific values // SY specific values
/*@ProtoNumber(600) var mangaOrder: List<Long> = emptyList(),*/ /*@ProtoNumber(600) var mangaOrder: List<Long> = emptyList(),*/
) { ) {
fun getCategoryImpl(): CategoryImpl { fun getCategory(): Category {
return CategoryImpl().apply { return Category(
name = this@BackupCategory.name id = 0,
flags = this@BackupCategory.flags.toInt() name = this@BackupCategory.name,
order = this@BackupCategory.order.toInt() flags = this@BackupCategory.flags,
order = this@BackupCategory.order,
/*mangaOrder = this@BackupCategory.mangaOrder*/ /*mangaOrder = this@BackupCategory.mangaOrder*/
} )
} }
} }
val backupCategoryMapper = { _: Long, name: String, order: Long, flags: Long -> val backupCategoryMapper = { category: Category ->
BackupCategory( BackupCategory(
name = name, name = category.name,
order = order, order = category.order,
flags = flags, flags = category.flags,
) )
} }

View File

@ -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(),
)
}

View File

@ -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()
}
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.track.myanimelist package eu.kanade.tachiyomi.data.track.myanimelist
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response

View File

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

View File

@ -12,7 +12,6 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.category.model.Category 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.Manga
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.presentation.library.LibraryScreen import eu.kanade.presentation.library.LibraryScreen
@ -205,7 +204,7 @@ class LibraryController(
fun showSettingsSheet() { fun showSettingsSheet() {
if (presenter.categories.isNotEmpty() /* SY --> */ && presenter.groupType == LibraryGroup.BY_DEFAULT /* SY <-- */) { if (presenter.categories.isNotEmpty() /* SY --> */ && presenter.groupType == LibraryGroup.BY_DEFAULT /* SY <-- */) {
presenter.categories[presenter.activeCategory].let { category -> presenter.categories[presenter.activeCategory].let { category ->
settingsSheet?.show(category.toDbCategory()) settingsSheet?.show(category)
} }
} else { } else {
settingsSheet?.show() settingsSheet?.show()

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@ -8,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
@ -34,6 +36,7 @@ import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.model.Track import eu.kanade.domain.track.model.Track
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.library.LibraryState import eu.kanade.presentation.library.LibraryState
import eu.kanade.presentation.library.LibraryStateImpl import eu.kanade.presentation.library.LibraryStateImpl
import eu.kanade.presentation.library.components.LibraryToolbarTitle import eu.kanade.presentation.library.components.LibraryToolbarTitle
@ -138,15 +141,9 @@ class LibraryPresenter(
// SY <-- // SY <--
) : BasePresenter<LibraryController>(), LibraryState by state { ) : BasePresenter<LibraryController>(), LibraryState by state {
private val context = preferences.context
var loadedManga by mutableStateOf(emptyMap<Long, List<LibraryItem>>()) var loadedManga by mutableStateOf(emptyMap<Long, List<LibraryItem>>())
private set private set
val isPerCategory by preferences.categorizedDisplaySettings().asState()
var currentDisplayMode by preferences.libraryDisplayMode().asState()
val tabVisibility by preferences.categoryTabs().asState() val tabVisibility by preferences.categoryTabs().asState()
val mangaCountVisibility by preferences.categoryNumberOfItems().asState() val mangaCountVisibility by preferences.categoryNumberOfItems().asState()
@ -175,15 +172,13 @@ class LibraryPresenter(
private var librarySubscription: Job? = null private var librarySubscription: Job? = null
// SY --> // SY -->
val favoritesSync = FavoritesSyncHelper(context) val favoritesSync = FavoritesSyncHelper(preferences.context)
val groupType by preferences.groupLibraryBy().asState()
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } } private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
private val services by lazy { private val services by lazy {
trackManager.services.associate { service -> trackManager.services.associate { service ->
service.id to context.getString(service.nameRes()) service.id to preferences.context.getString(service.nameRes())
} }
} }
@ -249,6 +244,7 @@ class LibraryPresenter(
.asFlow() .asFlow()
.collectLatest { .collectLatest {
// SY --> // SY -->
state.groupType = preferences.groupLibraryBy().get()
state.categories = it.categories state.categories = it.categories
// SY <-- // SY <--
state.isLoading = false state.isLoading = false
@ -578,8 +574,8 @@ class LibraryPresenter(
*/ */
private fun getLibraryObservable(): Observable<Library> { private fun getLibraryObservable(): Observable<Library> {
return combine(getCategoriesFlow(), getLibraryMangasFlow()) { dbCategories, libraryManga -> return combine(getCategoriesFlow(), getLibraryMangasFlow()) { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0) || libraryManga.isEmpty()) { val categories = if (libraryManga.isNotEmpty() && libraryManga.containsKey(0).not()) {
arrayListOf(Category.default(context)) + dbCategories dbCategories.filterNot { it.id == Category.UNCATEGORIZED_ID }
} else { } else {
dbCategories dbCategories
} }
@ -612,7 +608,8 @@ class LibraryPresenter(
} }
else -> { else -> {
val (items, customCategories) = getGroupedMangaItems( val (items, customCategories) = getGroupedMangaItems(
map.values.flatten().distinctBy { it.manga.id }, groupType = groupType,
libraryManga = map.values.flatten().distinctBy { it.manga.id },
) )
editedCategories = customCategories editedCategories = customCategories
items items
@ -909,13 +906,18 @@ class LibraryPresenter(
// TODO: This is good but should we separate title from count or get categories with count from db // TODO: This is good but should we separate title from count or get categories with count from db
@Composable @Composable
fun getToolbarTitle(): androidx.compose.runtime.State<LibraryToolbarTitle> { fun getToolbarTitle(): androidx.compose.runtime.State<LibraryToolbarTitle> {
val context = LocalContext.current
val category = categories.getOrNull(activeCategory) val category = categories.getOrNull(activeCategory)
val defaultTitle = stringResource(id = R.string.label_library) val defaultTitle = stringResource(id = R.string.label_library)
val categoryName = category?.visualName ?: defaultTitle
val default = remember { LibraryToolbarTitle(defaultTitle) } val default = remember { LibraryToolbarTitle(defaultTitle) }
return produceState(initialValue = default, category, loadedManga, mangaCountVisibility, tabVisibility) { return produceState(initialValue = default, category, loadedManga, mangaCountVisibility, tabVisibility, groupType, context) {
val title = if (tabVisibility.not()) category?.name ?: defaultTitle else defaultTitle val title = if (tabVisibility.not()) {
getCategoryName(context, category, groupType, categoryName)
} else defaultTitle
value = when { value = when {
category == null -> default category == null -> default
@ -928,6 +930,36 @@ class LibraryPresenter(
} }
} }
fun getCategoryName(
context: Context,
category: Category?,
groupType: Int,
categoryName: String,
): String {
return when (groupType) {
LibraryGroup.BY_STATUS -> when (category?.id) {
SManga.ONGOING.toLong() -> context.getString(R.string.ongoing)
SManga.LICENSED.toLong() -> context.getString(R.string.licensed)
SManga.CANCELLED.toLong() -> context.getString(R.string.cancelled)
SManga.ON_HIATUS.toLong() -> context.getString(R.string.on_hiatus)
SManga.PUBLISHING_FINISHED.toLong() -> context.getString(R.string.publishing_finished)
SManga.COMPLETED.toLong() -> context.getString(R.string.completed)
else -> context.getString(R.string.unknown)
}
LibraryGroup.BY_SOURCE -> if (category?.id == LocalSource.ID) {
context.getString(R.string.local_source)
} else {
categoryName
}
LibraryGroup.BY_TRACK_STATUS -> TrackStatus.values()
.find { it.int.toLong() == category?.id }
.let { it ?: TrackStatus.OTHER }
.let { context.getString(it.res) }
LibraryGroup.UNGROUPED -> context.getString(R.string.ungrouped)
else -> categoryName
}
}
// SY --> // SY -->
@Composable @Composable
fun getMangaForCategory(page: Int): androidx.compose.runtime.State<List<LibraryItem>> { fun getMangaForCategory(page: Int): androidx.compose.runtime.State<List<LibraryItem>> {
@ -1063,7 +1095,7 @@ class LibraryPresenter(
} }
} }
private fun filterTracks(constraint: String, tracks: List<eu.kanade.domain.track.model.Track>): Boolean { private fun filterTracks(constraint: String, tracks: List<Track>): Boolean {
return tracks.any { return tracks.any {
val trackService = trackManager.getService(it.syncId) val trackService = trackManager.getService(it.syncId)
if (trackService != null) { if (trackService != null) {
@ -1073,6 +1105,8 @@ class LibraryPresenter(
} else false } else false
} }
} }
private val currentCategory by preferences.libraryDisplayMode().asState()
// SY <-- // SY <--
@Composable @Composable
@ -1080,12 +1114,12 @@ class LibraryPresenter(
val category = categories[index] val category = categories[index]
return derivedStateOf { return derivedStateOf {
// SY --> // SY -->
if (groupType != LibraryGroup.BY_DEFAULT || isPerCategory.not() || (category.id == 0L && groupType == LibraryGroup.BY_DEFAULT)) { if (groupType != LibraryGroup.BY_DEFAULT) {
// SY <-- currentCategory
currentDisplayMode
} else { } else {
DisplayModeSetting.fromFlag(category.displayMode) DisplayModeSetting.fromFlag(category.displayMode)
} }
// SY <--
} }
} }
@ -1135,41 +1169,8 @@ class LibraryPresenter(
} }
} }
private fun getGroupedMangaItems(libraryManga: List<LibraryItem>): Pair<LibraryMap, List<Category>> { private fun getGroupedMangaItems(groupType: Int, libraryManga: List<LibraryItem>): Pair<LibraryMap, List<Category>> {
val groupType = preferences.groupLibraryBy().get() val manga = mutableMapOf<Long, MutableList<LibraryItem>>()
val grouping: MutableMap<Long, Pair<Long, String>> = mutableMapOf()
when (groupType) {
LibraryGroup.BY_STATUS -> {
grouping.putAll(
listOf(
SManga.ONGOING.toLong() to context.getString(R.string.ongoing),
SManga.LICENSED.toLong() to context.getString(R.string.licensed),
SManga.CANCELLED.toLong() to context.getString(R.string.cancelled),
SManga.ON_HIATUS.toLong() to context.getString(R.string.on_hiatus),
SManga.PUBLISHING_FINISHED.toLong() to context.getString(R.string.publishing_finished),
SManga.COMPLETED.toLong() to context.getString(R.string.completed),
SManga.UNKNOWN.toLong() to context.getString(R.string.unknown),
).associateBy(Pair<Long, *>::first),
)
}
LibraryGroup.BY_SOURCE ->
libraryManga
.map { it.manga.source }
.distinct()
.sorted()
.map { sourceId ->
sourceId to (sourceId to sourceManager.getOrStub(sourceId).name)
}
.let(grouping::putAll)
LibraryGroup.BY_TRACK_STATUS -> {
grouping.putAll(
TrackStatus.values()
.map { it.int.toLong() to context.getString(it.res) }
.associateBy(Pair<Long, *>::first),
)
}
}
val map: MutableMap<Long, MutableList<LibraryItem>> = mutableMapOf()
when (groupType) { when (groupType) {
LibraryGroup.BY_TRACK_STATUS -> { LibraryGroup.BY_TRACK_STATUS -> {
@ -1179,46 +1180,39 @@ class LibraryPresenter(
TrackStatus.parseTrackerStatus(track.syncId, track.status) TrackStatus.parseTrackerStatus(track.syncId, track.status)
} ?: TrackStatus.OTHER } ?: TrackStatus.OTHER
map.getOrPut(status.int.toLong()) { mutableListOf() } += libraryItem manga.getOrPut(status.int.toLong()) { mutableListOf() } += libraryItem
} }
} }
LibraryGroup.BY_SOURCE -> { LibraryGroup.BY_SOURCE -> {
libraryManga.forEach { libraryItem -> libraryManga.forEach { libraryItem ->
val group = grouping[libraryItem.manga.source] manga.getOrPut(libraryItem.manga.source) { mutableListOf() } += libraryItem
if (group != null) {
map.getOrPut(group.first) { mutableListOf() } += libraryItem
} else {
grouping.getOrPut(Long.MAX_VALUE) {
Long.MAX_VALUE to context.getString(R.string.unknown)
}
map.getOrPut(Long.MAX_VALUE) { mutableListOf() } += libraryItem
}
} }
} }
else -> { else -> {
libraryManga.forEach { libraryItem -> libraryManga.forEach { libraryItem ->
val group = grouping[libraryItem.manga.status.toLong()] manga.getOrPut(libraryItem.manga.status.toLong()) { mutableListOf() } += libraryItem
if (group != null) {
map.getOrPut(group.first) { mutableListOf() } += libraryItem
} else {
grouping.getOrPut(Long.MAX_VALUE) {
Long.MAX_VALUE to context.getString(R.string.unknown)
}
map.getOrPut(Long.MAX_VALUE) { mutableListOf() } += libraryItem
}
} }
} }
} }
val categories = when (groupType) { val categories = when (groupType) {
LibraryGroup.BY_SOURCE -> grouping.values.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, Pair<*, String>::second)) LibraryGroup.BY_SOURCE -> manga.keys.map { Category(it, sourceManager.getOrStub(it).name, 0, 0) }
LibraryGroup.BY_TRACK_STATUS, LibraryGroup.BY_STATUS -> grouping.values.filter { it.first in map.keys } .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
else -> grouping.values LibraryGroup.BY_TRACK_STATUS ->
}.map { (id, name) -> manga.keys
Category(id, name, 0, 0) .map { id ->
TrackStatus.values().find { id == it.int.toLong() }
?: TrackStatus.OTHER
}
.sortedBy { it.int }
.map {
Category(it.int.toLong(), "", 0, 0)
}
LibraryGroup.BY_STATUS -> manga.keys.sorted().map { Category(it, "", 0, 0) }
else -> throw IllegalStateException("Invalid group type $groupType")
} }
return map to categories return manga to categories
} }
fun runSync() { fun runSync() {

View File

@ -5,11 +5,10 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.UpdateCategory import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
import eu.kanade.domain.category.model.CategoryUpdate import eu.kanade.domain.category.interactor.SetSortModeForCategory
import eu.kanade.domain.category.model.Category
import eu.kanade.tachiyomi.R 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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -31,7 +30,8 @@ import uy.kohesive.injekt.injectLazy
class LibrarySettingsSheet( class LibrarySettingsSheet(
router: Router, router: Router,
private val trackManager: TrackManager = Injekt.get(), 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, onGroupClickListener: (ExtendedNavigationView.Group) -> Unit,
) : TabbedBottomSheetDialog(router.activity!!) { ) : TabbedBottomSheetDialog(router.activity!!) {
@ -226,8 +226,8 @@ class LibrarySettingsSheet(
override val footer = null override val footer = null
override fun initModels() { override fun initModels() {
val sorting = SortModeSetting.get(preferences, currentCategory?.toDomainCategory()) val sorting = SortModeSetting.get(preferences, currentCategory)
val order = if (SortDirectionSetting.get(preferences, currentCategory?.toDomainCategory()) == SortDirectionSetting.ASCENDING) { val order = if (SortDirectionSetting.get(preferences, currentCategory) == SortDirectionSetting.ASCENDING) {
Item.MultiSort.SORT_ASC Item.MultiSort.SORT_ASC
} else { } else {
Item.MultiSort.SORT_DESC Item.MultiSort.SORT_DESC
@ -292,18 +292,8 @@ class LibrarySettingsSheet(
SortDirectionSetting.DESCENDING SortDirectionSetting.DESCENDING
} }
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0 /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT /* SY <-- */) { sheetScope.launchIO {
currentCategory?.sortDirection = flag.flag.toInt() setSortModeForCategory.await(currentCategory!!, flag)
sheetScope.launchIO {
updateCategory.await(
CategoryUpdate(
id = currentCategory!!.id?.toLong()!!,
flags = currentCategory!!.flags.toLong(),
),
)
}
} else {
preferences.librarySortingAscending().set(flag)
} }
} }
@ -323,18 +313,8 @@ class LibrarySettingsSheet(
else -> throw NotImplementedError("Unknown display mode") else -> throw NotImplementedError("Unknown display mode")
} }
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0 /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT /* SY <-- */) { sheetScope.launchIO {
currentCategory?.sortMode = flag.flag.toInt() setSortModeForCategory.await(currentCategory!!, flag)
sheetScope.launchIO {
updateCategory.await(
CategoryUpdate(
id = currentCategory!!.id?.toLong()!!,
flags = currentCategory!!.flags.toLong(),
),
)
}
} else {
preferences.librarySortingMode().set(flag)
} }
} }
} }
@ -374,8 +354,8 @@ class LibrarySettingsSheet(
// Gets user preference of currently selected display mode at current category // Gets user preference of currently selected display mode at current category
private fun getDisplayModePreference(): DisplayModeSetting { private fun getDisplayModePreference(): DisplayModeSetting {
return if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0 /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT /* SY <-- */) { return if (currentCategory != null && preferences.categorizedDisplaySettings().get()/* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT /* SY <-- */) {
DisplayModeSetting.fromFlag(currentCategory?.displayMode?.toLong()) DisplayModeSetting.fromFlag(currentCategory!!.displayMode)
} else { } else {
preferences.libraryDisplayMode().get() preferences.libraryDisplayMode().get()
} }
@ -426,18 +406,8 @@ class LibrarySettingsSheet(
else -> throw NotImplementedError("Unknown display mode") else -> throw NotImplementedError("Unknown display mode")
} }
if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0 /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT /* SY <-- */) { sheetScope.launchIO {
currentCategory?.displayMode = flag.flag.toInt() setDisplayModeForCategory.await(currentCategory!!, flag)
sheetScope.launchIO {
updateCategory.await(
CategoryUpdate(
id = currentCategory!!.id?.toLong()!!,
flags = currentCategory!!.flags.toLong(),
),
)
}
} else {
preferences.libraryDisplayMode().set(flag)
} }
} }
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library.setting
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.library.LibraryGroup
enum class SortModeSetting(val flag: Long) { enum class SortModeSetting(val flag: Long) {
ALPHABETICAL(0b00000000), ALPHABETICAL(0b00000000),
@ -38,7 +39,7 @@ enum class SortModeSetting(val flag: Long) {
} }
fun get(preferences: PreferencesHelper, category: Category?): SortModeSetting { fun get(preferences: PreferencesHelper, category: Category?): SortModeSetting {
return if (preferences.categorizedDisplaySettings().get() && category != null && category.id != 0L) { return if (category != null && preferences.categorizedDisplaySettings().get() /* SY --> */ && preferences.groupLibraryBy().get() == LibraryGroup.BY_DEFAULT/* SY <-- */) {
fromFlag(category.sortMode) fromFlag(category.sortMode)
} else { } else {
preferences.librarySortingMode().get() preferences.librarySortingMode().get()

View File

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

View File

@ -8,7 +8,9 @@ import androidx.core.text.buildSpannedString
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.ResetCategoryFlags
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW
@ -55,6 +57,7 @@ class SettingsLibraryController : SettingsController() {
private val getCategories: GetCategories by injectLazy() private val getCategories: GetCategories by injectLazy()
private val trackManager: TrackManager by injectLazy() private val trackManager: TrackManager by injectLazy()
private val resetCategoryFlags: ResetCategoryFlags by injectLazy()
// SY --> // SY -->
/** /**
@ -66,8 +69,8 @@ class SettingsLibraryController : SettingsController() {
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.pref_category_library titleRes = R.string.pref_category_library
val dbCategories = runBlocking { getCategories.await() } val allCategories = runBlocking { getCategories.await() }
val categories = listOf(Category.default(context)) + dbCategories val userCategories = allCategories.filterNot(Category::isSystemCategory)
preferenceCategory { preferenceCategory {
titleRes = R.string.pref_category_display titleRes = R.string.pref_category_display
@ -120,7 +123,7 @@ class SettingsLibraryController : SettingsController() {
key = "pref_action_edit_categories" key = "pref_action_edit_categories"
titleRes = R.string.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) summary = context.resources.getQuantityString(R.plurals.num_categories, catCount, catCount)
onClick { onClick {
@ -133,15 +136,15 @@ class SettingsLibraryController : SettingsController() {
titleRes = R.string.default_category titleRes = R.string.default_category
entries = arrayOf(context.getString(R.string.default_category_summary)) + entries = arrayOf(context.getString(R.string.default_category_summary)) +
categories.map { it.name }.toTypedArray() allCategories.map { it.visualName(context) }.toTypedArray()
entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray() entryValues = arrayOf("-1") + allCategories.map { it.id.toString() }.toTypedArray()
defaultValue = "-1" defaultValue = "-1"
val selectedCategory = categories.find { it.id == preferences.defaultCategory().toLong() } val selectedCategory = allCategories.find { it.id == preferences.defaultCategory().toLong() }
summary = selectedCategory?.name summary = selectedCategory?.name
?: context.getString(R.string.default_category_summary) ?: context.getString(R.string.default_category_summary)
onChange { newValue -> onChange { newValue ->
summary = categories.find { summary = allCategories.find {
it.id == (newValue as String).toLong() it.id == (newValue as String).toLong()
}?.name ?: context.getString(R.string.default_category_summary) }?.name ?: context.getString(R.string.default_category_summary)
true true
@ -151,6 +154,14 @@ class SettingsLibraryController : SettingsController() {
switchPreference { switchPreference {
bindTo(preferences.categorizedDisplaySettings()) bindTo(preferences.categorizedDisplaySettings())
titleRes = R.string.categorized_display_settings titleRes = R.string.categorized_display_settings
preferences.categorizedDisplaySettings().asFlow()
.onEach {
if (it.not()) {
resetCategoryFlags.await()
}
}
.launchIn(viewScope)
} }
} }
@ -255,19 +266,19 @@ class SettingsLibraryController : SettingsController() {
fun updateSummary() { fun updateSummary() {
val includedCategories = preferences.libraryUpdateCategories().get() val includedCategories = preferences.libraryUpdateCategories().get()
.mapNotNull { id -> categories.find { it.id == id.toLong() } } .mapNotNull { id -> allCategories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
val excludedCategories = preferences.libraryUpdateCategoriesExclude().get() val excludedCategories = preferences.libraryUpdateCategoriesExclude().get()
.mapNotNull { id -> categories.find { it.id == id.toLong() } } .mapNotNull { id -> allCategories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
val allExcluded = excludedCategories.size == categories.size val allExcluded = excludedCategories.size == allCategories.size
val includedItemsText = when { val includedItemsText = when {
// Some selected, but not all // 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 // 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) allExcluded -> context.getString(R.string.none)
else -> context.getString(R.string.all) else -> context.getString(R.string.all)
} }
@ -403,10 +414,9 @@ class SettingsLibraryController : SettingsController() {
private val getCategories: GetCategories = Injekt.get() private val getCategories: GetCategories = Injekt.get()
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dbCategories = runBlocking { getCategories.await() } val categories = runBlocking { getCategories.await() }
val categories = listOf(Category.default(activity!!)) + dbCategories
val items = categories.map { it.name } val items = categories.map { it.visualName(activity!!) }
var selected = categories var selected = categories
.map { .map {
when (it.id.toString()) { when (it.id.toString()) {

View File

@ -8,6 +8,17 @@ CREATE TABLE categories(
manga_order TEXT AS List<Long> NOT NULL manga_order TEXT AS List<Long> NOT NULL
); );
-- Insert system category
INSERT OR IGNORE INTO categories(_id, name, sort, flags, manga_order) 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: getCategories:
SELECT SELECT
_id AS id, _id AS id,
@ -43,5 +54,9 @@ SET name = coalesce(:name, name),
flags = coalesce(:flags, flags) flags = coalesce(:flags, flags)
WHERE _id = :categoryId; WHERE _id = :categoryId;
updateAllFlags:
UPDATE categories SET
flags = coalesce(?, flags);
selectLastInsertedRowId: selectLastInsertedRowId:
SELECT last_insert_rowid(); SELECT last_insert_rowid();

View File

@ -0,0 +1,10 @@
-- Insert Default category
INSERT OR IGNORE INTO categories(_id, name, sort, flags, manga_order) 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;