diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt index a7d390f34..da244db46 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt @@ -62,7 +62,7 @@ fun GlobalSearchScreen( onToggleResults = onToggleResults, scrollBehavior = scrollBehavior, // KMK --> - toggleBulkSelectionMode = screenModel::toggleSelectionMode + toggleBulkSelectionMode = screenModel::toggleSelectionMode, // KMK <-- ) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index ad0e0c99e..ae159e8ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -339,7 +339,7 @@ open class BrowseSourceScreenModel( false -> Instant.now().toEpochMilli() }, ) - + // TODO: also allow deleting chapters when remove favorite (just like in [MangaScreenModel]) if (!new.favorite) { new = new.removeCovers(coverCache) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt index da382a574..a2cf8381b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt @@ -7,17 +7,23 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.GlobalSearchScreen +import eu.kanade.presentation.browse.components.RemoveMangaDialog 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.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.presentation.core.screens.LoadingScreen class GlobalSearchScreen( @@ -41,6 +47,9 @@ class GlobalSearchScreen( } // KMK --> + val scope = rememberCoroutineScope() + val haptic = LocalHapticFeedback.current + BackHandler(enabled = state.selectionMode) { when { state.selectionMode -> screenModel.toggleSelectionMode() @@ -89,8 +98,28 @@ class GlobalSearchScreen( // KMK <-- navigator.push(MangaScreen(it.id, true)) }, - onLongClickItem = { - navigator.push(MangaScreen(it.id, true)) + onLongClickItem = { manga -> + // KMK --> + if (state.selectionMode) + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) + // KMK --> + else + scope.launchIO { + val duplicateManga = screenModel.getDuplicateLibraryManga(manga) + when { + manga.favorite -> screenModel.setDialog(SearchScreenModel.Dialog.RemoveManga(manga)) + duplicateManga != null -> screenModel.setDialog( + SearchScreenModel.Dialog.AddDuplicateManga( + manga, + duplicateManga, + ), + ) + else -> screenModel.addFavorite(manga) + } + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + } + // KMK <-- }, ) } @@ -98,6 +127,33 @@ class GlobalSearchScreen( // KMK --> val onDismissRequest = { screenModel.setDialog(null) } when (val dialog = state.dialog) { + is SearchScreenModel.Dialog.AddDuplicateManga -> { + DuplicateMangaDialog( + onDismissRequest = onDismissRequest, + onConfirm = { screenModel.addFavorite(dialog.manga) }, + onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + ) + } + is SearchScreenModel.Dialog.RemoveManga -> { + RemoveMangaDialog( + onDismissRequest = onDismissRequest, + onConfirm = { + screenModel.changeMangaFavorite(dialog.manga) + }, + mangaToRemove = dialog.manga, + ) + } + is SearchScreenModel.Dialog.ChangeMangaCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, _ -> + screenModel.changeMangaFavorite(dialog.manga) + screenModel.moveMangaToCategories(dialog.manga, include) + }, + ) + } is SearchScreenModel.Dialog.ChangeMangasCategory -> { ChangeCategoryDialog( initialSelection = dialog.initialSelection, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 8fbede554..901623662 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -11,9 +11,12 @@ 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 @@ -35,19 +38,23 @@ 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( @@ -63,6 +70,9 @@ abstract class SearchScreenModel( 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(initialState) { @@ -390,11 +400,97 @@ abstract class SearchScreenModel( .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) { + 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>, + ) : Dialog data class ChangeMangasCategory( val mangas: List, val initialSelection: ImmutableList>,