Add more migration config options and remove skipping option (mihonapp/mihon#2193)

(cherry picked from commit 288f577a45a6835c34ad41caab95794f164b7a0b)

* Further tweak migration config screen sheet

(cherry picked from commit 019fc08da2392725c36e064ffadc4e645edafb63)

Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
This commit is contained in:
AntsyLich 2025-06-12 04:18:13 +06:00 committed by Cuong-Tran
parent 2236e94f50
commit 42a97bb918
8 changed files with 224 additions and 135 deletions

View file

@ -23,8 +23,6 @@ class SourcePreferences(
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
@ -71,14 +69,22 @@ class SourcePreferences(
false,
)
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
fun migrationFlags() = preferenceStore.getObjectFromInt(
key = "migrate_flags",
key = "migration_flags",
defaultValue = MigrationFlag.entries.toSet(),
serializer = { MigrationFlag.toBit(it) },
deserializer = { value: Int -> MigrationFlag.fromBit(value) },
)
fun skipMigrationConfig() = preferenceStore.getBoolean(Preference.appStateKey("skip_migration_config"), false)
fun migrationDeepSearchMode() = preferenceStore.getBoolean("migration_deep_search", false)
fun migrationPrioritizeByChapters() = preferenceStore.getBoolean("migration_prioritize_by_chapters", false)
fun migrationHideUnmatched() = preferenceStore.getBoolean("migration_hide_unmatched", false)
fun migrationHideWithoutUpdates() = preferenceStore.getBoolean("migration_hide_without_updates", false)
// KMK -->
fun globalSearchPinnedState() = preferenceStore.getEnum(
@ -122,14 +128,6 @@ class SourcePreferences(
WSRV_NL,
}
fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false)
fun useSourceWithMost() = preferenceStore.getBoolean("use_source_with_most", false)
fun hideNotFoundMigration() = preferenceStore.getBoolean("hide_not_found_migration", false)
fun showOnlyUpdatesMigration() = preferenceStore.getBoolean("show_only_updates_migration", false)
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")

View file

@ -166,22 +166,6 @@ object SettingsBrowseScreen : SearchableSettings {
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)),
),
),
getMigrationCategory(sourcePreferences),
)
}
@Composable
fun getMigrationCategory(sourcePreferences: SourcePreferences): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
stringResource(MR.strings.browseSettingsScreen_migrationCategoryHeader),
enabled = sourcePreferences.skipMigrationConfig().isSet(),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.skipMigrationConfig(),
title = stringResource(MR.strings.browseSettingsScreen_skipMigrationConfigTitle),
subtitle = stringResource(MR.strings.browseSettingsScreen_skipMigrationConfigSubtitle),
),
),
)
}
}

View file

