From a617528fdad1665b36e4118e8f24a0268c5599dd Mon Sep 17 00:00:00 2001 From: "Cuong M. Tran" Date: Wed, 6 Mar 2024 18:27:54 +0700 Subject: [PATCH] Allow/Skip one by one --- .../manga/DuplicateMangaDialog.kt | 103 ++++++++++++------ .../ui/browse/feed/FeedScreenModel.kt | 44 +++++++- .../tachiyomi/ui/browse/feed/FeedTab.kt | 23 ++++ .../source/browse/BrowseSourceScreen.kt | 17 ++- .../source/browse/BrowseSourceScreenModel.kt | 52 ++++++--- .../ui/browse/source/feed/SourceFeedScreen.kt | 23 ++++ .../source/feed/SourceFeedScreenModel.kt | 44 +++++++- .../commonMain/resources/MR/base/strings.xml | 6 +- 8 files changed, 254 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt index 859bb5820..e571c5bb9 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt @@ -1,13 +1,16 @@ package eu.kanade.presentation.manga import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.FlowColumn import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.AlertDialog import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.padding @@ -62,53 +65,91 @@ fun DuplicateMangaDialog( @Composable fun AllowDuplicateDialog( onDismissRequest: () -> Unit, - onAllowDuplicate: () -> Unit, - onSkipDuplicate: () -> Unit, - onOpenManga: () -> Unit = { }, + onAllowAllDuplicate: () -> Unit, + onSkipAllDuplicate: () -> Unit, + onOpenManga: () -> Unit = {}, + onAllowDuplicate: () -> Unit = {}, + onSkipDuplicate: () -> Unit = {}, + duplicatedName: String = "", ) { AlertDialog( onDismissRequest = onDismissRequest, - dismissButton = { - TextButton(onClick = onDismissRequest) { - Text(text = stringResource(MR.strings.action_cancel)) - } - }, title = { - Text(text = stringResource(MR.strings.are_you_sure)) + Text(text = duplicatedName) }, text = { Text(text = stringResource(MR.strings.confirm_add_duplicate_manga)) }, confirmButton = { FlowRow( - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceAround, ) { - TextButton( - onClick = { - onDismissRequest() - onOpenManga() - }, - ) { - Text(text = stringResource(MR.strings.action_show_manga)) + FlowColumn { + TextButton( + onClick = { + onDismissRequest() + onAllowDuplicate() + }, + Modifier.align(Alignment.CenterHorizontally), + ) { + Text(text = stringResource(MR.strings.action_allow_duplicate_manga)) + } + + TextButton( + onClick = { + onDismissRequest() + onAllowAllDuplicate() + }, + Modifier.align(Alignment.CenterHorizontally), + ) { + Text(text = stringResource(MR.strings.action_allow_all_duplicate_manga)) + } } - TextButton( - onClick = { - onDismissRequest() - onSkipDuplicate() - }, - ) { - Text(text = stringResource(MR.strings.action_skip_duplicate_manga)) + FlowColumn { + TextButton( + onClick = { + onDismissRequest() + onSkipDuplicate() + }, + Modifier.align(Alignment.CenterHorizontally), + ) { + Text(text = stringResource(MR.strings.action_skip_duplicate_manga)) + } + + TextButton( + onClick = { + onDismissRequest() + onSkipAllDuplicate() + }, + Modifier.align(Alignment.CenterHorizontally), + ) { + Text(text = stringResource(MR.strings.action_skip_all_duplicate_manga)) + } } - TextButton( - onClick = { - onDismissRequest() - onAllowDuplicate() - }, - ) { - Text(text = stringResource(MR.strings.action_allow_duplicate_manga)) + FlowColumn { + TextButton( + onClick = { + onDismissRequest() + onOpenManga() + }, + Modifier.align(Alignment.CenterHorizontally), + ) { + Text(text = stringResource(MR.strings.action_show_manga)) + } + + TextButton( + onClick = { + onDismissRequest() + }, + Modifier.align(Alignment.CenterHorizontally), + ) { + Text(text = stringResource(MR.strings.action_cancel)) + } } + } }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt index 31cbc83ca..9bd8dfc4f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.State 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 @@ -42,6 +43,7 @@ import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.model.Category 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.source.interactor.CountFeedSavedSearchGlobal @@ -78,6 +80,7 @@ open class FeedScreenModel( private val getCategories: GetCategories = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), + private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), // KMK <-- ) : StateScreenModel(FeedScreenState()) { @@ -337,9 +340,19 @@ open class FeedScreenModel( } } - fun addFavorite() { + fun addFavorite(startIdx: Int = 0) { screenModelScope.launch { - val mangaList = state.value.selection + 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() } @@ -376,6 +389,32 @@ open class FeedScreenModel( } } + private suspend fun getNotDuplicateLibraryMangas(): List { + return state.value.selection.filterNot { manga -> + getDuplicateLibraryManga.await(manga).isNotEmpty() + } + } + + private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Pair? { + 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. * @@ -463,6 +502,7 @@ open class FeedScreenModel( val mangas: List, val initialSelection: ImmutableList>, ) : Dialog() + data class AllowDuplicate(val duplicatedManga: Pair) : Dialog() // KMK <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt index bd99eb49a..71617924e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt @@ -23,6 +23,7 @@ import eu.kanade.presentation.browse.FeedScreen import eu.kanade.presentation.category.components.ChangeCategoryDialog import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.TabContent +import eu.kanade.presentation.manga.AllowDuplicateDialog import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.home.HomeScreen @@ -204,6 +205,28 @@ fun Screen.feedTab( }, ) } + is FeedScreenModel.Dialog.AllowDuplicate -> { + AllowDuplicateDialog( + onDismissRequest = onDismissRequest, + onAllowAllDuplicate = { + screenModel.addFavoriteDuplicate() + }, + onSkipAllDuplicate = { + screenModel.addFavoriteDuplicate(skipAllDuplicates = true) + }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + screenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + screenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + screenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, + ) + } // KMK <-- } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index 45bd99013..468d8de31 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -399,12 +399,23 @@ data class BrowseSourceScreen( is BrowseSourceScreenModel.Dialog.AllowDuplicate -> { AllowDuplicateDialog( onDismissRequest = onDismissRequest, - onAllowDuplicate = { + onAllowAllDuplicate = { screenModel.addFavoriteDuplicate() }, - onSkipDuplicate = { - screenModel.addFavoriteDuplicate(skipDuplicate = true) + onSkipAllDuplicate = { + screenModel.addFavoriteDuplicate(skipAllDuplicates = true) }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + screenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + screenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + screenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, ) } // 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 b19ffbde4..ad0e0c99e 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 @@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.util.fastForEachIndexed import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn @@ -475,18 +476,19 @@ open class BrowseSourceScreenModel( } } - fun addFavorite() { + fun addFavorite(startIdx: Int = 0) { screenModelScope.launch { - if (hasDuplicateLibraryMangas(state.value.selection)) - setDialog(Dialog.AllowDuplicate) + val mangaWithDup = getDuplicateLibraryManga(startIdx) + if (mangaWithDup != null) + setDialog(Dialog.AllowDuplicate(mangaWithDup)) else addFavoriteDuplicate() } } - fun addFavoriteDuplicate(skipDuplicate: Boolean = false) { + fun addFavoriteDuplicate(skipAllDuplicates: Boolean = false) { screenModelScope.launch { - val mangaList = if (skipDuplicate) getNotDuplicateLibraryMangas() else state.value.selection + 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() } @@ -523,6 +525,32 @@ open class BrowseSourceScreenModel( } } + private suspend fun getNotDuplicateLibraryMangas(): List { + return state.value.selection.filterNot { manga -> + getDuplicateLibraryManga.await(manga).isNotEmpty() + } + } + + private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Pair? { + 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. * @@ -583,18 +611,6 @@ open class BrowseSourceScreenModel( val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2) } return mangaCategories.flatten().distinct().subtract(common) } - - private suspend fun getNotDuplicateLibraryMangas(): List { - return state.value.selection.filterNot { manga -> - getDuplicateLibraryManga.await(manga).isNotEmpty() - } - } - - private suspend fun hasDuplicateLibraryMangas(mangas: List): Boolean { - return mangas.fastAny { manga -> - getDuplicateLibraryManga.await(manga).isNotEmpty() - } - } // KMK <-- sealed interface Dialog { @@ -616,7 +632,7 @@ open class BrowseSourceScreenModel( val mangas: List, val initialSelection: ImmutableList>, ) : Dialog - data object AllowDuplicate : Dialog + data class AllowDuplicate(val duplicatedManga: Pair) : Dialog // KMK <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt index 328f2677b..33fa5c3ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt @@ -15,6 +15,7 @@ import eu.kanade.presentation.browse.SourceFeedScreen import eu.kanade.presentation.browse.components.SourceFeedAddDialog import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog import eu.kanade.presentation.category.components.ChangeCategoryDialog +import eu.kanade.presentation.manga.AllowDuplicateDialog import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen @@ -109,6 +110,28 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { }, ) } + is SourceFeedScreenModel.Dialog.AllowDuplicate -> { + AllowDuplicateDialog( + onDismissRequest = onDismissRequest, + onAllowAllDuplicate = { + screenModel.addFavoriteDuplicate() + }, + onSkipAllDuplicate = { + screenModel.addFavoriteDuplicate(skipAllDuplicates = true) + }, + onOpenManga = { + navigator.push(MangaScreen(dialog.duplicatedManga.second.id)) + }, + onAllowDuplicate = { + screenModel.addFavorite(startIdx = dialog.duplicatedManga.first + 1) + }, + onSkipDuplicate = { + screenModel.removeDuplicateSelectedManga(index = dialog.duplicatedManga.first) + screenModel.addFavorite(startIdx = dialog.duplicatedManga.first) + }, + duplicatedName = dialog.duplicatedManga.second.title, + ) + } // KMK <-- SourceFeedScreenModel.Dialog.Filter -> { SourceFilterDialog( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt index 96fd1db3c..6f7e8b701 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.getValue 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 dev.icerock.moko.resources.StringResource @@ -49,6 +50,7 @@ import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.model.Category 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.source.interactor.CountFeedSavedSearchBySourceId @@ -84,6 +86,7 @@ open class SourceFeedScreenModel( private val getCategories: GetCategories = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), + private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), // KMK <-- ) : StateScreenModel(SourceFeedState()) { @@ -341,9 +344,19 @@ open class SourceFeedScreenModel( } } - fun addFavorite() { + fun addFavorite(startIdx: Int = 0) { screenModelScope.launch { - val mangaList = state.value.selection + 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() } @@ -380,6 +393,32 @@ open class SourceFeedScreenModel( } } + private suspend fun getNotDuplicateLibraryMangas(): List { + return state.value.selection.filterNot { manga -> + getDuplicateLibraryManga.await(manga).isNotEmpty() + } + } + + private suspend fun getDuplicateLibraryManga(startIdx: Int = 0): Pair? { + 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. * @@ -467,6 +506,7 @@ open class SourceFeedScreenModel( val mangas: List, val initialSelection: ImmutableList>, ) : Dialog() + data class AllowDuplicate(val duplicatedManga: Pair) : Dialog() // KMK <-- } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 3d3cc7866..af56253ca 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -678,6 +678,10 @@ Remove from library Unknown title You have an entry in your library with the same name.\n\nDo you still wish to continue? + Allow it + Skip it + Allow all + Skip all Added to library Removed from library More @@ -689,8 +693,6 @@ Source not installed: %1$s Add to library? No description - Allow duplicate - Skip duplicate Chapter %1$s