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

This commit is contained in:
AntsyLich
2025-06-12 04:18:13 +06:00
committed by GitHub
parent 0290a2d815
commit 288f577a45
5 changed files with 197 additions and 59 deletions

View File

@@ -22,8 +22,6 @@ class SourcePreferences(
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages()) fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet()) fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet()) fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
@@ -60,12 +58,20 @@ class SourcePreferences(
false, false,
) )
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
fun migrationFlags() = preferenceStore.getObjectFromInt( fun migrationFlags() = preferenceStore.getObjectFromInt(
key = "migrate_flags", key = "migration_flags",
defaultValue = MigrationFlag.entries.toSet(), defaultValue = MigrationFlag.entries.toSet(),
serializer = { MigrationFlag.toBit(it) }, serializer = { MigrationFlag.toBit(it) },
deserializer = { value: Int -> MigrationFlag.fromBit(value) }, 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)
} }

View File

@@ -71,22 +71,6 @@ object SettingsBrowseScreen : SearchableSettings {
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.parental_controls_info)), 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

@@ -22,7 +22,6 @@ import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -81,18 +80,11 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() {
var migrationSheetOpen by rememberSaveable { mutableStateOf(false) } var migrationSheetOpen by rememberSaveable { mutableStateOf(false) }
fun continueMigration(openSheet: Boolean) { fun continueMigration(openSheet: Boolean, extraSearchQuery: String?) {
if (openSheet) {
migrationSheetOpen = true
return
}
navigator.replace(MigrateSearchScreen(mangaId)) navigator.replace(MigrateSearchScreen(mangaId))
} }
if (state.isLoading) { if (state.isLoading) {
LaunchedEffect(state.skipMigrationConfig) {
if (state.skipMigrationConfig) continueMigration(openSheet = false)
}
LoadingScreen() LoadingScreen()
return return
} }
@@ -143,7 +135,7 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() {
icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) }, icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) },
onClick = { onClick = {
screenModel.saveSources() screenModel.saveSources()
continueMigration(openSheet = true) continueMigration(openSheet = true, extraSearchQuery = null)
}, },
expanded = lazyListState.shouldExpandFAB(), expanded = lazyListState.shouldExpandFAB(),
) )
@@ -204,9 +196,9 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() {
MigrationConfigScreenSheet( MigrationConfigScreenSheet(
preferences = screenModel.sourcePreferences, preferences = screenModel.sourcePreferences,
onDismissRequest = { migrationSheetOpen = false }, onDismissRequest = { migrationSheetOpen = false },
onStartMigration = { onStartMigration = { extraSearchQuery ->
migrationSheetOpen = false migrationSheetOpen = false
continueMigration(openSheet = false) continueMigration(openSheet = false, extraSearchQuery = extraSearchQuery)
}, },
) )
} }
@@ -312,9 +304,6 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() {
init { init {
screenModelScope.launchIO { screenModelScope.launchIO {
val skipMigrationConfig = sourcePreferences.skipMigrationConfig().get()
mutableState.update { it.copy(skipMigrationConfig = skipMigrationConfig) }
if (skipMigrationConfig) return@launchIO
initSources() initSources()
mutableState.update { it.copy(isLoading = false) } mutableState.update { it.copy(isLoading = false) }
} }
@@ -414,7 +403,6 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() {
data class State( data class State(
val isLoading: Boolean = true, val isLoading: Boolean = true,
val skipMigrationConfig: Boolean = false,
val sources: List<MigrationSource> = emptyList(), val sources: List<MigrationSource> = emptyList(),
) )

View File

@@ -3,60 +3,166 @@ package mihon.feature.migration.config
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll 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.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue 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.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.components.AdaptiveSheet 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.core.common.preference.toggle
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.theme.active
import tachiyomi.presentation.core.theme.header
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
@Composable @Composable
fun MigrationConfigScreenSheet( fun MigrationConfigScreenSheet(
preferences: SourcePreferences, preferences: SourcePreferences,
onDismissRequest: () -> Unit, 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) { AdaptiveSheet(onDismissRequest = onDismissRequest) {
Column(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.fillMaxWidth()) {
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f, fill = false)
.fillMaxWidth() .fillMaxWidth()
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState())
verticalArrangement = Arrangement.spacedBy(8.dp), .padding(top = MaterialTheme.padding.medium),
) { ) {
MigrationSheetItem( Text(
title = stringResource(MR.strings.migrationConfigScreen_skipMigrationConfigTitle), text = stringResource(MR.strings.migrationConfigScreen_dataToMigrateHeader),
subtitle = stringResource(MR.strings.migrationConfigScreen_skipMigrationConfigSubtitle), style = MaterialTheme.typography.header,
action = { modifier = Modifier
Switch( .fillMaxWidth()
checked = skipMigrationConfig, .padding(top = MaterialTheme.padding.extraSmall)
onCheckedChange = null, .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.forEach { flag ->
if (flag == MigrationFlag.REMOVE_DOWNLOAD) return@forEach
val selected = flag in migrationFlags
FilterChip(
selected = selected,
onClick = {
preferences.migrationFlags().getAndSet {
if (selected) {
it - flag
} else {
it + flag
}
}
},
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 {
if (removeDownloads) {
it - MigrationFlag.REMOVE_DOWNLOAD
} else {
it + MigrationFlag.REMOVE_DOWNLOAD
}
}
}, },
onClick = { preferences.skipMigrationConfig().toggle() }, )
MigrationSheetDividerItem()
OutlinedTextField(
value = extraSearchQuery,
onValueChange = { extraSearchQuery = it },
label = { Text(stringResource(MR.strings.migrationConfigScreen_additionalSearchQueryLabel)) },
placeholder = {
Text(stringResource(MR.strings.migrationConfigScreen_additionalSearchQueryPlaceholder))
},
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(),
)
MigrationSheetDividerItem()
MigrationSheetWarningItem(stringResource(MR.strings.migrationConfigScreen_enhancedOptionsWarning))
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_hideWithoutUpdatesTitle),
subtitle = stringResource(MR.strings.migrationConfigScreen_hideWithoutUpdatesSubtitle),
preference = preferences.migrationHideWithoutUpdates(),
)
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_deepSearchModeTitle),
subtitle = stringResource(MR.strings.migrationConfigScreen_deepSearchModeSubtitle),
preference = preferences.migrationDeepSearchMode(),
)
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_prioritizeByChaptersTitle),
subtitle = null,
preference = preferences.migrationPrioritizeByChapters(),
) )
} }
HorizontalDivider() HorizontalDivider()
Button( Button(
onClick = onStartMigration, onClick = { onStartMigration(extraSearchQuery) },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding( .padding(
@@ -71,17 +177,65 @@ fun MigrationConfigScreenSheet(
} }
@Composable @Composable
private fun MigrationSheetItem( private fun MigrationSheetSwitchItem(
title: String, title: String,
subtitle: String?, subtitle: String?,
action: @Composable () -> Unit, preference: Preference<Boolean>,
) {
val checked by preference.collectAsState()
MigrationSheetSwitchItem(
title = title,
subtitle = subtitle,
checked = checked,
onClick = { preference.toggle() },
)
}
@Composable
private fun MigrationSheetSwitchItem(
title: String,
subtitle: String?,
checked: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
ListItem( ListItem(
headlineContent = { Text(text = title) }, headlineContent = { Text(text = title) },
supportingContent = subtitle?.let { { Text(text = subtitle) } }, supportingContent = subtitle?.let { { Text(text = subtitle) } },
trailingContent = action, trailingContent = {
Switch(
checked = checked,
onCheckedChange = null,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent), colors = ListItemDefaults.colors(containerColor = Color.Transparent),
modifier = Modifier.clickable(onClick = onClick), 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,
modifier = Modifier,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}

View File

@@ -1004,10 +1004,16 @@
<string name="migrationConfigScreen.selectEnabledLabel">Select enabled sources</string> <string name="migrationConfigScreen.selectEnabledLabel">Select enabled sources</string>
<string name="migrationConfigScreen.selectPinnedLabel">Select pinned sources</string> <string name="migrationConfigScreen.selectPinnedLabel">Select pinned sources</string>
<string name="migrationConfigScreen.continueButtonText">Continue</string> <string name="migrationConfigScreen.continueButtonText">Continue</string>
<string name="migrationConfigScreen.skipMigrationConfigTitle">Skip migration config</string> <string name="migrationConfigScreen.dataToMigrateHeader">Data to migrate</string>
<string name="migrationConfigScreen.skipMigrationConfigSubtitle">To show this screen again in the future, disable skipping in Settings → Browse</string> <string name="migrationConfigScreen.removeDownloadsTitle">Delete downloads after migration</string>
<string name="migrationConfigScreen.additionalSearchQueryLabel">Additional search keywords</string>
<string name="browseSettingsScreen.migrationCategoryHeader">Migration</string> <string name="migrationConfigScreen.additionalSearchQueryPlaceholder">e.g. language:english</string>
<string name="browseSettingsScreen.skipMigrationConfigTitle">Skip migration config</string> <string name="migrationConfigScreen.additionalSearchQuerySupportingText">Appends keywords to the search query</string>
<string name="browseSettingsScreen.skipMigrationConfigSubtitle">Use last used sources and preferences for migration</string> <string name="migrationConfigScreen.hideUnmatchedTitle">Hide unmatched manga</string>
<string name="migrationConfigScreen.enhancedOptionsWarning">Settings below can be slower and may lead to temporary source restrictions</string>
<string name="migrationConfigScreen.hideWithoutUpdatesTitle">Hide if no new chapters</string>
<string name="migrationConfigScreen.hideWithoutUpdatesSubtitle">Show only if the match offers newer chapters</string>
<string name="migrationConfigScreen.deepSearchModeTitle">Deep search mode</string>
<string name="migrationConfigScreen.deepSearchModeSubtitle">More detailed keyword search</string>
<string name="migrationConfigScreen.prioritizeByChaptersTitle">Prioritize by newer chapters</string>
</resources> </resources>