feat(category): Add ability to hide categories from library

This commit is contained in:
Jeffery Orazulike 2023-09-15 19:24:08 +01:00 committed by GitHub
parent 20bf2a95a4
commit b536adb8ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 528 additions and 98 deletions

View file

@ -49,6 +49,8 @@ import tachiyomi.data.updates.manga.MangaUpdatesRepositoryImpl
import tachiyomi.domain.category.anime.interactor.CreateAnimeCategoryWithName
import tachiyomi.domain.category.anime.interactor.DeleteAnimeCategory
import tachiyomi.domain.category.anime.interactor.GetAnimeCategories
import tachiyomi.domain.category.anime.interactor.GetVisibleAnimeCategories
import tachiyomi.domain.category.anime.interactor.HideAnimeCategory
import tachiyomi.domain.category.anime.interactor.RenameAnimeCategory
import tachiyomi.domain.category.anime.interactor.ReorderAnimeCategory
import tachiyomi.domain.category.anime.interactor.ResetAnimeCategoryFlags
@ -60,6 +62,8 @@ import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository
import tachiyomi.domain.category.manga.interactor.CreateMangaCategoryWithName
import tachiyomi.domain.category.manga.interactor.DeleteMangaCategory
import tachiyomi.domain.category.manga.interactor.GetMangaCategories
import tachiyomi.domain.category.manga.interactor.GetVisibleMangaCategories
import tachiyomi.domain.category.manga.interactor.HideMangaCategory
import tachiyomi.domain.category.manga.interactor.RenameMangaCategory
import tachiyomi.domain.category.manga.interactor.ReorderMangaCategory
import tachiyomi.domain.category.manga.interactor.ResetMangaCategoryFlags
@ -142,6 +146,7 @@ class DomainModule : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingletonFactory<AnimeCategoryRepository> { AnimeCategoryRepositoryImpl(get()) }
addFactory { GetAnimeCategories(get()) }
addFactory { GetVisibleAnimeCategories(get()) }
addFactory { ResetAnimeCategoryFlags(get(), get()) }
addFactory { SetDisplayModeForAnimeCategory(get(), get()) }
addFactory { SetSortModeForAnimeCategory(get(), get()) }
@ -149,10 +154,12 @@ class DomainModule : InjektModule {
addFactory { RenameAnimeCategory(get()) }
addFactory { ReorderAnimeCategory(get()) }
addFactory { UpdateAnimeCategory(get()) }
addFactory { HideAnimeCategory(get()) }
addFactory { DeleteAnimeCategory(get()) }
addSingletonFactory<MangaCategoryRepository> { MangaCategoryRepositoryImpl(get()) }
addFactory { GetMangaCategories(get()) }
addFactory { GetVisibleMangaCategories(get()) }
addFactory { ResetMangaCategoryFlags(get(), get()) }
addFactory { SetDisplayModeForMangaCategory(get(), get()) }
addFactory { SetSortModeForMangaCategory(get(), get()) }
@ -160,6 +167,7 @@ class DomainModule : InjektModule {
addFactory { RenameMangaCategory(get()) }
addFactory { ReorderMangaCategory(get()) }
addFactory { UpdateMangaCategory(get()) }
addFactory { HideMangaCategory(get()) }
addFactory { DeleteMangaCategory(get()) }
addSingletonFactory<AnimeRepository> { AnimeRepositoryImpl(get()) }

View file

@ -23,6 +23,7 @@ fun AnimeCategoryScreen(
contentPadding: PaddingValues,
onClickCreate: () -> Unit,
onClickRename: (Category) -> Unit,
onClickHide: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit,
@ -49,6 +50,7 @@ fun AnimeCategoryScreen(
lazyListState = lazyListState,
paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename,
onClickHide = onClickHide,
onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp,
onMoveDown = onClickMoveDown,

View file

@ -23,6 +23,7 @@ fun MangaCategoryScreen(
contentPadding: PaddingValues,
onClickCreate: () -> Unit,
onClickRename: (Category) -> Unit,
onClickHide: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit,
@ -49,6 +50,7 @@ fun MangaCategoryScreen(
lazyListState = lazyListState,
paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename,
onClickHide = onClickHide,
onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp,
onMoveDown = onClickMoveDown,

View file

@ -17,6 +17,7 @@ fun CategoryContent(
lazyListState: LazyListState,
paddingValues: PaddingValues,
onClickRename: (Category) -> Unit,
onClickHide: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onMoveUp: (Category) -> Unit,
onMoveDown: (Category) -> Unit,
@ -38,6 +39,7 @@ fun CategoryContent(
onMoveUp = onMoveUp,
onMoveDown = onMoveDown,
onRename = { onClickRename(category) },
onHide = { onClickHide(category) },
onDelete = { onClickDelete(category) },
)
}

View file

@ -11,6 +11,8 @@ import androidx.compose.material.icons.outlined.ArrowDropUp
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Label
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@ -33,6 +35,7 @@ fun CategoryListItem(
onMoveUp: (Category) -> Unit,
onMoveDown: (Category) -> Unit,
onRename: () -> Unit,
onHide: () -> Unit,
onDelete: () -> Unit,
) {
ElevatedCard(
@ -71,10 +74,29 @@ fun CategoryListItem(
}
Spacer(modifier = Modifier.weight(1f))
IconButton(onClick = onRename) {
Icon(imageVector = Icons.Outlined.Edit, contentDescription = stringResource(R.string.action_rename_category))
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = stringResource(R.string.action_rename_category),
)
}
IconButton(
onClick = onHide,
content = {
Icon(
imageVector = if (category.hidden) {
Icons.Outlined.Visibility
} else {
Icons.Outlined.VisibilityOff
},
contentDescription = stringResource(R.string.action_hide),
)
},
)
IconButton(onClick = onDelete) {
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete))
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = stringResource(R.string.action_delete),
)
}
}
}

View file

@ -54,13 +54,20 @@ object SettingsLibraryScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetMangaCategories>() }
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
val allCategories by getCategories.subscribe()
.collectAsState(initial = runBlocking { getCategories.await() })
val getAnimeCategories = remember { Injekt.get<GetAnimeCategories>() }
val allAnimeCategories by getAnimeCategories.subscribe().collectAsState(initial = runBlocking { getAnimeCategories.await() })
val allAnimeCategories by getAnimeCategories.subscribe()
.collectAsState(initial = runBlocking { getAnimeCategories.await() })
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
return listOf(
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, allAnimeCategories, libraryPreferences),
getCategoriesGroup(
LocalNavigator.currentOrThrow,
allCategories,
allAnimeCategories,
libraryPreferences,
),
getGlobalUpdateGroup(allCategories, allAnimeCategories, libraryPreferences),
getChapterSwipeActionsGroup(libraryPreferences),
getEpisodeSwipeActionsGroup(libraryPreferences),
@ -82,7 +89,8 @@ object SettingsLibraryScreen : SearchableSettings {
val defaultCategory by libraryPreferences.defaultMangaCategory().collectAsState()
val selectedCategory = allCategories.find { it.id == defaultCategory.toLong() }
val defaultAnimeCategory by libraryPreferences.defaultAnimeCategory().collectAsState()
val selectedAnimeCategory = allAnimeCategories.find { it.id == defaultAnimeCategory.toLong() }
val selectedAnimeCategory =
allAnimeCategories.find { it.id == defaultAnimeCategory.toLong() }
// For default category
val mangaIds = listOf(libraryPreferences.defaultMangaCategory().defaultValue()) +
@ -110,7 +118,8 @@ object SettingsLibraryScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultAnimeCategory(),
title = stringResource(R.string.default_anime_category),
subtitle = selectedAnimeCategory?.visualName ?: stringResource(R.string.default_category_summary),
subtitle = selectedAnimeCategory?.visualName
?: stringResource(R.string.default_category_summary),
entries = animeIds.zip(animeLabels).toMap(),
),
Preference.PreferenceItem.TextPreference(
@ -125,7 +134,8 @@ object SettingsLibraryScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultMangaCategory(),
title = stringResource(R.string.default_manga_category),
subtitle = selectedCategory?.visualName ?: stringResource(R.string.default_category_summary),
subtitle = selectedCategory?.visualName
?: stringResource(R.string.default_category_summary),
entries = mangaIds.zip(mangaLabels).toMap(),
),
Preference.PreferenceItem.SwitchPreference(
@ -140,6 +150,10 @@ object SettingsLibraryScreen : SearchableSettings {
true
},
),
Preference.PreferenceItem.SwitchPreference(
pref = libraryPreferences.hideHiddenCategoriesSettings(),
title = stringResource(R.string.pref_category_hide_hidden),
),
),
)
}
@ -158,7 +172,8 @@ object SettingsLibraryScreen : SearchableSettings {
val libraryUpdateMangaRestrictionPref = libraryPreferences.libraryUpdateItemRestriction()
val animelibUpdateCategoriesPref = libraryPreferences.animeLibraryUpdateCategories()
val animelibUpdateCategoriesExcludePref = libraryPreferences.animeLibraryUpdateCategoriesExclude()
val animelibUpdateCategoriesExcludePref =
libraryPreferences.animeLibraryUpdateCategoriesExclude()
val includedAnime by animelibUpdateCategoriesPref.collectAsState()
val excludedAnime by animelibUpdateCategoriesExcludePref.collectAsState()
@ -174,14 +189,18 @@ object SettingsLibraryScreen : SearchableSettings {
onDismissRequest = { showAnimeDialog = false },
onValueChanged = { newIncluded, newExcluded ->
animelibUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
animelibUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
animelibUpdateCategoriesExcludePref.set(
newExcluded.map { it.id.toString() }
.toSet(),
)
showAnimeDialog = false
},
)
}
val libraryUpdateCategoriesPref = libraryPreferences.mangaLibraryUpdateCategories()
val libraryUpdateCategoriesExcludePref = libraryPreferences.mangaLibraryUpdateCategoriesExclude()
val libraryUpdateCategoriesExcludePref =
libraryPreferences.mangaLibraryUpdateCategoriesExclude()
val includedManga by libraryUpdateCategoriesPref.collectAsState()
val excludedManga by libraryUpdateCategoriesExcludePref.collectAsState()
@ -197,7 +216,10 @@ object SettingsLibraryScreen : SearchableSettings {
onDismissRequest = { showMangaDialog = false },
onValueChanged = { newIncluded, newExcluded ->
libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
libraryUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
libraryUpdateCategoriesExcludePref.set(
newExcluded.map { it.id.toString() }
.toSet(),
)
showMangaDialog = false
},
)

