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>
This commit is contained in:
Maddie Witman 2024-03-22 09:04:43 -04:00 committed by GitHub
parent 34930920a5
commit c0a888807b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 156 additions and 45 deletions

View File

@ -1,16 +1,33 @@
package eu.kanade.presentation.manga package eu.kanade.presentation.manga
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement 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.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.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
@ -18,42 +35,92 @@ fun DuplicateMangaDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onConfirm: () -> Unit, onConfirm: () -> Unit,
onOpenManga: () -> Unit, onOpenManga: () -> Unit,
onMigrate: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
AlertDialog( val minHeight = LocalPreferenceMinHeight.current
AdaptiveSheet(
modifier = modifier,
onDismissRequest = onDismissRequest, 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),
) { ) {
TextButton( Column(
onClick = { 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() onDismissRequest()
onOpenManga() onOpenManga()
}, },
) { )
Text(text = stringResource(MR.strings.action_show_manga))
}
Spacer(modifier = Modifier.weight(1f)) HorizontalDivider()
TextButton(onClick = onDismissRequest) { TextPreferenceWidget(
Text(text = stringResource(MR.strings.action_cancel)) title = stringResource(MR.strings.action_migrate_duplicate),
} icon = Icons.Outlined.SwapVert,
TextButton( onPreferenceClick = {
onClick = { onDismissRequest()
onMigrate()
},
)
HorizontalDivider()
TextPreferenceWidget(
title = stringResource(MR.strings.action_add_anyway),
icon = Icons.Outlined.Add,
onPreferenceClick = {
onDismissRequest() onDismissRequest()
onConfirm() onConfirm()
}, },
) {
Text(text = stringResource(MR.strings.action_add))
}
}
},
) )
Row(
modifier = Modifier
.sizeIn(minHeight = minHeight)
.clickable { onDismissRequest.invoke() }
.padding(ButtonPadding)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
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)

View File

@ -83,7 +83,7 @@ data class SourceSearchScreen(
) { paddingValues -> ) { paddingValues ->
val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState() val pagingFlow by screenModel.mangaPagerFlowFlow.collectAsState()
val openMigrateDialog: (Manga) -> Unit = { val openMigrateDialog: (Manga) -> Unit = {
screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(it)) screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(newManga = it, oldManga = oldManga))
} }
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,

View File

@ -47,6 +47,8 @@ import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialog
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialogScreenModel
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
@ -252,6 +254,22 @@ data class BrowseSourceScreen(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onConfirm = { screenModel.addFavorite(dialog.manga) }, onConfirm = { screenModel.addFavorite(dialog.manga) },
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
onMigrate = {
screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, dialog.duplicate))
},
)
}
is BrowseSourceScreenModel.Dialog.Migrate -> {
MigrateDialog(
oldManga = dialog.oldManga,
newManga = dialog.newManga,
screenModel = MigrateDialogScreenModel(),
onDismissRequest = onDismissRequest,
onClickTitle = { navigator.push(MangaScreen(dialog.oldManga.id)) },
onPopScreen = {
onDismissRequest()
},
) )
} }
is BrowseSourceScreenModel.Dialog.RemoveManga -> { is BrowseSourceScreenModel.Dialog.RemoveManga -> {
@ -274,7 +292,6 @@ data class BrowseSourceScreen(
}, },
) )
} }
is BrowseSourceScreenModel.Dialog.Migrate -> {}
else -> {} else -> {}
} }

View File

@ -345,7 +345,7 @@ class BrowseSourceScreenModel(
val manga: Manga, val manga: Manga,
val initialSelection: ImmutableList<CheckboxState.State<Category>>, val initialSelection: ImmutableList<CheckboxState.State<Category>>,
) : Dialog ) : Dialog
data class Migrate(val newManga: Manga) : Dialog data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog
} }
@Immutable @Immutable

View File

@ -41,6 +41,8 @@ import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.isLocalOrStub import eu.kanade.tachiyomi.source.isLocalOrStub
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialog
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialogScreenModel
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
@ -191,11 +193,28 @@ class MangaScreen(
}, },
) )
} }
is MangaScreenModel.Dialog.DuplicateManga -> DuplicateMangaDialog(
is MangaScreenModel.Dialog.DuplicateManga -> {
DuplicateMangaDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onConfirm = { screenModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) }, onConfirm = { screenModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) },
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
onMigrate = {
screenModel.showMigrateDialog(dialog.duplicate)
},
) )
}
is MangaScreenModel.Dialog.Migrate -> {
MigrateDialog(
oldManga = dialog.oldManga,
newManga = dialog.newManga,
screenModel = MigrateDialogScreenModel(),
onDismissRequest = onDismissRequest,
onClickTitle = { navigator.push(MangaScreen(dialog.oldManga.id)) },
onPopScreen = { navigator.replace(MangaScreen(dialog.newManga.id)) },
)
}
MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog( MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
manga = successState.manga, manga = successState.manga,

View File

@ -1003,6 +1003,7 @@ class MangaScreenModel(
) : Dialog ) : Dialog
data class DeleteChapters(val chapters: List<Chapter>) : Dialog data class DeleteChapters(val chapters: List<Chapter>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog
data class SetFetchInterval(val manga: Manga) : Dialog data class SetFetchInterval(val manga: Manga) : Dialog
data object SettingsSheet : Dialog data object SettingsSheet : Dialog
data object TrackSheet : Dialog data object TrackSheet : Dialog
@ -1029,6 +1030,11 @@ class MangaScreenModel(
updateSuccessState { it.copy(dialog = Dialog.FullCover) } updateSuccessState { it.copy(dialog = Dialog.FullCover) }
} }
fun showMigrateDialog(duplicate: Manga) {
val manga = successState?.manga ?: return
updateSuccessState { it.copy(dialog = Dialog.Migrate(newManga = manga, oldManga = duplicate)) }
}
fun setExcludedScanlators(excludedScanlators: Set<String>) { fun setExcludedScanlators(excludedScanlators: Set<String>) {
screenModelScope.launchIO { screenModelScope.launchIO {
setExcludedScanlators.await(mangaId, excludedScanlators) setExcludedScanlators.await(mangaId, excludedScanlators)

View File

@ -160,6 +160,8 @@
<string name="action_webview_refresh">Refresh</string> <string name="action_webview_refresh">Refresh</string>
<string name="action_start_downloading_now">Start downloading now</string> <string name="action_start_downloading_now">Start downloading now</string>
<string name="action_not_now">Not now</string> <string name="action_not_now">Not now</string>
<string name="action_add_anyway">Add anyway</string>
<string name="action_migrate_duplicate">Migrate existing entry</string>
<!-- Operations --> <!-- Operations -->
<string name="loading">Loading…</string> <string name="loading">Loading…</string>