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
This commit is contained in:
parent
ec30ccccc2
commit
2ea488bff5
@ -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),
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
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()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_show_manga))
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
HorizontalDivider()
|
||||
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
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()
|
||||
},
|
||||
) {
|
||||
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)
|
||||
|
@ -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<Long>) : Screen() {
|
||||
sealed class MigrationType : Serializable {
|
||||
data class MangaList(val mangaIds: List<Long>) : 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<Long>) : 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<Long>) : 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))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -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) {
|
||||
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,15 +556,20 @@ class MigrationListScreenModel(
|
||||
}
|
||||
|
||||
fun removeManga(item: MigratingManga) {
|
||||
val ids = config.mangaIds.toMutableList()
|
||||
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.mangaIds = ids
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDispose() {
|
||||
super.onDispose()
|
||||
|
@ -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<Long>,
|
||||
var migration: MigrationType,
|
||||
val extraSearchParams: String?,
|
||||
) : Serializable
|
||||
|
@ -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<UnsortedPreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
dialog.duplicate.id,
|
||||
dialog.manga.id,
|
||||
)
|
||||
// SY <--
|
||||
},
|
||||
)
|
||||
}
|
||||
is BrowseSourceScreenModel.Dialog.RemoveManga -> {
|
||||
|
@ -455,7 +455,7 @@ open class BrowseSourceScreenModel(
|
||||
val manga: Manga,
|
||||
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
||||
) : 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
|
||||
|
@ -251,11 +251,19 @@ class MangaScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
is MangaScreenModel.Dialog.DuplicateManga -> DuplicateMangaDialog(
|
||||
|
||||
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<UnsortedPreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
listOf(manga.id),
|
||||
manga.id,
|
||||
toMangaId,
|
||||
)
|
||||
// SY <--
|
||||
}
|
||||
|
@ -1548,6 +1548,9 @@ class MangaScreenModel(
|
||||
) : Dialog
|
||||
data class DeleteChapters(val chapters: List<Chapter>) : 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<String>) {
|
||||
screenModelScope.launchIO {
|
||||
setExcludedScanlators.await(mangaId, excludedScanlators)
|
||||
|
@ -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<UnsortedPreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
dialog.duplicate.id,
|
||||
dialog.manga.id,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
is BrowseSourceScreenModel.Dialog.RemoveManga -> {
|
||||
|
@ -162,6 +162,8 @@
|
||||
<string name="action_webview_refresh">Refresh</string>
|
||||
<string name="action_start_downloading_now">Start downloading 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 -->
|
||||
<string name="loading">Loading…</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user