komikku/app/src/main/java/mihon/feature/migration/dialog/MigrateMangaDialog.kt
AntsyLich e79b3f97f4
Fix migration dialog migrating to wrong entry (mihonapp/mihon#2631)
(cherry picked from commit 5e7fecc2c11b4a175fe1c3f698f7daeb58fe311f)
2026-01-21 19:48:12 +07:00

195 lines
7.2 KiB
Kotlin

package mihon.feature.migration.dialog
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastForEach
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import kotlinx.coroutines.flow.update
import mihon.domain.migration.models.MigrationFlag
import mihon.domain.migration.usecases.MigrateMangaUseCase
import mihon.feature.common.utils.getLabel
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.LoadingScreen
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
internal fun Screen.MigrateMangaDialog(
current: Manga,
target: Manga,
onClickTitle: () -> Unit,
onDismissRequest: () -> Unit,
onComplete: () -> Unit = onDismissRequest,
) {
val scope = rememberCoroutineScope()
val screenModel = rememberScreenModel { MigrateDialogScreenModel() }
LaunchedEffect(current, target) {
screenModel.init(current, target)
}
val state by screenModel.state.collectAsState()
if (state.isMigrated) return
if (state.isMigrating) {
LoadingScreen(
modifier = Modifier.background(MaterialTheme.colorScheme.background.copy(alpha = 0.7f)),
)
return
}
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(MR.strings.migration_dialog_what_to_include))
},
text = {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
) {
state.applicableFlags.fastForEach { flag ->
LabeledCheckbox(
label = stringResource(flag.getLabel()),
checked = flag in state.selectedFlags,
onCheckedChange = { screenModel.toggleSelection(flag) },
)
}
}
},
confirmButton = {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
) {
TextButton(
onClick = {
// KMK -->
// Allow `migrate` mangas when using `Bulk-favorite`
// onDismissRequest()
// KMK <--
onClickTitle()
},
) {
Text(text = stringResource(MR.strings.action_show_manga))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(
onClick = {
scope.launchIO {
screenModel.migrateManga(replace = false)
withUIContext { onComplete() }
}
},
) {
Text(text = stringResource(MR.strings.copy))
}
TextButton(
onClick = {
scope.launchIO {
screenModel.migrateManga(replace = true)
withUIContext { onComplete() }
}
},
) {
Text(text = stringResource(MR.strings.migrate))
}
}
},
)
}
private class MigrateDialogScreenModel(
val sourcePreference: SourcePreferences = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val migrateManga: MigrateMangaUseCase = Injekt.get(),
) : StateScreenModel<MigrateDialogScreenModel.State>(State()) {
fun init(current: Manga, target: Manga) {
val applicableFlags = buildList {
MigrationFlag.entries.forEach {
val applicable = when (it) {
MigrationFlag.CHAPTER -> true
MigrationFlag.CATEGORY -> true
// KMK -->
MigrationFlag.TRACK -> true
// KMK <--
MigrationFlag.CUSTOM_COVER -> current.hasCustomCover(coverCache)
MigrationFlag.NOTES -> current.notes.isNotBlank()
MigrationFlag.REMOVE_DOWNLOAD -> downloadManager.getDownloadCount(current) > 0
// KMK -->
MigrationFlag.EXTRA -> true
// KMK <--
}
if (applicable) add(it)
}
}
val selectedFlags = sourcePreference.migrationFlags().get()
mutableState.update {
it.copy(
current = current,
target = target,
applicableFlags = applicableFlags,
selectedFlags = selectedFlags,
)
}
}
fun toggleSelection(flag: MigrationFlag) {
mutableState.update {
val selectedFlags = it.selectedFlags.toMutableSet()
.apply { if (contains(flag)) remove(flag) else add(flag) }
.toSet()
it.copy(selectedFlags = selectedFlags)
}
}
suspend fun migrateManga(replace: Boolean) {
val state = state.value
val current = state.current ?: return
val target = state.target ?: return
// KMK -->
// sourcePreference.migrationFlags().set(state.selectedFlags)
// KMK <--
mutableState.update { it.copy(isMigrating = true) }
migrateManga(current, target, replace, /* KMK --> */ state.selectedFlags /* KMK <-- */)
mutableState.update { it.copy(isMigrating = false, isMigrated = true) }
}
data class State(
val current: Manga? = null,
val target: Manga? = null,
val applicableFlags: List<MigrationFlag> = emptyList(),
val selectedFlags: Set<MigrationFlag> = emptySet(),
val isMigrating: Boolean = false,
val isMigrated: Boolean = false,
)
}