From 2ea488bff5cd7e3b1ef1178e76a2761d8d5cad09 Mon Sep 17 00:00:00 2001 From: Maddie Witman Date: Fri, 22 Mar 2024 09:04:43 -0400 Subject: [PATCH] Rework Duplicate Dialog and Allow Migration (#492) * (Mostly) Working Manga screen migration via duplicate dialog * Fully working migrate from Browse Search * Small tweaks for Antsy * Update app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt * Update app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt --------- Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit c0a888807b78891b28c6f6b9f16b719e24b03de1) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt --- .../manga/DuplicateMangaDialog.kt | 141 +++++++++++++----- .../advanced/design/PreMigrationScreen.kt | 26 +++- .../process/MigrationListScreenModel.kt | 58 +++++-- .../process/MigrationProcedureConfig.kt | 3 +- .../source/browse/BrowseSourceScreen.kt | 14 ++ .../source/browse/BrowseSourceScreenModel.kt | 2 +- .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 23 ++- .../tachiyomi/ui/manga/MangaScreenModel.kt | 9 ++ .../exh/md/follows/MangaDexFollowsScreen.kt | 12 ++ .../commonMain/resources/MR/base/strings.xml | 2 + 10 files changed, 230 insertions(+), 60 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 e3ff20ad2..0707ac2bc 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt @@ -1,16 +1,33 @@ package eu.kanade.presentation.manga +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.material3.AlertDialog +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Book +import androidx.compose.material.icons.outlined.SwapVert +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton 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 androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import eu.kanade.presentation.components.AdaptiveSheet +import eu.kanade.presentation.components.TabbedDialogPaddings +import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight +import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import tachiyomi.i18n.MR -import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource @Composable @@ -18,42 +35,92 @@ fun DuplicateMangaDialog( onDismissRequest: () -> Unit, onConfirm: () -> Unit, onOpenManga: () -> Unit, + onMigrate: () -> Unit, + modifier: Modifier = Modifier, ) { - AlertDialog( + val minHeight = LocalPreferenceMinHeight.current + + AdaptiveSheet( + modifier = modifier, onDismissRequest = onDismissRequest, - title = { - Text(text = stringResource(MR.strings.are_you_sure)) - }, - text = { - Text(text = stringResource(MR.strings.confirm_add_duplicate_manga)) - }, - confirmButton = { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), + ) { + Column( + modifier = Modifier + .padding( + vertical = TabbedDialogPaddings.Vertical, + horizontal = TabbedDialogPaddings.Horizontal, + ) + .fillMaxWidth(), + ) { + Text( + modifier = Modifier.padding(TitlePadding), + text = stringResource(MR.strings.are_you_sure), + style = MaterialTheme.typography.headlineMedium, + ) + + Text( + text = stringResource(MR.strings.confirm_add_duplicate_manga), + style = MaterialTheme.typography.bodyMedium, + ) + + Spacer(Modifier.height(PaddingSize)) + + TextPreferenceWidget( + title = stringResource(MR.strings.action_show_manga), + icon = Icons.Outlined.Book, + onPreferenceClick = { + onDismissRequest() + onOpenManga() + }, + ) + + HorizontalDivider() + + TextPreferenceWidget( + title = stringResource(MR.strings.action_migrate_duplicate), + icon = Icons.Outlined.SwapVert, + onPreferenceClick = { + onDismissRequest() + onMigrate() + }, + ) + + HorizontalDivider() + + TextPreferenceWidget( + title = stringResource(MR.strings.action_add_anyway), + icon = Icons.Outlined.Add, + onPreferenceClick = { + onDismissRequest() + onConfirm() + }, + ) + + Row( + modifier = Modifier + .sizeIn(minHeight = minHeight) + .clickable { onDismissRequest.invoke() } + .padding(ButtonPadding) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, ) { - TextButton( - onClick = { - onDismissRequest() - onOpenManga() - }, - ) { - Text(text = stringResource(MR.strings.action_show_manga)) - } - - Spacer(modifier = Modifier.weight(1f)) - - TextButton(onClick = onDismissRequest) { - Text(text = stringResource(MR.strings.action_cancel)) - } - TextButton( - onClick = { - onDismissRequest() - onConfirm() - }, - ) { - Text(text = stringResource(MR.strings.action_add)) + OutlinedButton(onClick = onDismissRequest, modifier = Modifier.fillMaxWidth()) { + Text( + modifier = Modifier + .padding(vertical = 8.dp), + text = stringResource(MR.strings.action_cancel), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.titleLarge, + fontSize = 16.sp, + ) } } - }, - ) + } + } } + +private val PaddingSize = 16.dp + +private val ButtonPadding = PaddingValues(top = 16.dp, bottom = 16.dp) +private val TitlePadding = PaddingValues(bottom = 16.dp, top = 8.dp) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt index 4a1690a6e..61a61a47f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt @@ -46,9 +46,15 @@ import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource +import java.io.Serializable import kotlin.math.roundToInt -class PreMigrationScreen(val mangaIds: List) : Screen() { +sealed class MigrationType : Serializable { + data class MangaList(val mangaIds: List) : MigrationType() + data class MangaSingle(val fromMangaId: Long, val toManga: Long?) : MigrationType() +} + +class PreMigrationScreen(val migration: MigrationType) : Screen() { @Composable override fun Content() { val screenModel = rememberScreenModel { PreMigrationScreenModel() } @@ -173,7 +179,7 @@ class PreMigrationScreen(val mangaIds: List) : Screen() { screenModel.onMigrationSheet(false) screenModel.saveEnabledSources() - navigator replace MigrationListScreen(MigrationProcedureConfig(mangaIds, extraParam)) + navigator replace MigrationListScreen(MigrationProcedureConfig(migration, extraParam)) }, ) } @@ -184,10 +190,22 @@ class PreMigrationScreen(val mangaIds: List) : Screen() { navigator.push( if (skipPre) { MigrationListScreen( - MigrationProcedureConfig(mangaIds, null), + MigrationProcedureConfig(MigrationType.MangaList(mangaIds), null), ) } else { - PreMigrationScreen(mangaIds) + PreMigrationScreen(MigrationType.MangaList(mangaIds)) + }, + ) + } + + fun navigateToMigration(skipPre: Boolean, navigator: Navigator, fromMangaId: Long, toManga: Long?) { + navigator.push( + if (skipPre) { + MigrationListScreen( + MigrationProcedureConfig(MigrationType.MangaSingle(fromMangaId, toManga), null), + ) + } else { + PreMigrationScreen(MigrationType.MangaSingle(fromMangaId, toManga)) }, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt index c570233e1..a44c95fd0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags +import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationType import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.SearchResult import eu.kanade.tachiyomi.util.system.toast import exh.eh.EHentaiThrottleManager @@ -104,8 +105,14 @@ class MigrationListScreenModel( init { screenModelScope.launchIO { + val mangaIds = when (val migration = config.migration) { + is MigrationType.MangaList -> { + migration.mangaIds + } + is MigrationType.MangaSingle -> listOf(migration.fromMangaId) + } runMigrations( - config.mangaIds + mangaIds .map { async { val manga = getManga.await(it) ?: return@async null @@ -161,9 +168,13 @@ class MigrationListScreenModel( break } // in case it was removed - if (manga.manga.id !in config.mangaIds) { - continue + when (val migration = config.migration) { + is MigrationType.MangaList -> if (manga.manga.id !in migration.mangaIds) { + continue + } + else -> Unit } + if (manga.searchResult.value == SearchResult.Searching && manga.migrationScope.isActive) { val mangaObj = manga.manga val mangaSource = sourceManager.getOrStub(mangaObj.source) @@ -175,6 +186,28 @@ class MigrationListScreenModel( } else { sources.filter { it.id != mangaSource.id } } + when (val migration = config.migration) { + is MigrationType.MangaSingle -> if (migration.toManga != null) { + val localManga = getManga.await(migration.toManga) + if (localManga != null) { + val source = sourceManager.get(localManga.source) as? CatalogueSource + if (source != null) { + val chapters = if (source is EHentai) { + source.getChapterList(localManga.toSManga(), throttleManager::throttle) + } else { + source.getChapterList(localManga.toSManga()) + } + try { + syncChaptersWithSource.await(chapters, localManga, source) + } catch (_: Exception) { + } + manga.progress.value = validSources.size to validSources.size + return@async localManga + } + } + } + else -> Unit + } if (useSourceWithMost) { val sourceSemaphore = Semaphore(3) val processedSources = AtomicInteger() @@ -523,13 +556,18 @@ class MigrationListScreenModel( } fun removeManga(item: MigratingManga) { - val ids = config.mangaIds.toMutableList() - val index = ids.indexOf(item.manga.id) - if (index > -1) { - ids.removeAt(index) - config.mangaIds = ids - val index2 = migratingItems.value.orEmpty().indexOf(item) - if (index2 > -1) migratingItems.value = (migratingItems.value.orEmpty() - item).toImmutableList() + when (val migration = config.migration) { + is MigrationType.MangaList -> { + val ids = migration.mangaIds.toMutableList() + val index = ids.indexOf(item.manga.id) + if (index > -1) { + ids.removeAt(index) + config.migration = MigrationType.MangaList(ids) + val index2 = migratingItems.value.orEmpty().indexOf(item) + if (index2 > -1) migratingItems.value = (migratingItems.value.orEmpty() - item).toImmutableList() + } + } + is MigrationType.MangaSingle -> Unit } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt index c0469b885..7a57710a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt @@ -1,8 +1,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process +import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationType import java.io.Serializable data class MigrationProcedureConfig( - var mangaIds: List, + var migration: MigrationType, val extraSearchParams: String?, ) : Serializable 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 d5ce76220..6e5bbbfb6 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 @@ -49,6 +49,7 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen +import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing import eu.kanade.tachiyomi.ui.category.CategoryScreen @@ -62,6 +63,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow import tachiyomi.core.common.Constants import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.source.model.StubSource import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold @@ -69,6 +71,8 @@ import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.source.local.LocalSource +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get data class BrowseSourceScreen( private val sourceId: Long, @@ -319,6 +323,16 @@ data class BrowseSourceScreen( onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onMigrate = { + // SY --> + PreMigrationScreen.navigateToMigration( + Injekt.get().skipPreMigration().get(), + navigator, + dialog.duplicate.id, + dialog.manga.id, + ) + // SY <-- + }, ) } is BrowseSourceScreenModel.Dialog.RemoveManga -> { 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 5d42384e0..9a0a85c04 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 @@ -455,7 +455,7 @@ open class BrowseSourceScreenModel( val manga: Manga, val initialSelection: ImmutableList>, ) : Dialog - data class Migrate(val newManga: Manga) : Dialog + data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog // SY --> data class DeleteSavedSearch(val idToDelete: Long, val name: String) : Dialog diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index b4bfba757..106c0f7bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -251,11 +251,19 @@ class MangaScreen( }, ) } - is MangaScreenModel.Dialog.DuplicateManga -> DuplicateMangaDialog( - onDismissRequest = onDismissRequest, - onConfirm = { screenModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) }, - onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, - ) + + is MangaScreenModel.Dialog.DuplicateManga -> { + DuplicateMangaDialog( + onDismissRequest = onDismissRequest, + onConfirm = { screenModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) }, + onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onMigrate = { + // SY --> + migrateManga(navigator, dialog.duplicate, screenModel.manga!!.id) + // SY <-- + }, + ) + } MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog( onDismissRequest = onDismissRequest, manga = successState.manga, @@ -457,12 +465,13 @@ class MangaScreen( /** * Initiates source migration for the specific manga. */ - private fun migrateManga(navigator: Navigator, manga: Manga) { + private fun migrateManga(navigator: Navigator, manga: Manga, toMangaId: Long? = null) { // SY --> PreMigrationScreen.navigateToMigration( Injekt.get().skipPreMigration().get(), navigator, - listOf(manga.id), + manga.id, + toMangaId, ) // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 33895fb5a..861f2067a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -1548,6 +1548,9 @@ class MangaScreenModel( ) : Dialog data class DeleteChapters(val chapters: List) : Dialog data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog + /* SY --> + data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog + SY <-- */ data class SetFetchInterval(val manga: Manga) : Dialog // SY --> @@ -1580,6 +1583,12 @@ class MangaScreenModel( updateSuccessState { it.copy(dialog = Dialog.FullCover) } } + /* SY --> + fun showMigrateDialog(duplicate: Manga) { + val manga = successState?.manga ?: return + updateSuccessState { it.copy(dialog = Dialog.Migrate(newManga = manga, oldManga = duplicate)) } + } SY <-- */ + fun setExcludedScanlators(excludedScanlators: Set) { screenModelScope.launchIO { setExcludedScanlators.await(mangaId, excludedScanlators) diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt index ad93b5a57..9a2aa6f42 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt @@ -20,15 +20,19 @@ import eu.kanade.presentation.browse.components.RemoveMangaDialog import eu.kanade.presentation.category.components.ChangeCategoryDialog import eu.kanade.presentation.manga.DuplicateMangaDialog import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import exh.ui.ifSourcesLoaded import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.domain.UnsortedPreferences import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { @@ -104,6 +108,14 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onMigrate = { + PreMigrationScreen.navigateToMigration( + Injekt.get().skipPreMigration().get(), + navigator, + dialog.duplicate.id, + dialog.manga.id, + ) + } ) } is BrowseSourceScreenModel.Dialog.RemoveManga -> { diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index d01df7e72..39cfa134c 100755 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -162,6 +162,8 @@ Refresh Start downloading now Not now + Add anyway + Migrate existing entry Loading…