From a7c6232a502d145bd830bc5c49901e2c66e4f632 Mon Sep 17 00:00:00 2001 From: "Cuong M. Tran" Date: Sat, 9 Mar 2024 18:26:58 +0700 Subject: [PATCH] =?UTF-8?q?add=20BulkSelection=20to=20the=20rest=20of=20Mi?= =?UTF-8?q?grate,=20MangaDex=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/browse/BrowseSourceScreen.kt | 2 +- .../components/BrowseSourceSimpleToolbar.kt | 11 ++ .../migration/search/MigrateSearchScreen.kt | 27 +-- .../migration/search/SourceSearchScreen.kt | 114 ++++++++++++- .../exh/md/follows/MangaDexFollowsScreen.kt | 128 +++++++++++--- .../exh/md/similar/MangaDexSimilarScreen.kt | 155 ++++++++++++++++- .../main/java/exh/recs/RecommendsScreen.kt | 156 +++++++++++++++++- 7 files changed, 531 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt index a626dd5c3..5494801d1 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt @@ -59,7 +59,7 @@ fun BrowseSourceContent( onMangaClick: (Manga) -> Unit, onMangaLongClick: (Manga) -> Unit, // KMK --> - selection: List? = null, + selection: List, // KMK <-- ) { val context = LocalContext.current diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceSimpleToolbar.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceSimpleToolbar.kt index 341587fc5..17d59bbc8 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceSimpleToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceSimpleToolbar.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.browse.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.Checklist import androidx.compose.material.icons.outlined.ViewModule import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon @@ -27,6 +28,9 @@ fun BrowseSourceSimpleToolbar( displayMode: LibraryDisplayMode?, onDisplayModeChange: (LibraryDisplayMode) -> Unit, scrollBehavior: TopAppBarScrollBehavior, + // KMK --> + toggleSelectionMode: () -> Unit, + // KMK <-- ) { AppBar( navigateUp = navigateUp, @@ -41,6 +45,13 @@ fun BrowseSourceSimpleToolbar( icon = Icons.Outlined.ViewModule, onClick = { selectingDisplayMode = true }, ), + // KMK --> + AppBar.Action( + title = stringResource(MR.strings.action_bulk_select), + icon = Icons.Outlined.Checklist, + onClick = toggleSelectionMode, + ), + // KMK <-- ), ) DropdownMenu( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt index d9a540ab3..1a47d1f71 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt @@ -53,18 +53,19 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L }, onClickItem = { // KMK --> - if (bulkFavoriteState.selectionMode) + if (bulkFavoriteState.selectionMode) { bulkFavoriteScreenModel.toggleSelection(it) - else { + } else // KMK <-- - // SY --> - navigator.items - .filterIsInstance() - .last() - .newSelectedItem = mangaId to it.id - navigator.popUntil { it is MigrationListScreen } - // SY <-- - } + { + // SY --> + navigator.items + .filterIsInstance() + .last() + .newSelectedItem = mangaId to it.id + navigator.popUntil { it is MigrationListScreen } + // SY <-- + } }, onLongClickItem = { navigator.push(MangaScreen(it.id, true)) }, // KMK --> @@ -73,12 +74,12 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L ) // KMK --> - val onDismissRequest = { bulkFavoriteScreenModel.setDialog(null) } + val onBulkDismissRequest = { bulkFavoriteScreenModel.setDialog(null) } when (val dialog = bulkFavoriteState.dialog) { is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> { ChangeCategoryDialog( initialSelection = dialog.initialSelection, - onDismissRequest = onDismissRequest, + onDismissRequest = onBulkDismissRequest, onEditCategories = { navigator.push(CategoryScreen()) }, onConfirm = { include, exclude -> bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude) @@ -87,7 +88,7 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L } is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> { AllowDuplicateDialog( - onDismissRequest = onDismissRequest, + onDismissRequest = onBulkDismissRequest, onAllowAllDuplicate = { bulkFavoriteScreenModel.addFavoriteDuplicate() }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt index 1537b2dab..3572e295a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.ui.browse.migration.search +import androidx.activity.compose.BackHandler +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Checklist import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -14,18 +17,27 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton +import eu.kanade.presentation.category.components.ChangeCategoryDialog +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.SearchToolbar +import eu.kanade.presentation.components.SelectionToolbar +import eu.kanade.presentation.manga.AllowDuplicateDialog import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterDialog +import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewScreen import kotlinx.collections.immutable.persistentListOf import tachiyomi.core.common.Constants import tachiyomi.domain.manga.model.Manga +import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.source.local.LocalSource data class SourceSearchScreen( @@ -44,15 +56,51 @@ data class SourceSearchScreen( val snackbarHostState = remember { SnackbarHostState() } + // KMK --> + val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } + val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + + BackHandler(enabled = bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelectionMode() + } + // KMK <-- + Scaffold( topBar = { scrollBehavior -> - SearchToolbar( - searchQuery = state.toolbarQuery ?: "", - onChangeSearchQuery = screenModel::setToolbarQuery, - onClickCloseSearch = navigator::pop, - onSearch = screenModel::search, - scrollBehavior = scrollBehavior, - ) + // KMK --> + if (bulkFavoriteState.selectionMode) { + SelectionToolbar( + selectedCount = bulkFavoriteState.selection.size, + onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, + onChangeCategoryClicked = bulkFavoriteScreenModel::addFavorite, + ) + } else { + // KMK <-- + SearchToolbar( + searchQuery = state.toolbarQuery ?: "", + onChangeSearchQuery = screenModel::setToolbarQuery, + onClickCloseSearch = navigator::pop, + onSearch = screenModel::search, + scrollBehavior = scrollBehavior, + // KMK --> + actions = { + AppBarActions( + actions = persistentListOf().builder() + .apply { + add( + AppBar.Action( + title = stringResource(MR.strings.action_bulk_select), + icon = Icons.Outlined.Checklist, + onClick = bulkFavoriteScreenModel::toggleSelectionMode, + ), + ) + } + .build(), + ) + }, + // KMK <-- + ) + } }, floatingActionButton = { // SY --> @@ -96,8 +144,19 @@ data class SourceSearchScreen( }, onHelpClick = { uriHandler.openUri(Constants.URL_HELP) }, onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }, - onMangaClick = openMigrateDialog, + onMangaClick = { manga -> + // KMK --> + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + openMigrateDialog(manga) + } + }, onMangaLongClick = { navigator.push(MangaScreen(it.id, true)) }, + // KMK --> + selection = bulkFavoriteState.selection, + // KMK <-- ) } @@ -123,5 +182,44 @@ data class SourceSearchScreen( } else -> {} } + + // KMK --> + val onBulkDismissRequest = { bulkFavoriteScreenModel.setDialog(null) } + when (val dialog = bulkFavoriteState.dialog) { + is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onBulkDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, exclude -> + bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude) + }, + ) + } + is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> { + AllowDuplicateDialog( + onDismissRequest = onBulkDismissRequest, + onAllowAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate() + }, + onSkipAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate(skipAllDuplicates = true) + }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + bulkFavoriteScreenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, + ) + } + else -> {} + } + // KMK <-- } } diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt index 6fbf6c376..a643f6366 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt @@ -1,5 +1,6 @@ package exh.md.follows +import androidx.activity.compose.BackHandler import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -18,8 +19,11 @@ import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar import eu.kanade.presentation.browse.components.RemoveMangaDialog import eu.kanade.presentation.category.components.ChangeCategoryDialog +import eu.kanade.presentation.components.SelectionToolbar +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.BrowseSourceScreenModel import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen @@ -38,17 +42,39 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { val screenModel = rememberScreenModel { MangaDexFollowsScreenModel(sourceId) } val state by screenModel.state.collectAsState() + // KMK --> + val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } + val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + + BackHandler(enabled = bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelectionMode() + } + // KMK <-- + val snackbarHostState = remember { SnackbarHostState() } Scaffold( topBar = { scrollBehavior -> - BrowseSourceSimpleToolbar( - title = stringResource(SYMR.strings.mangadex_follows), - displayMode = screenModel.displayMode, - onDisplayModeChange = { screenModel.displayMode = it }, - navigateUp = navigator::pop, - scrollBehavior = scrollBehavior, - ) + // KMK --> + if (bulkFavoriteState.selectionMode) { + SelectionToolbar( + selectedCount = bulkFavoriteState.selection.size, + onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, + onChangeCategoryClicked = bulkFavoriteScreenModel::addFavorite, + ) + } else { + // KMK <-- + BrowseSourceSimpleToolbar( + title = stringResource(SYMR.strings.mangadex_follows), + displayMode = screenModel.displayMode, + onDisplayModeChange = { screenModel.displayMode = it }, + navigateUp = navigator::pop, + scrollBehavior = scrollBehavior, + // KMK --> + toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, + // KMK <-- + ) + } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) @@ -69,23 +95,42 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { onWebViewClick = null, onHelpClick = null, onLocalSourceHelpClick = null, - onMangaClick = { navigator.push(MangaScreen(it.id, true)) }, - onMangaLongClick = { manga -> - scope.launchIO { - val duplicateManga = screenModel.getDuplicateLibraryManga(manga) - when { - manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga)) - duplicateManga != null -> screenModel.setDialog( - BrowseSourceScreenModel.Dialog.AddDuplicateManga( - manga, - duplicateManga, - ), - ) - else -> screenModel.addFavorite(manga) - } - haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onMangaClick = { + // KMK --> + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(it) + } else { + // KMK <-- + navigator.push(MangaScreen(it.id, true)) } }, + onMangaLongClick = { manga -> + // KMK --> + if (bulkFavoriteState.selectionMode) { + navigator.push(MangaScreen(manga.id, true)) + } else { + // KMK <-- + scope.launchIO { + val duplicateManga = screenModel.getDuplicateLibraryManga(manga) + when { + manga.favorite -> screenModel.setDialog( + BrowseSourceScreenModel.Dialog.RemoveManga(manga) + ) + duplicateManga != null -> screenModel.setDialog( + BrowseSourceScreenModel.Dialog.AddDuplicateManga( + manga, + duplicateManga, + ), + ) + else -> screenModel.addFavorite(manga) + } + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + } + } + }, + // KMK --> + selection = bulkFavoriteState.selection, + // KMK <-- ) } @@ -123,5 +168,44 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { } else -> {} } + + // KMK --> + val onBulkDismissRequest = { bulkFavoriteScreenModel.setDialog(null) } + when (val dialog = bulkFavoriteState.dialog) { + is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onBulkDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, exclude -> + bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude) + }, + ) + } + is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> { + AllowDuplicateDialog( + onDismissRequest = onBulkDismissRequest, + onAllowAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate() + }, + onSkipAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate(skipAllDuplicates = true) + }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + bulkFavoriteScreenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, + ) + } + else -> {} + } + // KMK <-- } } diff --git a/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt b/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt index 62394333b..0980f5257 100644 --- a/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt +++ b/app/src/main/java/exh/md/similar/MangaDexSimilarScreen.kt @@ -1,20 +1,32 @@ package exh.md.similar +import androidx.activity.compose.BackHandler import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.paging.compose.collectAsLazyPagingItems import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar +import eu.kanade.presentation.browse.components.RemoveMangaDialog +import eu.kanade.presentation.category.components.ChangeCategoryDialog +import eu.kanade.presentation.components.SelectionToolbar +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.category.CategoryScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.material.Scaffold @@ -28,6 +40,18 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() { val state by screenModel.state.collectAsState() val navigator = LocalNavigator.currentOrThrow + // KMK --> + val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } + val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + + val scope = rememberCoroutineScope() + val haptic = LocalHapticFeedback.current + + BackHandler(enabled = bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelectionMode() + } + // KMK <-- + val onMangaClick: (Manga) -> Unit = { navigator.push(MangaScreen(it.id, true)) } @@ -36,13 +60,26 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() { Scaffold( topBar = { scrollBehavior -> - BrowseSourceSimpleToolbar( - navigateUp = navigator::pop, - title = stringResource(SYMR.strings.similar, screenModel.manga.title), - displayMode = screenModel.displayMode, - onDisplayModeChange = { screenModel.displayMode = it }, - scrollBehavior = scrollBehavior, - ) + // KMK --> + if (bulkFavoriteState.selectionMode) { + SelectionToolbar( + selectedCount = bulkFavoriteState.selection.size, + onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, + onChangeCategoryClicked = bulkFavoriteScreenModel::addFavorite, + ) + } else { + // KMK <-- + BrowseSourceSimpleToolbar( + navigateUp = navigator::pop, + title = stringResource(SYMR.strings.similar, screenModel.manga.title), + displayMode = screenModel.displayMode, + onDisplayModeChange = { screenModel.displayMode = it }, + scrollBehavior = scrollBehavior, + // KMK --> + toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, + // KMK <-- + ) + } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { paddingValues -> @@ -61,9 +98,109 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() { onWebViewClick = null, onHelpClick = null, onLocalSourceHelpClick = null, - onMangaClick = onMangaClick, - onMangaLongClick = onMangaClick, + onMangaClick = { manga -> + // KMK --> + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + onMangaClick(manga) + } + }, + onMangaLongClick = { manga -> + // KMK --> + if (!bulkFavoriteState.selectionMode) { + scope.launchIO { + val duplicateManga = bulkFavoriteScreenModel.getDuplicateLibraryManga(manga) + when { + manga.favorite -> bulkFavoriteScreenModel.setDialog( + BulkFavoriteScreenModel.Dialog.RemoveManga(manga) + ) + duplicateManga != null -> bulkFavoriteScreenModel.setDialog( + BulkFavoriteScreenModel.Dialog.AddDuplicateManga( + manga, + duplicateManga, + ), + ) + else -> bulkFavoriteScreenModel.addFavorite(manga) + } + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + } + } else { + // KMK <-- + onMangaClick(manga) + } + }, + // KMK --> + selection = bulkFavoriteState.selection, + // KMK <-- ) } + + // KMK --> + val onBulkDismissRequest = { bulkFavoriteScreenModel.setDialog(null) } + when (val dialog = bulkFavoriteState.dialog) { + is BulkFavoriteScreenModel.Dialog.AddDuplicateManga -> { + DuplicateMangaDialog( + onDismissRequest = onBulkDismissRequest, + onConfirm = { bulkFavoriteScreenModel.addFavorite(dialog.manga) }, + onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + ) + } + is BulkFavoriteScreenModel.Dialog.RemoveManga -> { + RemoveMangaDialog( + onDismissRequest = onBulkDismissRequest, + onConfirm = { + bulkFavoriteScreenModel.changeMangaFavorite(dialog.manga) + }, + mangaToRemove = dialog.manga, + ) + } + is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onBulkDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, _ -> + bulkFavoriteScreenModel.changeMangaFavorite(dialog.manga) + bulkFavoriteScreenModel.moveMangaToCategories(dialog.manga, include) + }, + ) + } + is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onBulkDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, exclude -> + bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude) + }, + ) + } + is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> { + AllowDuplicateDialog( + onDismissRequest = onBulkDismissRequest, + onAllowAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate() + }, + onSkipAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate(skipAllDuplicates = true) + }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + bulkFavoriteScreenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, + ) + } + else -> {} + } + // KMK <-- } } diff --git a/app/src/main/java/exh/recs/RecommendsScreen.kt b/app/src/main/java/exh/recs/RecommendsScreen.kt index 39cec8033..bfd1b7aad 100644 --- a/app/src/main/java/exh/recs/RecommendsScreen.kt +++ b/app/src/main/java/exh/recs/RecommendsScreen.kt @@ -1,12 +1,16 @@ package exh.recs +import androidx.activity.compose.BackHandler import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.paging.compose.collectAsLazyPagingItems import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator @@ -14,8 +18,17 @@ import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.BrowseSourceContent import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar +import eu.kanade.presentation.browse.components.RemoveMangaDialog +import eu.kanade.presentation.category.components.ChangeCategoryDialog +import eu.kanade.presentation.components.SelectionToolbar +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.SourcesScreen +import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.presentation.core.components.material.Scaffold @@ -27,6 +40,18 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() { val state by screenModel.state.collectAsState() val navigator = LocalNavigator.currentOrThrow + // KMK --> + val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } + val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + + val scope = rememberCoroutineScope() + val haptic = LocalHapticFeedback.current + + BackHandler(enabled = bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelectionMode() + } + // KMK <-- + val onMangaClick: (Manga) -> Unit = { manga -> openSmartSearch(navigator, manga.ogTitle) } @@ -35,13 +60,26 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() { Scaffold( topBar = { scrollBehavior -> - BrowseSourceSimpleToolbar( - navigateUp = navigator::pop, - title = screenModel.manga.title, - displayMode = screenModel.displayMode, - onDisplayModeChange = { screenModel.displayMode = it }, - scrollBehavior = scrollBehavior, - ) + // KMK --> + if (bulkFavoriteState.selectionMode) { + SelectionToolbar( + selectedCount = bulkFavoriteState.selection.size, + onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, + onChangeCategoryClicked = bulkFavoriteScreenModel::addFavorite, + ) + } else { + // KMK <-- + BrowseSourceSimpleToolbar( + navigateUp = navigator::pop, + title = screenModel.manga.title, + displayMode = screenModel.displayMode, + onDisplayModeChange = { screenModel.displayMode = it }, + scrollBehavior = scrollBehavior, + // KMK --> + toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, + // KMK <-- + ) + } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { paddingValues -> @@ -60,10 +98,110 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() { onWebViewClick = null, onHelpClick = null, onLocalSourceHelpClick = null, - onMangaClick = onMangaClick, - onMangaLongClick = onMangaClick, + onMangaClick = { manga -> + // KMK --> + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + onMangaClick(manga) + } + }, + onMangaLongClick = { manga -> + // KMK --> + if (!bulkFavoriteState.selectionMode) { + scope.launchIO { + val duplicateManga = bulkFavoriteScreenModel.getDuplicateLibraryManga(manga) + when { + manga.favorite -> bulkFavoriteScreenModel.setDialog( + BulkFavoriteScreenModel.Dialog.RemoveManga(manga) + ) + duplicateManga != null -> bulkFavoriteScreenModel.setDialog( + BulkFavoriteScreenModel.Dialog.AddDuplicateManga( + manga, + duplicateManga, + ), + ) + else -> bulkFavoriteScreenModel.addFavorite(manga) + } + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + } + } else { + // KMK <-- + onMangaClick(manga) + } + }, + // KMK --> + selection = bulkFavoriteState.selection, + // KMK <-- ) } + + // KMK --> + val onBulkDismissRequest = { bulkFavoriteScreenModel.setDialog(null) } + when (val dialog = bulkFavoriteState.dialog) { + is BulkFavoriteScreenModel.Dialog.AddDuplicateManga -> { + DuplicateMangaDialog( + onDismissRequest = onBulkDismissRequest, + onConfirm = { bulkFavoriteScreenModel.addFavorite(dialog.manga) }, + onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + ) + } + is BulkFavoriteScreenModel.Dialog.RemoveManga -> { + RemoveMangaDialog( + onDismissRequest = onBulkDismissRequest, + onConfirm = { + bulkFavoriteScreenModel.changeMangaFavorite(dialog.manga) + }, + mangaToRemove = dialog.manga, + ) + } + is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onBulkDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, _ -> + bulkFavoriteScreenModel.changeMangaFavorite(dialog.manga) + bulkFavoriteScreenModel.moveMangaToCategories(dialog.manga, include) + }, + ) + } + is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> { + ChangeCategoryDialog( + initialSelection = dialog.initialSelection, + onDismissRequest = onBulkDismissRequest, + onEditCategories = { navigator.push(CategoryScreen()) }, + onConfirm = { include, exclude -> + bulkFavoriteScreenModel.setMangasCategories(dialog.mangas, include, exclude) + }, + ) + } + is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> { + AllowDuplicateDialog( + onDismissRequest = onBulkDismissRequest, + onAllowAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate() + }, + onSkipAllDuplicate = { + bulkFavoriteScreenModel.addFavoriteDuplicate(skipAllDuplicates = true) + }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + bulkFavoriteScreenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + bulkFavoriteScreenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, + ) + } + else -> {} + } + // KMK <-- } private fun openSmartSearch(navigator: Navigator, title: String) {