View file

@ -18,6 +18,7 @@ class BackupCategory(
name = this@BackupCategory.name,
flags = this@BackupCategory.flags,
order = this@BackupCategory.order,
hidden = false,
)
}
}

View file

@ -13,18 +13,24 @@ import kotlinx.coroutines.launch
import tachiyomi.domain.category.anime.interactor.CreateAnimeCategoryWithName
import tachiyomi.domain.category.anime.interactor.DeleteAnimeCategory
import tachiyomi.domain.category.anime.interactor.GetAnimeCategories
import tachiyomi.domain.category.anime.interactor.GetVisibleAnimeCategories
import tachiyomi.domain.category.anime.interactor.HideAnimeCategory
import tachiyomi.domain.category.anime.interactor.RenameAnimeCategory
import tachiyomi.domain.category.anime.interactor.ReorderAnimeCategory
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class AnimeCategoryScreenModel(
private val getCategories: GetAnimeCategories = Injekt.get(),
private val getAllCategories: GetAnimeCategories = Injekt.get(),
private val getVisibleCategories: GetVisibleAnimeCategories = Injekt.get(),
private val createCategoryWithName: CreateAnimeCategoryWithName = Injekt.get(),
private val hideCategory: HideAnimeCategory = Injekt.get(),
private val deleteCategory: DeleteAnimeCategory = Injekt.get(),
private val reorderCategory: ReorderAnimeCategory = Injekt.get(),
private val renameCategory: RenameAnimeCategory = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
) : StateScreenModel<AnimeCategoryScreenState>(AnimeCategoryScreenState.Loading) {
private val _events: Channel<AnimeCategoryEvent> = Channel()
@ -32,8 +38,13 @@ class AnimeCategoryScreenModel(
init {
coroutineScope.launch {
getCategories.subscribe()
.collectLatest { categories ->
val allCategories = if (libraryPreferences.hideHiddenCategoriesSettings().get()) {
getVisibleCategories.subscribe()
} else {
getAllCategories.subscribe()
}
allCategories.collectLatest { categories ->
mutableState.update {
AnimeCategoryScreenState.Success(
categories = categories.filterNot(Category::isSystemCategory),
@ -46,7 +57,19 @@ class AnimeCategoryScreenModel(
fun createCategory(name: String) {
coroutineScope.launch {
when (createCategoryWithName.await(name)) {
is CreateAnimeCategoryWithName.Result.InternalError -> _events.send(AnimeCategoryEvent.InternalError)
is CreateAnimeCategoryWithName.Result.InternalError -> _events.send(
AnimeCategoryEvent.InternalError,
)
else -> {}
}
}
}
fun hideCategory(category: Category) {
coroutineScope.launch {
when (hideCategory.await(category)) {
is HideAnimeCategory.Result.InternalError -> _events.send(AnimeCategoryEvent.InternalError)
else -> {}
}
}

View file

@ -36,6 +36,7 @@ fun Screen.animeCategoryTab(): TabContent {
contentPadding = contentPadding,
onClickCreate = { screenModel.showDialog(AnimeCategoryDialog.Create) },
onClickRename = { screenModel.showDialog(AnimeCategoryDialog.Rename(it)) },
onClickHide = screenModel::hideCategory,
onClickDelete = { screenModel.showDialog(AnimeCategoryDialog.Delete(it)) },
onClickMoveUp = screenModel::moveUp,
onClickMoveDown = screenModel::moveDown,

View file

@ -13,18 +13,24 @@ import kotlinx.coroutines.launch
import tachiyomi.domain.category.manga.interactor.CreateMangaCategoryWithName
import tachiyomi.domain.category.manga.interactor.DeleteMangaCategory
import tachiyomi.domain.category.manga.interactor.GetMangaCategories
import tachiyomi.domain.category.manga.interactor.GetVisibleMangaCategories
import tachiyomi.domain.category.manga.interactor.HideMangaCategory
import tachiyomi.domain.category.manga.interactor.RenameMangaCategory
import tachiyomi.domain.category.manga.interactor.ReorderMangaCategory
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MangaCategoryScreenModel(
private val getCategories: GetMangaCategories = Injekt.get(),
private val getAllCategories: GetMangaCategories = Injekt.get(),
private val getVisibleCategories: GetVisibleMangaCategories = Injekt.get(),
private val createCategoryWithName: CreateMangaCategoryWithName = Injekt.get(),
private val hideCategory: HideMangaCategory = Injekt.get(),
private val deleteCategory: DeleteMangaCategory = Injekt.get(),
private val reorderCategory: ReorderMangaCategory = Injekt.get(),
private val renameCategory: RenameMangaCategory = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
) : StateScreenModel<MangaCategoryScreenState>(MangaCategoryScreenState.Loading) {
private val _events: Channel<MangaCategoryEvent> = Channel()
@ -32,8 +38,13 @@ class MangaCategoryScreenModel(
init {
coroutineScope.launch {
getCategories.subscribe()
.collectLatest { categories ->
val allCategories = if (libraryPreferences.hideHiddenCategoriesSettings().get()) {
getVisibleCategories.subscribe()
} else {
getAllCategories.subscribe()
}
allCategories.collectLatest { categories ->
mutableState.update {
MangaCategoryScreenState.Success(
categories = categories.filterNot(Category::isSystemCategory),
@ -46,7 +57,19 @@ class MangaCategoryScreenModel(
fun createCategory(name: String) {
coroutineScope.launch {
when (createCategoryWithName.await(name)) {
is CreateMangaCategoryWithName.Result.InternalError -> _events.send(MangaCategoryEvent.InternalError)
is CreateMangaCategoryWithName.Result.InternalError -> _events.send(
MangaCategoryEvent.InternalError,
)
else -> {}
}
}
}
fun hideCategory(category: Category) {
coroutineScope.launch {
when (hideCategory.await(category)) {
is HideMangaCategory.Result.InternalError -> _events.send(MangaCategoryEvent.InternalError)
else -> {}
}
}

View file

@ -37,6 +37,7 @@ fun Screen.mangaCategoryTab(): TabContent {
contentPadding = contentPadding,
onClickCreate = { screenModel.showDialog(MangaCategoryDialog.Create) },
onClickRename = { screenModel.showDialog(MangaCategoryDialog.Rename(it)) },
onClickHide = screenModel::hideCategory,
onClickDelete = { screenModel.showDialog(MangaCategoryDialog.Delete(it)) },
onClickMoveUp = screenModel::moveUp,
onClickMoveDown = screenModel::moveDown,

View file

@ -44,7 +44,7 @@ import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.domain.category.anime.interactor.GetAnimeCategories
import tachiyomi.domain.category.anime.interactor.GetVisibleAnimeCategories
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.entries.TriStateFilter
@ -75,7 +75,7 @@ typealias AnimeLibraryMap = Map<Category, List<AnimeLibraryItem>>
class AnimeLibraryScreenModel(
private val getLibraryAnime: GetLibraryAnime = Injekt.get(),
private val getCategories: GetAnimeCategories = Injekt.get(),
private val getCategories: GetVisibleAnimeCategories = Injekt.get(),
private val getTracksPerAnime: GetTracksPerAnime = Injekt.get(),
private val getNextEpisodes: GetNextEpisodes = Injekt.get(),
private val getEpisodesByAnimeId: GetEpisodeByAnimeId = Injekt.get(),

View file

@ -44,7 +44,7 @@ import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.domain.category.manga.interactor.GetMangaCategories
import tachiyomi.domain.category.manga.interactor.GetVisibleMangaCategories
import tachiyomi.domain.category.manga.interactor.SetMangaCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.entries.TriStateFilter
@ -75,7 +75,7 @@ typealias MangaLibraryMap = Map<Category, List<MangaLibraryItem>>
class MangaLibraryScreenModel(
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getCategories: GetMangaCategories = Injekt.get(),
private val getCategories: GetVisibleMangaCategories = Injekt.get(),
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
private val getNextChapters: GetNextChapters = Injekt.get(),
private val getChaptersByMangaId: GetChapterByMangaId = Injekt.get(),

View file

@ -2,11 +2,13 @@ package tachiyomi.data.category
import tachiyomi.domain.category.model.Category
val categoryMapper: (Long, String, Long, Long) -> Category = { id, name, order, flags ->
val categoryMapper: (Long, String, Long, Long, Long) -> Category =
{ id, name, order, flags, hidden ->
Category(
id = id,
name = name,
order = order,
flags = flags,
hidden = hidden == 1L,
)
}
}

View file

@ -20,22 +20,42 @@ class AnimeCategoryRepositoryImpl(
return handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
}
override suspend fun getAllVisibleAnimeCategories(): List<Category> {
return handler.awaitList { categoriesQueries.getVisibleCategories(categoryMapper) }
}
override fun getAllAnimeCategoriesAsFlow(): Flow<List<Category>> {
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
}
override fun getAllVisibleAnimeCategoriesAsFlow(): Flow<List<Category>> {
return handler.subscribeToList { categoriesQueries.getVisibleCategories(categoryMapper) }
}
override suspend fun getCategoriesByAnimeId(animeId: Long): List<Category> {
return handler.awaitList {
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
}
}
override suspend fun getVisibleCategoriesByAnimeId(animeId: Long): List<Category> {
return handler.awaitList {
categoriesQueries.getVisibleCategoriesByAnimeId(animeId, categoryMapper)
}
}
override fun getCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>> {
return handler.subscribeToList {
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
}
}
override fun getVisibleCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>> {
return handler.subscribeToList {
categoriesQueries.getVisibleCategoriesByAnimeId(animeId, categoryMapper)
}
}
override suspend fun insertAnimeCategory(category: Category) {
handler.await {
categoriesQueries.insert(
@ -65,6 +85,7 @@ class AnimeCategoryRepositoryImpl(
name = update.name,
order = update.order,
flags = update.flags,
hidden = if (update.hidden == true) 1L else 0L,
categoryId = update.id,
)
}

View file

@ -20,22 +20,42 @@ class MangaCategoryRepositoryImpl(
return handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
}
override suspend fun getAllVisibleMangaCategories(): List<Category> {
return handler.awaitList { categoriesQueries.getVisibleCategories(categoryMapper) }
}
override fun getAllMangaCategoriesAsFlow(): Flow<List<Category>> {
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
}
override fun getAllVisibleMangaCategoriesAsFlow(): Flow<List<Category>> {
return handler.subscribeToList { categoriesQueries.getVisibleCategories(categoryMapper) }
}
override suspend fun getCategoriesByMangaId(mangaId: Long): List<Category> {
return handler.awaitList {
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
}
}
override suspend fun getVisibleCategoriesByMangaId(mangaId: Long): List<Category> {
return handler.awaitList {
categoriesQueries.getVisibleCategoriesByMangaId(mangaId, categoryMapper)
}
}
override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>> {
return handler.subscribeToList {
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
}
}
override fun getVisibleCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>> {
return handler.subscribeToList {
categoriesQueries.getVisibleCategoriesByMangaId(mangaId, categoryMapper)
}
}
override suspend fun insertMangaCategory(category: Category) {
handler.await {
categoriesQueries.insert(
@ -65,6 +85,7 @@ class MangaCategoryRepositoryImpl(
name = update.name,
order = update.order,
flags = update.flags,
hidden = if (update.hidden == true) 1L else 0L,
categoryId = update.id,
)
}

View file

@ -2,7 +2,8 @@ CREATE TABLE categories(
_id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
sort INTEGER NOT NULL,
flags INTEGER NOT NULL
flags INTEGER NOT NULL,
hidden INTEGER NOT NULL DEFAULT 0
);
-- Insert system category
@ -27,21 +28,46 @@ SELECT
_id AS id,
name,
sort AS `order`,
flags
flags,
hidden
FROM categories
ORDER BY sort;
getVisibleCategories:
SELECT
_id AS id,
name,
sort AS `order`,
flags,
hidden
FROM categories
WHERE hidden = 0
ORDER BY sort;
getCategoriesByMangaId:
SELECT
C._id AS id,
C.name,
C.sort AS `order`,
C.flags
C.flags,
C.hidden
FROM categories C
JOIN mangas_categories MC
ON C._id = MC.category_id
WHERE MC.manga_id = :mangaId;
getVisibleCategoriesByMangaId:
SELECT
C._id AS id,
C.name,
C.sort AS `order`,
C.flags,
C.hidden
FROM categories C
JOIN mangas_categories MC
ON C._id = MC.category_id
WHERE MC.manga_id = :mangaId AND C.hidden = 0;
insert:
INSERT INTO categories(name, sort, flags)
VALUES (:name, :order, :flags);
@ -54,7 +80,8 @@ update:
UPDATE categories
SET name = coalesce(:name, name),
sort = coalesce(:order, sort),
flags = coalesce(:flags, flags)
flags = coalesce(:flags, flags),
hidden = coalesce(:hidden, hidden)
WHERE _id = :categoryId;
updateAllFlags:

View file

@ -0,0 +1 @@
ALTER TABLE categories ADD COLUMN hidden INTEGER DEFAULT 0 NOT NULL;

View file

@ -2,7 +2,8 @@ CREATE TABLE categories(
_id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
sort INTEGER NOT NULL,
flags INTEGER NOT NULL
flags INTEGER NOT NULL,
hidden INTEGER NOT NULL DEFAULT 0
);
-- Insert system category
@ -27,21 +28,46 @@ SELECT
_id AS id,
name,
sort AS `order`,
flags
flags,
hidden
FROM categories
ORDER BY sort;
getVisibleCategories:
SELECT
_id AS id,
name,
sort AS `order`,
flags,
hidden
FROM categories
WHERE hidden = 0
ORDER BY sort;
getCategoriesByAnimeId:
SELECT
C._id AS id,
C.name,
C.sort AS `order`,
C.flags
C.flags,
C.hidden
FROM categories C
JOIN animes_categories AC
ON C._id = AC.category_id
WHERE AC.anime_id = :animeId;
getVisibleCategoriesByAnimeId:
SELECT
C._id AS id,
C.name,
C.sort AS `order`,
C.flags,
C.hidden
FROM categories C
JOIN animes_categories AC
ON C._id = AC.category_id
WHERE AC.anime_id = :animeId AND C.hidden = 0;
insert:
INSERT INTO categories(name, sort, flags)
VALUES (:name, :order, :flags);
@ -54,7 +80,8 @@ update:
UPDATE categories
SET name = coalesce(:name, name),
sort = coalesce(:order, sort),
flags = coalesce(:flags, flags)
flags = coalesce(:flags, flags),
hidden = coalesce(:hidden, hidden)
WHERE _id = :categoryId;
updateAllFlags:

View file

@ -0,0 +1 @@
ALTER TABLE categories ADD COLUMN hidden INTEGER DEFAULT 0 NOT NULL;

View file

@ -28,6 +28,7 @@ class CreateAnimeCategoryWithName(
name = name,
order = nextOrder,
flags = initialFlags,
hidden = false,
)
try {

View file

@ -0,0 +1,25 @@
package tachiyomi.domain.category.anime.interactor
import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository
import tachiyomi.domain.category.model.Category
class GetVisibleAnimeCategories(
private val categoryRepository: AnimeCategoryRepository,
) {
fun subscribe(): Flow<List<Category>> {
return categoryRepository.getAllVisibleAnimeCategoriesAsFlow()
}
fun subscribe(animeId: Long): Flow<List<Category>> {
return categoryRepository.getVisibleCategoriesByAnimeIdAsFlow(animeId)
}
suspend fun await(): List<Category> {
return categoryRepository.getAllVisibleAnimeCategories()
}
suspend fun await(animeId: Long): List<Category> {
return categoryRepository.getVisibleCategoriesByAnimeId(animeId)
}
}

View file

@ -0,0 +1,33 @@
package tachiyomi.domain.category.anime.interactor
import logcat.LogPriority
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.model.CategoryUpdate
class HideAnimeCategory(
private val categoryRepository: AnimeCategoryRepository,
) {
suspend fun await(category: Category) = withNonCancellableContext {
val update = CategoryUpdate(
id = category.id,
hidden = !category.hidden,
)
try {
categoryRepository.updatePartialAnimeCategory(update)
RenameAnimeCategory.Result.Success
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Result.InternalError(e)
}
}
sealed class Result {
object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View file

@ -10,12 +10,20 @@ interface AnimeCategoryRepository {
suspend fun getAllAnimeCategories(): List<Category>
suspend fun getAllVisibleAnimeCategories(): List<Category>
fun getAllAnimeCategoriesAsFlow(): Flow<List<Category>>
fun getAllVisibleAnimeCategoriesAsFlow(): Flow<List<Category>>
suspend fun getCategoriesByAnimeId(animeId: Long): List<Category>
suspend fun getVisibleCategoriesByAnimeId(animeId: Long): List<Category>
fun getCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>>
fun getVisibleCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>>
suspend fun insertAnimeCategory(category: Category)
suspend fun updatePartialAnimeCategory(update: CategoryUpdate)

View file

@ -28,6 +28,7 @@ class CreateMangaCategoryWithName(
name = name,
order = nextOrder,
flags = initialFlags,
hidden = false,
)
try {

View file

@ -7,7 +7,6 @@ import tachiyomi.domain.category.model.Category
class GetMangaCategories(
private val categoryRepository: MangaCategoryRepository,
) {
fun subscribe(): Flow<List<Category>> {
return categoryRepository.getAllMangaCategoriesAsFlow()
}

View file

@ -0,0 +1,25 @@
package tachiyomi.domain.category.manga.interactor
import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.category.manga.repository.MangaCategoryRepository
import tachiyomi.domain.category.model.Category
class GetVisibleMangaCategories(
private val categoryRepository: MangaCategoryRepository,
) {
fun subscribe(): Flow<List<Category>> {
return categoryRepository.getAllVisibleMangaCategoriesAsFlow()
}
fun subscribe(mangaId: Long): Flow<List<Category>> {
return categoryRepository.getVisibleCategoriesByMangaIdAsFlow(mangaId)
}
suspend fun await(): List<Category> {
return categoryRepository.getAllVisibleMangaCategories()
}
suspend fun await(mangaId: Long): List<Category> {
return categoryRepository.getVisibleCategoriesByMangaId(mangaId)
}
}

View file

@ -0,0 +1,33 @@
package tachiyomi.domain.category.manga.interactor
import logcat.LogPriority
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.manga.repository.MangaCategoryRepository
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.category.model.CategoryUpdate
class HideMangaCategory(
private val categoryRepository: MangaCategoryRepository,
) {
suspend fun await(category: Category) = withNonCancellableContext {
val update = CategoryUpdate(
id = category.id,
hidden = !category.hidden,
)
try {
categoryRepository.updatePartialMangaCategory(update)
Result.Success
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Result.InternalError(e)
}
}
sealed class Result {
object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View file

@ -10,12 +10,20 @@ interface MangaCategoryRepository {
suspend fun getAllMangaCategories(): List<Category>
suspend fun getAllVisibleMangaCategories(): List<Category>
fun getAllMangaCategoriesAsFlow(): Flow<List<Category>>
fun getAllVisibleMangaCategoriesAsFlow(): Flow<List<Category>>
suspend fun getCategoriesByMangaId(mangaId: Long): List<Category>
suspend fun getVisibleCategoriesByMangaId(mangaId: Long): List<Category>
fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>>
fun getVisibleCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>>
suspend fun insertMangaCategory(category: Category)
suspend fun updatePartialMangaCategory(update: CategoryUpdate)

View file

@ -7,6 +7,7 @@ data class Category(
val name: String,
val order: Long,
val flags: Long,
val hidden: Boolean,
) : Serializable {
val isSystemCategory: Boolean = id == UNCATEGORIZED_ID

View file

@ -5,4 +5,5 @@ data class CategoryUpdate(
val name: String? = null,
val order: Long? = null,
val flags: Long? = null,
val hidden: Boolean? = null,
)

View file

@ -17,13 +17,29 @@ class LibraryPreferences(
fun bottomNavStyle() = preferenceStore.getInt("bottom_nav_style", 0)
fun isDefaultHomeTabLibraryManga() = preferenceStore.getBoolean("default_home_tab_library", false)
fun isDefaultHomeTabLibraryManga() =
preferenceStore.getBoolean("default_home_tab_library", false)
fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
fun libraryDisplayMode() = preferenceStore.getObject(
"pref_display_mode_library",
LibraryDisplayMode.default,
LibraryDisplayMode.Serializer::serialize,
LibraryDisplayMode.Serializer::deserialize,
)
fun libraryMangaSortingMode() = preferenceStore.getObject("library_sorting_mode", MangaLibrarySort.default, MangaLibrarySort.Serializer::serialize, MangaLibrarySort.Serializer::deserialize)
fun libraryMangaSortingMode() = preferenceStore.getObject(
"library_sorting_mode",
MangaLibrarySort.default,
MangaLibrarySort.Serializer::serialize,
MangaLibrarySort.Serializer::deserialize,
)
fun libraryAnimeSortingMode() = preferenceStore.getObject("animelib_sorting_mode", AnimeLibrarySort.default, AnimeLibrarySort.Serializer::serialize, AnimeLibrarySort.Serializer::deserialize)
fun libraryAnimeSortingMode() = preferenceStore.getObject(
"animelib_sorting_mode",
AnimeLibrarySort.default,
AnimeLibrarySort.Serializer::serialize,
AnimeLibrarySort.Serializer::deserialize,
)
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
@ -35,6 +51,7 @@ class LibraryPreferences(
DEVICE_ONLY_ON_WIFI,
),
)
fun libraryUpdateItemRestriction() = preferenceStore.getStringSet(
"library_update_manga_restriction",
setOf(
@ -48,7 +65,8 @@ class LibraryPreferences(
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
fun showContinueViewingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
fun showContinueViewingButton() =
preferenceStore.getBoolean("display_continue_reading_button", false)
// Common Category
@ -58,6 +76,8 @@ class LibraryPreferences(
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
fun hideHiddenCategoriesSettings() = preferenceStore.getBoolean("hidden_categories", false)
// Common badges
fun downloadBadge() = preferenceStore.getBoolean("display_download_badge", false)
@ -82,23 +102,41 @@ class LibraryPreferences(
// Mixture Filter
fun filterDownloadedAnime() = preferenceStore.getEnum("pref_filter_animelib_downloaded_v2", TriStateFilter.DISABLED)
fun filterDownloadedManga() = preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriStateFilter.DISABLED)
fun filterDownloadedAnime() =
preferenceStore.getEnum("pref_filter_animelib_downloaded_v2", TriStateFilter.DISABLED)
fun filterUnseen() = preferenceStore.getEnum("pref_filter_animelib_unread_v2", TriStateFilter.DISABLED)
fun filterUnread() = preferenceStore.getEnum("pref_filter_library_unread_v2", TriStateFilter.DISABLED)
fun filterDownloadedManga() =
preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriStateFilter.DISABLED)
fun filterStartedAnime() = preferenceStore.getEnum("pref_filter_animelib_started_v2", TriStateFilter.DISABLED)
fun filterStartedManga() = preferenceStore.getEnum("pref_filter_library_started_v2", TriStateFilter.DISABLED)
fun filterUnseen() =
preferenceStore.getEnum("pref_filter_animelib_unread_v2", TriStateFilter.DISABLED)
fun filterBookmarkedAnime() = preferenceStore.getEnum("pref_filter_animelib_bookmarked_v2", TriStateFilter.DISABLED)
fun filterBookmarkedManga() = preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriStateFilter.DISABLED)
fun filterUnread() =
preferenceStore.getEnum("pref_filter_library_unread_v2", TriStateFilter.DISABLED)
fun filterCompletedAnime() = preferenceStore.getEnum("pref_filter_animelib_completed_v2", TriStateFilter.DISABLED)
fun filterCompletedManga() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED)
fun filterStartedAnime() =
preferenceStore.getEnum("pref_filter_animelib_started_v2", TriStateFilter.DISABLED)
fun filterTrackedAnime(id: Int) = preferenceStore.getEnum("pref_filter_animelib_tracked_${id}_v2", TriStateFilter.DISABLED)
fun filterTrackedManga(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED)
fun filterStartedManga() =
preferenceStore.getEnum("pref_filter_library_started_v2", TriStateFilter.DISABLED)
fun filterBookmarkedAnime() =
preferenceStore.getEnum("pref_filter_animelib_bookmarked_v2", TriStateFilter.DISABLED)
fun filterBookmarkedManga() =
preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriStateFilter.DISABLED)
fun filterCompletedAnime() =
preferenceStore.getEnum("pref_filter_animelib_completed_v2", TriStateFilter.DISABLED)
fun filterCompletedManga() =
preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED)
fun filterTrackedAnime(id: Int) =
preferenceStore.getEnum("pref_filter_animelib_tracked_${id}_v2", TriStateFilter.DISABLED)
fun filterTrackedManga(id: Int) =
preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED)
// Mixture Update Count
@ -113,32 +151,68 @@ class LibraryPreferences(
fun lastUsedAnimeCategory() = preferenceStore.getInt("last_used_anime_category", 0)
fun lastUsedMangaCategory() = preferenceStore.getInt("last_used_category", 0)
fun animeLibraryUpdateCategories() = preferenceStore.getStringSet("animelib_update_categories", emptySet())
fun mangaLibraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
fun animeLibraryUpdateCategories() =
preferenceStore.getStringSet("animelib_update_categories", emptySet())
fun animeLibraryUpdateCategoriesExclude() = preferenceStore.getStringSet("animelib_update_categories_exclude", emptySet())
fun mangaLibraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
fun mangaLibraryUpdateCategories() =
preferenceStore.getStringSet("library_update_categories", emptySet())
fun animeLibraryUpdateCategoriesExclude() =
preferenceStore.getStringSet("animelib_update_categories_exclude", emptySet())
fun mangaLibraryUpdateCategoriesExclude() =
preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
// Mixture Item
fun filterEpisodeBySeen() = preferenceStore.getLong("default_episode_filter_by_seen", Anime.SHOW_ALL)
fun filterChapterByRead() = preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
fun filterEpisodeBySeen() =
preferenceStore.getLong("default_episode_filter_by_seen", Anime.SHOW_ALL)
fun filterEpisodeByDownloaded() = preferenceStore.getLong("default_episode_filter_by_downloaded", Anime.SHOW_ALL)
fun filterChapterByDownloaded() = preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
fun filterChapterByRead() =
preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
fun filterEpisodeByBookmarked() = preferenceStore.getLong("default_episode_filter_by_bookmarked", Anime.SHOW_ALL)
fun filterChapterByBookmarked() = preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
fun filterEpisodeByDownloaded() =
preferenceStore.getLong("default_episode_filter_by_downloaded", Anime.SHOW_ALL)
fun filterChapterByDownloaded() =
preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
fun filterEpisodeByBookmarked() =
preferenceStore.getLong("default_episode_filter_by_bookmarked", Anime.SHOW_ALL)
fun filterChapterByBookmarked() =
preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
// and upload date
fun sortEpisodeBySourceOrNumber() = preferenceStore.getLong("default_episode_sort_by_source_or_number", Anime.EPISODE_SORTING_SOURCE)
fun sortChapterBySourceOrNumber() = preferenceStore.getLong("default_chapter_sort_by_source_or_number", Manga.CHAPTER_SORTING_SOURCE)
fun sortEpisodeBySourceOrNumber() = preferenceStore.getLong(
"default_episode_sort_by_source_or_number",
Anime.EPISODE_SORTING_SOURCE,
)
fun displayEpisodeByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Anime.EPISODE_DISPLAY_NAME)
fun displayChapterByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Manga.CHAPTER_DISPLAY_NAME)
fun sortChapterBySourceOrNumber() = preferenceStore.getLong(
"default_chapter_sort_by_source_or_number",
Manga.CHAPTER_SORTING_SOURCE,
)
fun sortEpisodeByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Anime.EPISODE_SORT_DESC)
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Manga.CHAPTER_SORT_DESC)
fun displayEpisodeByNameOrNumber() = preferenceStore.getLong(
"default_chapter_display_by_name_or_number",
Anime.EPISODE_DISPLAY_NAME,
)
fun displayChapterByNameOrNumber() = preferenceStore.getLong(
"default_chapter_display_by_name_or_number",
Manga.CHAPTER_DISPLAY_NAME,
)
fun sortEpisodeByAscendingOrDescending() = preferenceStore.getLong(
"default_chapter_sort_by_ascending_or_descending",
Anime.EPISODE_SORT_DESC,
)
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong(
"default_chapter_sort_by_ascending_or_descending",
Manga.CHAPTER_SORT_DESC,
)
fun setEpisodeSettingsDefault(anime: Anime) {
filterEpisodeBySeen().set(anime.unseenFilterRaw)
@ -160,13 +234,21 @@ class LibraryPreferences(
// region Swipe Actions
fun swipeEpisodeEndAction() = preferenceStore.getEnum("pref_episode_swipe_start_action", EpisodeSwipeAction.ToggleBookmark)
fun swipeEpisodeEndAction() = preferenceStore.getEnum(
"pref_episode_swipe_start_action",
EpisodeSwipeAction.ToggleBookmark,
)
fun swipeEpisodeStartAction() = preferenceStore.getEnum("pref_episode_swipe_end_action", EpisodeSwipeAction.ToggleSeen)
fun swipeEpisodeStartAction() =
preferenceStore.getEnum("pref_episode_swipe_end_action", EpisodeSwipeAction.ToggleSeen)
fun swipeChapterEndAction() = preferenceStore.getEnum("pref_chapter_swipe_start_action", ChapterSwipeAction.ToggleBookmark)
fun swipeChapterEndAction() = preferenceStore.getEnum(
"pref_chapter_swipe_start_action",
ChapterSwipeAction.ToggleBookmark,
)
fun swipeChapterStartAction() = preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleRead)
fun swipeChapterStartAction() =
preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleRead)
// endregion

View file

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Actions -->
<string name="action_hide">Hide</string>
<string name="manga_categories">Manga Categories</string>
<string name="general_categories">Categories</string>
<string name="anime_categories">Anime Categories</string>
@ -308,6 +311,9 @@
<string name="pref_episode_swipe">Episode swipe</string>
<string name="pref_episode_swipe_end">Swipe to right action</string>
<string name="pref_episode_swipe_start">Swipe to left action</string>
<string name="pref_category_hide_hidden">Hide hidden categories from categories screen</string>
<!-- TachiyomiSY -->
<string name="data_saver_exclude">Exclude from data saver</string>
<string name="data_saver_stop_exclude">Stop excluding from data saver</string>