@ -4,7 +4,6 @@ import android.view.LayoutInflater
import android.widget.CompoundButton
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.rememberScrollState
@ -21,13 +20,11 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.theme.colorscheme.AndroidViewColorScheme
import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding
import eu.kanade.tachiyomi.util.system.toast
import mihon.domain.migration.models.MigrationFlag
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.util.lang.toLong
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import uy.kohesive.injekt.injectLazy
@Composable
@ -85,7 +82,6 @@ fun MigrationBottomSheetDialog(
listOf(
useSmartSearch,
extraSearchParam,
skipStep,
hideNotFoundManga,
onlyShowUpdates,
).forEach {
@ -137,29 +133,19 @@ class MigrationBottomSheetDialogState(
checkBox.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
}
useSmartSearch.bindToPreference(preferences.smartMigration())
useSmartSearch.bindToPreference(preferences.migrationDeepSearchMode())
extraSearchParamInputLayout.isVisible = false
extraSearchParam.setOnCheckedChangeListener { _, isChecked ->
extraSearchParamInputLayout.isVisible = isChecked
}
sourceGroup.bindToPreference(preferences.useSourceWithMost())
sourceGroup.bindToPreference(preferences.migrationPrioritizeByChapters())
skipStep.isChecked = preferences.skipMigrationConfig().get()
hideNotFoundManga.isChecked = preferences.hideNotFoundMigration().get()
onlyShowUpdates.isChecked = preferences.showOnlyUpdatesMigration().get()
skipStep.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
root.context.toast(
SYMR.strings.pre_migration_skip_toast,
Toast.LENGTH_LONG,
)
}
}
hideNotFoundManga.isChecked = preferences.migrationHideUnmatched().get()
onlyShowUpdates.isChecked = preferences.migrationHideWithoutUpdates().get()
migrateBtn.setOnClickListener {
preferences.skipMigrationConfig().set(skipStep.isChecked)
preferences.hideNotFoundMigration().set(hideNotFoundManga.isChecked)
preferences.showOnlyUpdatesMigration().set(onlyShowUpdates.isChecked)
preferences.migrationHideUnmatched().set(hideNotFoundManga.isChecked)
preferences.migrationHideWithoutUpdates().set(onlyShowUpdates.isChecked)
onStartMigration.value(
if (useSmartSearch.isChecked && !extraSearchParamText.text.isNullOrBlank()) {
extraSearchParamText.text.toString()
@ -175,7 +161,6 @@ class MigrationBottomSheetDialogState(
extraSearchParam.isVisible = false
extraSearchParamInputLayout.isVisible = false
sourceGroup.isVisible = false
skipStep.isVisible = false
migrateBtn.text = root.context.stringResource(MR.strings.action_save)
}
// KMK <--

View file

@ -69,10 +69,10 @@ class MigrationListScreenModel(
val manualMigrations = MutableStateFlow(0)
var hideNotFound = preferences.hideNotFoundMigration().get()
private var showOnlyUpdates = preferences.showOnlyUpdatesMigration().get()
private var useSourceWithMost = preferences.useSourceWithMost().get()
private var useSmartSearch = preferences.smartMigration().get()
var hideNotFound = preferences.migrationHideUnmatched().get()
private var showOnlyUpdates = preferences.migrationHideWithoutUpdates().get()
private var useSourceWithMost = preferences.migrationPrioritizeByChapters().get()
private var useSmartSearch = preferences.migrationDeepSearchMode().get()
val navigateOut = MutableSharedFlow<Unit>()
@ -498,10 +498,10 @@ class MigrationListScreenModel(
}
fun updateOptions() {
hideNotFound = preferences.hideNotFoundMigration().get()
showOnlyUpdates = preferences.showOnlyUpdatesMigration().get()
useSourceWithMost = preferences.useSourceWithMost().get()
useSmartSearch = preferences.smartMigration().get()
hideNotFound = preferences.migrationHideUnmatched().get()
showOnlyUpdates = preferences.migrationHideWithoutUpdates().get()
useSourceWithMost = preferences.migrationPrioritizeByChapters().get()
useSmartSearch = preferences.migrationDeepSearchMode().get()
}
// KMK <--

View file

@ -30,7 +30,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@ -59,7 +58,6 @@ import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.SOURCE_SEARCH_BOX_HEIGHT
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationBottomSheetDialog
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationType
@ -111,38 +109,24 @@ class MigrationConfigScreen(private val mangaIds: List<Long>) : Screen() {
val state by screenModel.state.collectAsState()
var migrationSheetOpen by rememberSaveable { mutableStateOf(false) }
// KMK -->
var migrationBottomSheetDialog by rememberSaveable { mutableStateOf(false) }
fun continueMigrationBottom(openSheet: Boolean, extraParam: String? = null) {
if (openSheet) {
migrationBottomSheetDialog = true
return
}
navigator.replace(
// KMK -->
MigrationListScreen(
MigrationProcedureConfig(MigrationType.MangaList(mangaIds), extraParam),
),
// KMK <--
)
}
// KMK <--
fun continueMigration(openSheet: Boolean) {
fun continueMigration(openSheet: Boolean, extraSearchQuery: String?) {
if (openSheet) {
migrationSheetOpen = true
return
}
navigator.replace(
// KMK -->
continueMigrationBottom(true)
// MigrateSearchScreen(mangaId)
MigrationListScreen(
MigrationProcedureConfig(MigrationType.MangaList(mangaIds), extraSearchQuery),
),
// KMK <--
)
}
if (state.isLoading) {
LaunchedEffect(state.skipMigrationConfig) {
if (state.skipMigrationConfig) continueMigration(openSheet = false)
}
LoadingScreen()
return
}
@ -213,7 +197,7 @@ class MigrationConfigScreen(private val mangaIds: List<Long>) : Screen() {
icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) },
onClick = {
screenModel.saveSources()
continueMigration(openSheet = true)
continueMigration(openSheet = true, extraSearchQuery = null)
},
expanded = lazyListState.shouldExpandFAB(),
)
@ -305,24 +289,12 @@ class MigrationConfigScreen(private val mangaIds: List<Long>) : Screen() {
MigrationConfigScreenSheet(
preferences = screenModel.sourcePreferences,
onDismissRequest = { migrationSheetOpen = false },
onStartMigration = {
onStartMigration = { extraSearchQuery ->
migrationSheetOpen = false
continueMigration(openSheet = false)
continueMigration(openSheet = false, extraSearchQuery = extraSearchQuery)
},
)
}
// KMK -->
if (migrationBottomSheetDialog) {
MigrationBottomSheetDialog(
onDismissRequest = { migrationBottomSheetDialog = false },
onStartMigration = { extraParam ->
migrationBottomSheetDialog = false
continueMigrationBottom(openSheet = false, extraParam)
},
)
}
// KMK <--
}
@Composable
@ -432,9 +404,6 @@ class MigrationConfigScreen(private val mangaIds: List<Long>) : Screen() {
init {
screenModelScope.launchIO {
val skipMigrationConfig = sourcePreferences.skipMigrationConfig().get()
mutableState.update { it.copy(skipMigrationConfig = skipMigrationConfig) }
if (skipMigrationConfig) return@launchIO
initSources()
mutableState.update { it.copy(isLoading = false) }
}
@ -543,7 +512,6 @@ class MigrationConfigScreen(private val mangaIds: List<Long>) : Screen() {
data class State(
val isLoading: Boolean = true,
val skipMigrationConfig: Boolean = false,
val sources: List<MigrationSource> = emptyList(),
)

View file

@ -3,60 +3,171 @@ package mihon.feature.migration.config
import androidx.compose.foundation.clickable
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.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import eu.kanade.core.util.fastFilterNot
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.components.AdaptiveSheet
import mihon.domain.migration.models.MigrationFlag
import mihon.feature.common.utils.getLabel
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.preference.toggle
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.theme.active
import tachiyomi.presentation.core.theme.header
import tachiyomi.presentation.core.util.collectAsState
@Composable
fun MigrationConfigScreenSheet(
preferences: SourcePreferences,
onDismissRequest: () -> Unit,
onStartMigration: () -> Unit,
onStartMigration: (extraSearchQuery: String?) -> Unit,
) {
val skipMigrationConfig by preferences.skipMigrationConfig().collectAsState()
var extraSearchQuery by rememberSaveable { mutableStateOf("") }
val migrationFlags by preferences.migrationFlags().collectAsState()
AdaptiveSheet(onDismissRequest = onDismissRequest) {
Column(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.weight(1f, fill = false)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp),
.verticalScroll(rememberScrollState())
.padding(top = MaterialTheme.padding.medium),
) {
MigrationSheetItem(
title = stringResource(MR.strings.migrationConfigScreen_skipMigrationConfigTitle),
subtitle = stringResource(MR.strings.migrationConfigScreen_skipMigrationConfigSubtitle),
action = {
Switch(
checked = skipMigrationConfig,
onCheckedChange = null,
Text(
text = stringResource(MR.strings.migrationConfigScreen_dataToMigrateHeader),
style = MaterialTheme.typography.header,
modifier = Modifier
.fillMaxWidth()
.padding(top = MaterialTheme.padding.extraSmall)
.padding(horizontal = MaterialTheme.padding.medium),
)
Spacer(modifier = Modifier.height(MaterialTheme.padding.extraSmall))
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = MaterialTheme.padding.medium)
.padding(bottom = MaterialTheme.padding.extraSmall),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
MigrationFlag.entries
.fastFilterNot { it == MigrationFlag.REMOVE_DOWNLOAD }
.fastForEach { flag ->
val selected = flag in migrationFlags
FilterChip(
selected = selected,
onClick = {
preferences.migrationFlags().getAndSet { currentFlags ->
if (flag in currentFlags) {
currentFlags - flag
} else {
currentFlags + flag
}
}
},
onClick = { preferences.skipMigrationConfig().toggle() },
label = { Text(stringResource(flag.getLabel())) },
leadingIcon = {
if (selected) {
Icon(
imageVector = Icons.Outlined.Check,
contentDescription = null,
)
}
},
)
}
}
val removeDownloads = MigrationFlag.REMOVE_DOWNLOAD in migrationFlags
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_removeDownloadsTitle),
subtitle = null,
checked = removeDownloads,
onClick = {
preferences.migrationFlags().getAndSet {
// KMK -->
if (MigrationFlag.REMOVE_DOWNLOAD in it) {
// KMK <--
it - MigrationFlag.REMOVE_DOWNLOAD
} else {
it + MigrationFlag.REMOVE_DOWNLOAD
}
}
},
)
MigrationSheetDividerItem()
OutlinedTextField(
value = extraSearchQuery,
onValueChange = { extraSearchQuery = it },
label = { Text(stringResource(MR.strings.migrationConfigScreen_additionalSearchQueryLabel)) },
supportingText = {
Text(stringResource(MR.strings.migrationConfigScreen_additionalSearchQuerySupportingText))
},
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.extraSmall,
),
)
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_hideUnmatchedTitle),
subtitle = null,
preference = preferences.migrationHideUnmatched(),
)
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_hideWithoutUpdatesTitle),
subtitle = stringResource(MR.strings.migrationConfigScreen_hideWithoutUpdatesSubtitle),
preference = preferences.migrationHideWithoutUpdates(),
)
MigrationSheetDividerItem()
MigrationSheetWarningItem(stringResource(MR.strings.migrationConfigScreen_enhancedOptionsWarning))
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_deepSearchModeTitle),
subtitle = stringResource(MR.strings.migrationConfigScreen_deepSearchModeSubtitle),
preference = preferences.migrationDeepSearchMode(),
)
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_prioritizeByChaptersTitle),
subtitle = stringResource(MR.strings.migrationConfigScreen_prioritizeByChaptersSubtitle),
preference = preferences.migrationPrioritizeByChapters(),
)
}
HorizontalDivider()
Button(
onClick = onStartMigration,
onClick = {
val cleanedExtraSearchQuery = extraSearchQuery.trim().ifBlank { null }
onStartMigration(cleanedExtraSearchQuery)
},
modifier = Modifier
.fillMaxWidth()
.padding(
@ -71,17 +182,63 @@ fun MigrationConfigScreenSheet(
}
@Composable
private fun MigrationSheetItem(
private fun MigrationSheetSwitchItem(
title: String,
subtitle: String?,
action: @Composable () -> Unit,
preference: Preference<Boolean>,
) {
MigrationSheetSwitchItem(
title = title,
subtitle = subtitle,
checked = preference.collectAsState().value,
onClick = { preference.toggle() },
)
}
@Composable
private fun MigrationSheetSwitchItem(
title: String,
subtitle: String?,
checked: Boolean,
onClick: () -> Unit,
) {
ListItem(
headlineContent = { Text(text = title) },
supportingContent = subtitle?.let { { Text(text = subtitle) } },
trailingContent = action,
trailingContent = {
Switch(
checked = checked,
onCheckedChange = null,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
modifier = Modifier.clickable(onClick = onClick),
)
}
@Composable
private fun MigrationSheetDividerItem() {
HorizontalDivider(modifier = Modifier.padding(vertical = MaterialTheme.padding.extraSmall))
}
@Composable
private fun MigrationSheetWarningItem(
text: String,
) {
ListItem(
leadingContent = {
Icon(
imageVector = Icons.Outlined.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.active,
)
},
headlineContent = {
Text(
text = text,
color = MaterialTheme.colorScheme.error,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}

View file

@ -189,15 +189,6 @@
android:textSize="14sp"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/skip_step"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/skip_this_step_next_time" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/hide_not_found_manga"
android:layout_width="match_parent"

View file

@ -1015,10 +1015,16 @@
<string name="migrationConfigScreen.selectEnabledLabel">Select enabled sources</string>
<string name="migrationConfigScreen.selectPinnedLabel">Select pinned sources</string>
<string name="migrationConfigScreen.continueButtonText">Continue</string>
<string name="migrationConfigScreen.skipMigrationConfigTitle">Skip migration config</string>
<string name="migrationConfigScreen.skipMigrationConfigSubtitle">To show this screen again in the future, disable skipping in Settings → Browse</string>
<string name="browseSettingsScreen.migrationCategoryHeader">Migration</string>
<string name="browseSettingsScreen.skipMigrationConfigTitle">Skip migration config</string>
<string name="browseSettingsScreen.skipMigrationConfigSubtitle">Use last used sources and preferences for migration</string>
<string name="migrationConfigScreen.dataToMigrateHeader">Data to migrate</string>
<string name="migrationConfigScreen.removeDownloadsTitle">Delete downloads of current entry after migration</string>
<string name="migrationConfigScreen.additionalSearchQueryLabel">Additional keywords (optional)</string>
<string name="migrationConfigScreen.additionalSearchQuerySupportingText">Helps narrow down search results by adding additional keywords</string>
<string name="migrationConfigScreen.hideUnmatchedTitle">Hide entries without a match</string>
<string name="migrationConfigScreen.hideWithoutUpdatesTitle">Hide entries without newer chapters</string>
<string name="migrationConfigScreen.hideWithoutUpdatesSubtitle">Only show entry if the match has additional chapters</string>
<string name="migrationConfigScreen.enhancedOptionsWarning">These options are slow and dangerous and may lead to restrictions from sources</string>
<string name="migrationConfigScreen.deepSearchModeTitle">Advanced search mode</string>
<string name="migrationConfigScreen.deepSearchModeSubtitle">Breaks down the title into keywords for a wider search</string>
<string name="migrationConfigScreen.prioritizeByChaptersTitle">Match based on chapter number</string>
<string name="migrationConfigScreen.prioritizeByChaptersSubtitle">If enabled, chooses the match furthest ahead. Otherwise, picks the first match by source priority.</string>
</resources>