separate BulkSelection into standalone BulkFavoriteScreenModel
This commit is contained in:
parent
42022bf661
commit
cc5b6a1d5f
6 changed files with 419 additions and 386 deletions
|
|
@ -12,7 +12,7 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.presentation.components.SelectionToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
|
|
@ -24,9 +24,6 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||
|
||||
@Composable
|
||||
fun GlobalSearchScreen(
|
||||
// KMK -->
|
||||
screenModel: GlobalSearchScreenModel,
|
||||
// KMK <--
|
||||
state: SearchScreenModel.State,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
|
|
@ -37,15 +34,20 @@ fun GlobalSearchScreen(
|
|||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
// KMK -->
|
||||
bulkFavoriteState: BulkFavoriteScreenModel.State,
|
||||
toggleSelectionMode: () -> Unit,
|
||||
addFavorite: () -> Unit,
|
||||
// KMK <--
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
// KMK -->
|
||||
if (state.selectionMode)
|
||||
if (bulkFavoriteState.selectionMode)
|
||||
SelectionToolbar(
|
||||
selectedCount = state.selection.size,
|
||||
onClickClearSelection = screenModel::toggleSelectionMode,
|
||||
onChangeCategoryClicked = screenModel::addFavorite,
|
||||
selectedCount = bulkFavoriteState.selection.size,
|
||||
onClickClearSelection = toggleSelectionMode,
|
||||
onChangeCategoryClicked = addFavorite,
|
||||
)
|
||||
else
|
||||
// KMK <--
|
||||
|
|
@ -62,7 +64,7 @@ fun GlobalSearchScreen(
|
|||
onToggleResults = onToggleResults,
|
||||
scrollBehavior = scrollBehavior,
|
||||
// KMK -->
|
||||
toggleBulkSelectionMode = screenModel::toggleSelectionMode,
|
||||
toggleBulkSelectionMode = toggleSelectionMode,
|
||||
// KMK <--
|
||||
)
|
||||
},
|
||||
|
|
@ -75,7 +77,7 @@ fun GlobalSearchScreen(
|
|||
onClickItem = onClickItem,
|
||||
onLongClickItem = onLongClickItem,
|
||||
// KMK -->
|
||||
selection = state.selection,
|
||||
selection = bulkFavoriteState.selection,
|
||||
// KMK <--
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import androidx.compose.runtime.State
|
|||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.presentation.components.SelectionToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
|
@ -13,9 +13,6 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||
|
||||
@Composable
|
||||
fun MigrateSearchScreen(
|
||||
// KMK -->
|
||||
screenModel: MigrateSearchScreenModel,
|
||||
// KMK <--
|
||||
state: SearchScreenModel.State,
|
||||
fromSourceId: Long?,
|
||||
navigateUp: () -> Unit,
|
||||
|
|
@ -27,15 +24,20 @@ fun MigrateSearchScreen(
|
|||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
// KMK -->
|
||||
bulkFavoriteState: BulkFavoriteScreenModel.State,
|
||||
toggleSelectionMode: () -> Unit,
|
||||
addFavorite: () -> Unit,
|
||||
// KMK <--
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
// KMK -->
|
||||
if (state.selectionMode)
|
||||
if (bulkFavoriteState.selectionMode)
|
||||
SelectionToolbar(
|
||||
selectedCount = state.selection.size,
|
||||
onClickClearSelection = screenModel::toggleSelectionMode,
|
||||
onChangeCategoryClicked = screenModel::addFavorite,
|
||||
selectedCount = bulkFavoriteState.selection.size,
|
||||
onClickClearSelection = toggleSelectionMode,
|
||||
onChangeCategoryClicked = addFavorite,
|
||||
)
|
||||
else
|
||||
// KMK <--
|
||||
|
|
@ -52,7 +54,7 @@ fun MigrateSearchScreen(
|
|||
onToggleResults = onToggleResults,
|
||||
scrollBehavior = scrollBehavior,
|
||||
// KMK -->
|
||||
toggleBulkSelectionMode = screenModel::toggleSelectionMode
|
||||
toggleBulkSelectionMode = toggleSelectionMode
|
||||
// KMK <--
|
||||
)
|
||||
},
|
||||
|
|
@ -66,7 +68,7 @@ fun MigrateSearchScreen(
|
|||
onClickItem = onClickItem,
|
||||
onLongClickItem = onLongClickItem,
|
||||
// KMK -->
|
||||
selection = state.selection,
|
||||
selection = bulkFavoriteState.selection,
|
||||
// KMK <--
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,336 @@
|
|||
package eu.kanade.tachiyomi.ui.browse
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
class BulkFavoriteScreenModel(
|
||||
initialState: State = State(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
) : StateScreenModel<BulkFavoriteScreenModel.State>(initialState) {
|
||||
|
||||
fun backHandler() {
|
||||
toggleSelectionMode()
|
||||
}
|
||||
|
||||
fun toggleSelectionMode() {
|
||||
if (state.value.selectionMode)
|
||||
clearSelection()
|
||||
mutableState.update { it.copy(selectionMode = !it.selectionMode) }
|
||||
}
|
||||
|
||||
private fun clearSelection() {
|
||||
mutableState.update { it.copy(selection = persistentListOf()) }
|
||||
}
|
||||
|
||||
fun toggleSelection(manga: Manga) {
|
||||
mutableState.update { state ->
|
||||
val newSelection = state.selection.mutate { list ->
|
||||
if (list.fastAny { it.id == manga.id }) {
|
||||
list.removeAll { it.id == manga.id }
|
||||
} else {
|
||||
list.add(manga)
|
||||
}
|
||||
}
|
||||
state.copy(selection = newSelection)
|
||||
}.also {
|
||||
if (state.value.selection.isEmpty())
|
||||
toggleSelectionMode()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavorite(startIdx: Int = 0) {
|
||||
screenModelScope.launch {
|
||||
val mangaWithDup = getDuplicateLibraryManga(startIdx)
|
||||
if (mangaWithDup != null)
|
||||
setDialog(Dialog.AllowDuplicate(mangaWithDup))
|
||||
else
|
||||
addFavoriteDuplicate()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavoriteDuplicate(skipAllDuplicates: Boolean = false) {
|
||||
screenModelScope.launch {
|
||||
val mangaList = if (skipAllDuplicates) getNotDuplicateLibraryMangas() else state.value.selection
|
||||
val categories = getCategories()
|
||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||
|
||||
when {
|
||||
// Default category set
|
||||
defaultCategory != null -> {
|
||||
setMangasCategories(mangaList, listOf(defaultCategory.id), emptyList())
|
||||
}
|
||||
|
||||
// Automatic 'Default' or no categories
|
||||
defaultCategoryId == 0 || categories.isEmpty() -> {
|
||||
// Automatic 'Default' or no categories
|
||||
setMangasCategories(mangaList, emptyList(), emptyList())
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Get indexes of the common categories to preselect.
|
||||
val common = getCommonCategories(mangaList)
|
||||
// Get indexes of the mix categories to preselect.
|
||||
val mix = getMixCategories(mangaList)
|
||||
val preselected = categories
|
||||
.map {
|
||||
when (it) {
|
||||
in common -> CheckboxState.State.Checked(it)
|
||||
in mix -> CheckboxState.TriState.Exclude(it)
|
||||
else -> CheckboxState.State.None(it)
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
setDialog(Dialog.ChangeMangasCategory(mangaList, preselected))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNotDuplicateLibraryMangas(): List<Manga> {
|
||||
return state.value.selection.filterNot { manga ->
|
||||
getDuplicateLibraryManga.await(manga).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Pair<Int, Manga>? {
|
||||
val mangas = state.value.selection
|
||||
mangas.fastForEachIndexed { index, manga ->
|
||||
if (index < startIdx) return@fastForEachIndexed
|
||||
val dup = getDuplicateLibraryManga.await(manga)
|
||||
if (dup.isEmpty()) return@fastForEachIndexed
|
||||
return Pair(index, dup.first())
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun removeDuplicateSelectedManga(index: Int) {
|
||||
mutableState.update { state ->
|
||||
val newSelection = state.selection.mutate { list ->
|
||||
list.removeAt(index)
|
||||
}
|
||||
state.copy(selection = newSelection)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update categories of manga using old and new common categories.
|
||||
*
|
||||
* @param mangaList the list of manga to move.
|
||||
* @param addCategories the categories to add for all mangas.
|
||||
* @param removeCategories the categories to remove in all mangas.
|
||||
*/
|
||||
fun setMangasCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
||||
screenModelScope.launchNonCancellable {
|
||||
mangaList.fastForEach { manga ->
|
||||
val categoryIds = getCategories.await(manga.id)
|
||||
.map { it.id }
|
||||
.subtract(removeCategories.toSet())
|
||||
.plus(addCategories)
|
||||
.toList()
|
||||
|
||||
moveMangaToCategoriesAndAddToLibrary(manga, categoryIds)
|
||||
}
|
||||
}
|
||||
toggleSelectionMode()
|
||||
}
|
||||
|
||||
private fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Long>) {
|
||||
moveMangaToCategory(manga.id, categories)
|
||||
if (manga.favorite) return
|
||||
|
||||
screenModelScope.launchIO {
|
||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveMangaToCategory(mangaId: Long, categoryIds: List<Long>) {
|
||||
screenModelScope.launchIO {
|
||||
setMangaCategories.await(mangaId, categoryIds)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the common categories for the given list of manga.
|
||||
*
|
||||
* @param mangas the list of manga.
|
||||
*/
|
||||
private suspend fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
||||
if (mangas.isEmpty()) return emptyList()
|
||||
return mangas
|
||||
.map { getCategories.await(it.id).toSet() }
|
||||
.reduce { set1, set2 -> set1.intersect(set2) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mix (non-common) categories for the given list of manga.
|
||||
*
|
||||
* @param mangas the list of manga.
|
||||
*/
|
||||
private suspend fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
||||
if (mangas.isEmpty()) return emptyList()
|
||||
val mangaCategories = mangas.map { getCategories.await(it.id).toSet() }
|
||||
val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2) }
|
||||
return mangaCategories.flatten().distinct().subtract(common)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user categories.
|
||||
*
|
||||
* @return List of categories, not including the default category
|
||||
*/
|
||||
suspend fun getCategories(): List<Category> {
|
||||
return getCategories.subscribe()
|
||||
.firstOrNull()
|
||||
?.filterNot { it.isSystemCategory }
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
suspend fun getDuplicateLibraryManga(manga: Manga): Manga? {
|
||||
return getDuplicateLibraryManga.await(manga).getOrNull(0)
|
||||
}
|
||||
|
||||
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
||||
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
|
||||
}
|
||||
|
||||
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
||||
screenModelScope.launchIO {
|
||||
setMangaCategories.await(
|
||||
mangaId = manga.id,
|
||||
categoryIds = categoryIds.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes a manga from the library.
|
||||
*
|
||||
* @param manga the manga to update.
|
||||
*/
|
||||
fun changeMangaFavorite(manga: Manga) {
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
|
||||
screenModelScope.launch {
|
||||
var new = manga.copy(
|
||||
favorite = !manga.favorite,
|
||||
dateAdded = when (manga.favorite) {
|
||||
true -> 0
|
||||
false -> Instant.now().toEpochMilli()
|
||||
},
|
||||
)
|
||||
// TODO: also allow deleting chapters when remove favorite (just like in [MangaScreenModel])
|
||||
if (!new.favorite) {
|
||||
new = new.removeCovers(coverCache)
|
||||
} else {
|
||||
setMangaDefaultChapterFlags.await(manga)
|
||||
addTracks.bindEnhancedTrackers(manga, source)
|
||||
}
|
||||
|
||||
updateManga.await(new.toMangaUpdate())
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavorite(manga: Manga) {
|
||||
screenModelScope.launch {
|
||||
val categories = getCategories()
|
||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||
|
||||
when {
|
||||
// Default category set
|
||||
defaultCategory != null -> {
|
||||
moveMangaToCategories(manga, defaultCategory)
|
||||
|
||||
changeMangaFavorite(manga)
|
||||
}
|
||||
|
||||
// Automatic 'Default' or no categories
|
||||
defaultCategoryId == 0 || categories.isEmpty() -> {
|
||||
moveMangaToCategories(manga)
|
||||
|
||||
changeMangaFavorite(manga)
|
||||
}
|
||||
|
||||
// Choose a category
|
||||
else -> {
|
||||
val preselectedIds = getCategories.await(manga.id).map { it.id }
|
||||
setDialog(
|
||||
Dialog.ChangeMangaCategory(
|
||||
manga,
|
||||
categories.mapAsCheckboxState { it.id in preselectedIds }.toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update {
|
||||
it.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
|
||||
interface Dialog {
|
||||
data class RemoveManga(val manga: Manga) : Dialog
|
||||
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
|
||||
data class ChangeMangaCategory(
|
||||
val manga: Manga,
|
||||
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
||||
) : Dialog
|
||||
data class ChangeMangasCategory(
|
||||
val mangas: List<Manga>,
|
||||
val initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||
) : Dialog
|
||||
data class AllowDuplicate(val duplicatedManga: Pair<Int, Manga>) : Dialog
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State (
|
||||
val dialog: Dialog? = null,
|
||||
val selection: PersistentList<Manga> = persistentListOf(),
|
||||
val selectionMode: Boolean = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -11,8 +11,8 @@ import eu.kanade.presentation.browse.MigrateSearchScreen
|
|||
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.manga.AllowDuplicateDialog
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
|
||||
|
|
@ -29,17 +29,15 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L
|
|||
val dialogState by dialogScreenModel.state.collectAsState()
|
||||
|
||||
// KMK -->
|
||||
BackHandler(enabled = state.selectionMode) {
|
||||
when {
|
||||
state.selectionMode -> screenModel.toggleSelectionMode()
|
||||
}
|
||||
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
|
||||
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
|
||||
|
||||
BackHandler(enabled = bulkFavoriteState.selectionMode) {
|
||||
bulkFavoriteScreenModel.toggleSelectionMode()
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
MigrateSearchScreen(
|
||||
// KMK -->
|
||||
screenModel = screenModel,
|
||||
// KMK <--
|
||||
state = state,
|
||||
fromSourceId = state.fromSourceId,
|
||||
navigateUp = navigator::pop,
|
||||
|
|
@ -55,8 +53,8 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L
|
|||
},
|
||||
onClickItem = {
|
||||
// KMK -->
|
||||
if (state.selectionMode) {
|
||||
screenModel.toggleSelection(it)
|
||||
if (bulkFavoriteState.selectionMode) {
|
||||
bulkFavoriteScreenModel.toggleSelection(it)
|
||||
}
|
||||
else
|
||||
// KMK <--
|
||||
|
|
@ -71,39 +69,44 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L
|
|||
}
|
||||
},
|
||||
onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||
// KMK -->
|
||||
bulkFavoriteState = bulkFavoriteState,
|
||||
toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode,
|
||||
addFavorite = bulkFavoriteScreenModel::addFavorite,
|
||||
// KMK <--
|
||||
)
|
||||
|
||||
// KMK -->
|
||||
val onDismissRequest = { screenModel.setDialog(null) }
|
||||
when (val dialog = state.dialog) {
|
||||
is SearchScreenModel.Dialog.ChangeMangasCategory -> {
|
||||
val onDismissRequest = { bulkFavoriteScreenModel.setDialog(null) }
|
||||
when (val dialog = bulkFavoriteState.dialog) {
|
||||
is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||
onConfirm = { include, exclude ->
|
||||
screenModel.setMangaCategories(dialog.mangas, include, exclude)
|
||||
bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude)
|
||||
},
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.AllowDuplicate -> {
|
||||
is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> {
|
||||
AllowDuplicateDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onAllowAllDuplicate = {
|
||||
screenModel.addFavoriteDuplicate()
|
||||
bulkFavoriteScreenModel.addFavoriteDuplicate()
|
||||
},
|
||||
onSkipAllDuplicate = {
|
||||
screenModel.addFavoriteDuplicate(skipAllDuplicates = true)
|
||||
bulkFavoriteScreenModel.addFavoriteDuplicate(skipAllDuplicates = true)
|
||||
},
|
||||
onOpenManga = {
|
||||
navigator.push(MangaScreen(dialog.duplicatedManga.second.id))
|
||||
},
|
||||
onAllowDuplicate = {
|
||||
screenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1)
|
||||
bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1)
|
||||
},
|
||||
onSkipDuplicate = {
|
||||
screenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first)
|
||||
screenModel.addFavorite(startIdx = dialog.duplicatedManga.first)
|
||||
bulkFavoriteScreenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first)
|
||||
bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first)
|
||||
},
|
||||
duplicatedName = dialog.duplicatedManga.second.title,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
|||
import eu.kanade.presentation.manga.AllowDuplicateDialog
|
||||
import eu.kanade.presentation.manga.DuplicateMangaDialog
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
|
|
@ -50,10 +51,11 @@ class GlobalSearchScreen(
|
|||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
BackHandler(enabled = state.selectionMode) {
|
||||
when {
|
||||
state.selectionMode -> screenModel.toggleSelectionMode()
|
||||
}
|
||||
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
|
||||
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
|
||||
|
||||
BackHandler(enabled = bulkFavoriteState.selectionMode) {
|
||||
bulkFavoriteScreenModel.backHandler()
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
|
|
@ -77,9 +79,6 @@ class GlobalSearchScreen(
|
|||
}
|
||||
} else {
|
||||
GlobalSearchScreen(
|
||||
// KMK -->
|
||||
screenModel = screenModel,
|
||||
// KMK <--
|
||||
state = state,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
|
|
@ -92,96 +91,101 @@ class GlobalSearchScreen(
|
|||
},
|
||||
onClickItem = {
|
||||
// KMK -->
|
||||
if (state.selectionMode)
|
||||
screenModel.toggleSelection(it)
|
||||
if (bulkFavoriteState.selectionMode)
|
||||
bulkFavoriteScreenModel.toggleSelection(it)
|
||||
else
|
||||
// KMK <--
|
||||
navigator.push(MangaScreen(it.id, true))
|
||||
},
|
||||
onLongClickItem = { manga ->
|
||||
// KMK -->
|
||||
if (state.selectionMode)
|
||||
if (bulkFavoriteState.selectionMode)
|
||||
// KMK <--
|
||||
navigator.push(MangaScreen(manga.id, true))
|
||||
// KMK -->
|
||||
else
|
||||
scope.launchIO {
|
||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||
val duplicateManga = bulkFavoriteScreenModel.getDuplicateLibraryManga(manga)
|
||||
when {
|
||||
manga.favorite -> screenModel.setDialog(SearchScreenModel.Dialog.RemoveManga(manga))
|
||||
duplicateManga != null -> screenModel.setDialog(
|
||||
SearchScreenModel.Dialog.AddDuplicateManga(
|
||||
manga.favorite -> bulkFavoriteScreenModel.setDialog(BulkFavoriteScreenModel.Dialog.RemoveManga(manga))
|
||||
duplicateManga != null -> bulkFavoriteScreenModel.setDialog(
|
||||
BulkFavoriteScreenModel.Dialog.AddDuplicateManga(
|
||||
manga,
|
||||
duplicateManga,
|
||||
),
|
||||
)
|
||||
else -> screenModel.addFavorite(manga)
|
||||
else -> bulkFavoriteScreenModel.addFavorite(manga)
|
||||
}
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
// KMK <--
|
||||
},
|
||||
// KMK -->
|
||||
bulkFavoriteState = bulkFavoriteState,
|
||||
toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode,
|
||||
addFavorite = bulkFavoriteScreenModel::addFavorite,
|
||||
// KMK <--
|
||||
)
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
val onDismissRequest = { screenModel.setDialog(null) }
|
||||
when (val dialog = state.dialog) {
|
||||
is SearchScreenModel.Dialog.AddDuplicateManga -> {
|
||||
val onDismissRequest = { bulkFavoriteScreenModel.setDialog(null) }
|
||||
when (val dialog = bulkFavoriteState.dialog) {
|
||||
is BulkFavoriteScreenModel.Dialog.AddDuplicateManga -> {
|
||||
DuplicateMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||
onConfirm = { bulkFavoriteScreenModel.addFavorite(dialog.manga) },
|
||||
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.RemoveManga -> {
|
||||
is BulkFavoriteScreenModel.Dialog.RemoveManga -> {
|
||||
RemoveMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = {
|
||||
screenModel.changeMangaFavorite(dialog.manga)
|
||||
bulkFavoriteScreenModel.changeMangaFavorite(dialog.manga)
|
||||
},
|
||||
mangaToRemove = dialog.manga,
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.ChangeMangaCategory -> {
|
||||
is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||
onConfirm = { include, _ ->
|
||||
screenModel.changeMangaFavorite(dialog.manga)
|
||||
screenModel.moveMangaToCategories(dialog.manga, include)
|
||||
bulkFavoriteScreenModel.changeMangaFavorite(dialog.manga)
|
||||
bulkFavoriteScreenModel.moveMangaToCategories(dialog.manga, include)
|
||||
},
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.ChangeMangasCategory -> {
|
||||
is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||
onConfirm = { include, exclude ->
|
||||
screenModel.setMangaCategories(dialog.mangas, include, exclude)
|
||||
bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude)
|
||||
},
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.AllowDuplicate -> {
|
||||
is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> {
|
||||
AllowDuplicateDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onAllowAllDuplicate = {
|
||||
screenModel.addFavoriteDuplicate()
|
||||
bulkFavoriteScreenModel.addFavoriteDuplicate()
|
||||
},
|
||||
onSkipAllDuplicate = {
|
||||
screenModel.addFavoriteDuplicate(skipAllDuplicates = true)
|
||||
bulkFavoriteScreenModel.addFavoriteDuplicate(skipAllDuplicates = true)
|
||||
},
|
||||
onOpenManga = {
|
||||
navigator.push(MangaScreen(dialog.duplicatedManga.second.id))
|
||||
},
|
||||
onAllowDuplicate = {
|
||||
screenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1)
|
||||
bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1)
|
||||
},
|
||||
onSkipDuplicate = {
|
||||
screenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first)
|
||||
screenModel.addFavorite(startIdx = dialog.duplicatedManga.first)
|
||||
bulkFavoriteScreenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first)
|
||||
bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first)
|
||||
},
|
||||
duplicatedName = dialog.duplicatedManga.second.title,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,27 +3,15 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toDomainManga
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -32,29 +20,16 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
abstract class SearchScreenModel(
|
||||
|
|
@ -64,16 +39,6 @@ abstract class SearchScreenModel(
|
|||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
// KMK -->
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
// KMK <--
|
||||
) : StateScreenModel<SearchScreenModel.State>(initialState) {
|
||||
|
||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||
|
|
@ -228,280 +193,6 @@ abstract class SearchScreenModel(
|
|||
updateItems(newItems)
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
fun toggleSelectionMode() {
|
||||
if (state.value.selectionMode)
|
||||
clearSelection()
|
||||
mutableState.update { it.copy(selectionMode = !it.selectionMode) }
|
||||
}
|
||||
|
||||
private fun clearSelection() {
|
||||
mutableState.update { it.copy(selection = persistentListOf()) }
|
||||
}
|
||||
|
||||
fun toggleSelection(manga: Manga) {
|
||||
mutableState.update { state ->
|
||||
val newSelection = state.selection.mutate { list ->
|
||||
if (list.fastAny { it.id == manga.id }) {
|
||||
list.removeAll { it.id == manga.id }
|
||||
} else {
|
||||
list.add(manga)
|
||||
}
|
||||
}
|
||||
state.copy(selection = newSelection)
|
||||
}.also {
|
||||
if (state.value.selection.isEmpty())
|
||||
toggleSelectionMode()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavorite(startIdx: Int = 0) {
|
||||
screenModelScope.launch {
|
||||
val mangaWithDup = getDuplicateLibraryManga(startIdx)
|
||||
if (mangaWithDup != null)
|
||||
setDialog(Dialog.AllowDuplicate(mangaWithDup))
|
||||
else
|
||||
addFavoriteDuplicate()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavoriteDuplicate(skipAllDuplicates: Boolean = false) {
|
||||
screenModelScope.launch {
|
||||
val mangaList = if (skipAllDuplicates) getNotDuplicateLibraryMangas() else state.value.selection
|
||||
val categories = getCategories()
|
||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||
|
||||
when {
|
||||
// Default category set
|
||||
defaultCategory != null -> {
|
||||
setMangaCategories(mangaList, listOf(defaultCategory.id), emptyList())
|
||||
}
|
||||
|
||||
// Automatic 'Default' or no categories
|
||||
defaultCategoryId == 0 || categories.isEmpty() -> {
|
||||
// Automatic 'Default' or no categories
|
||||
setMangaCategories(mangaList, emptyList(), emptyList())
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Get indexes of the common categories to preselect.
|
||||
val common = getCommonCategories(mangaList)
|
||||
// Get indexes of the mix categories to preselect.
|
||||
val mix = getMixCategories(mangaList)
|
||||
val preselected = categories
|
||||
.map {
|
||||
when (it) {
|
||||
in common -> CheckboxState.State.Checked(it)
|
||||
in mix -> CheckboxState.TriState.Exclude(it)
|
||||
else -> CheckboxState.State.None(it)
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
setDialog(Dialog.ChangeMangasCategory(mangaList, preselected))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNotDuplicateLibraryMangas(): List<Manga> {
|
||||
return state.value.selection.filterNot { manga ->
|
||||
getDuplicateLibraryManga.await(manga).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Pair<Int, Manga>? {
|
||||
val mangas = state.value.selection
|
||||
mangas.fastForEachIndexed { index, manga ->
|
||||
if (index < startIdx) return@fastForEachIndexed
|
||||
val dup = getDuplicateLibraryManga.await(manga)
|
||||
if (dup.isEmpty()) return@fastForEachIndexed
|
||||
return Pair(index, dup.first())
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun removeDuplicateSelectedManga(index: Int) {
|
||||
mutableState.update { state ->
|
||||
val newSelection = state.selection.mutate { list ->
|
||||
list.removeAt(index)
|
||||
}
|
||||
state.copy(selection = newSelection)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update categories of manga using old and new common categories.
|
||||
*
|
||||
* @param mangaList the list of manga to move.
|
||||
* @param addCategories the categories to add for all mangas.
|
||||
* @param removeCategories the categories to remove in all mangas.
|
||||
*/
|
||||
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
||||
screenModelScope.launchNonCancellable {
|
||||
mangaList.fastForEach { manga ->
|
||||
val categoryIds = getCategories.await(manga.id)
|
||||
.map { it.id }
|
||||
.subtract(removeCategories.toSet())
|
||||
.plus(addCategories)
|
||||
.toList()
|
||||
|
||||
moveMangaToCategoriesAndAddToLibrary(manga, categoryIds)
|
||||
}
|
||||
}
|
||||
toggleSelectionMode()
|
||||
}
|
||||
|
||||
private fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Long>) {
|
||||
moveMangaToCategory(manga.id, categories)
|
||||
if (manga.favorite) return
|
||||
|
||||
screenModelScope.launchIO {
|
||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveMangaToCategory(mangaId: Long, categoryIds: List<Long>) {
|
||||
screenModelScope.launchIO {
|
||||
setMangaCategories.await(mangaId, categoryIds)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the common categories for the given list of manga.
|
||||
*
|
||||
* @param mangas the list of manga.
|
||||
*/
|
||||
private suspend fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
||||
if (mangas.isEmpty()) return emptyList()
|
||||
return mangas
|
||||
.map { getCategories.await(it.id).toSet() }
|
||||
.reduce { set1, set2 -> set1.intersect(set2) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mix (non-common) categories for the given list of manga.
|
||||
*
|
||||
* @param mangas the list of manga.
|
||||
*/
|
||||
private suspend fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
||||
if (mangas.isEmpty()) return emptyList()
|
||||
val mangaCategories = mangas.map { getCategories.await(it.id).toSet() }
|
||||
val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2) }
|
||||
return mangaCategories.flatten().distinct().subtract(common)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user categories.
|
||||
*
|
||||
* @return List of categories, not including the default category
|
||||
*/
|
||||
suspend fun getCategories(): List<Category> {
|
||||
return getCategories.subscribe()
|
||||
.firstOrNull()
|
||||
?.filterNot { it.isSystemCategory }
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
suspend fun getDuplicateLibraryManga(manga: Manga): Manga? {
|
||||
return getDuplicateLibraryManga.await(manga).getOrNull(0)
|
||||
}
|
||||
|
||||
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
||||
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
|
||||
}
|
||||
|
||||
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
||||
screenModelScope.launchIO {
|
||||
setMangaCategories.await(
|
||||
mangaId = manga.id,
|
||||
categoryIds = categoryIds.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes a manga from the library.
|
||||
*
|
||||
* @param manga the manga to update.
|
||||
*/
|
||||
fun changeMangaFavorite(manga: Manga) {
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
|
||||
screenModelScope.launch {
|
||||
var new = manga.copy(
|
||||
favorite = !manga.favorite,
|
||||
dateAdded = when (manga.favorite) {
|
||||
true -> 0
|
||||
false -> Instant.now().toEpochMilli()
|
||||
},
|
||||
)
|
||||
// TODO: also allow deleting chapters when remove favorite (just like in [MangaScreenModel])
|
||||
if (!new.favorite) {
|
||||
new = new.removeCovers(coverCache)
|
||||
} else {
|
||||
setMangaDefaultChapterFlags.await(manga)
|
||||
addTracks.bindEnhancedTrackers(manga, source)
|
||||
}
|
||||
|
||||
updateManga.await(new.toMangaUpdate())
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavorite(manga: Manga) {
|
||||
screenModelScope.launch {
|
||||
val categories = getCategories()
|
||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||
|
||||
when {
|
||||
// Default category set
|
||||
defaultCategory != null -> {
|
||||
moveMangaToCategories(manga, defaultCategory)
|
||||
|
||||
changeMangaFavorite(manga)
|
||||
}
|
||||
|
||||
// Automatic 'Default' or no categories
|
||||
defaultCategoryId == 0 || categories.isEmpty() -> {
|
||||
moveMangaToCategories(manga)
|
||||
|
||||
changeMangaFavorite(manga)
|
||||
}
|
||||
|
||||
// Choose a category
|
||||
else -> {
|
||||
val preselectedIds = getCategories.await(manga.id).map { it.id }
|
||||
setDialog(
|
||||
Dialog.ChangeMangaCategory(
|
||||
manga,
|
||||
categories.mapAsCheckboxState { it.id in preselectedIds }.toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update { it.copy(dialog = dialog) }
|
||||
}
|
||||
|
||||
sealed interface Dialog {
|
||||
data class RemoveManga(val manga: Manga) : Dialog
|
||||
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
|
||||
data class ChangeMangaCategory(
|
||||
val manga: Manga,
|
||||
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
||||
) : Dialog
|
||||
data class ChangeMangasCategory(
|
||||
val mangas: List<Manga>,
|
||||
val initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||
) : Dialog
|
||||
data class AllowDuplicate(val duplicatedManga: Pair<Int, Manga>) : Dialog
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val fromSourceId: Long? = null,
|
||||
|
|
@ -509,11 +200,6 @@ abstract class SearchScreenModel(
|
|||
val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: PersistentMap<CatalogueSource, SearchItemResult> = persistentMapOf(),
|
||||
// KMK -->
|
||||
val dialog: Dialog? = null,
|
||||
val selection: PersistentList<Manga> = persistentListOf(),
|
||||
val selectionMode: Boolean = false,
|
||||
// KMK <--
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
|
|
|
|||
Loading…
Reference in a new issue