diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index 5f88b0454..8bb574893 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -22,8 +22,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()) @@ -60,12 +58,20 @@ 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) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt index 6677b39a4..6baeda597 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt @@ -71,22 +71,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), - ), - ), ) } } diff --git a/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt b/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt index 689be9cd1..8fb2daf19 100644 --- a/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt +++ b/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt @@ -22,7 +22,6 @@ import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme 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 @@ -81,18 +80,11 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() { var migrationSheetOpen by rememberSaveable { mutableStateOf(false) } - fun continueMigration(openSheet: Boolean) { - if (openSheet) { - migrationSheetOpen = true - return - } + fun continueMigration(openSheet: Boolean, extraSearchQuery: String?) { navigator.replace(MigrateSearchScreen(mangaId)) } if (state.isLoading) { - LaunchedEffect(state.skipMigrationConfig) { - if (state.skipMigrationConfig) continueMigration(openSheet = false) - } LoadingScreen() return } @@ -143,7 +135,7 @@ class MigrationConfigScreen(private val mangaId: 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(), ) @@ -204,9 +196,9 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() { MigrationConfigScreenSheet( preferences = screenModel.sourcePreferences, onDismissRequest = { migrationSheetOpen = false }, - onStartMigration = { + onStartMigration = { extraSearchQuery -> migrationSheetOpen = false - continueMigration(openSheet = false) + continueMigration(openSheet = false, extraSearchQuery = extraSearchQuery) }, ) } @@ -312,9 +304,6 @@ class MigrationConfigScreen(private val mangaId: 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) } } @@ -414,7 +403,6 @@ class MigrationConfigScreen(private val mangaId: Long) : Screen() { data class State( val isLoading: Boolean = true, - val skipMigrationConfig: Boolean = false, val sources: List = emptyList(), ) diff --git a/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreenSheet.kt b/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreenSheet.kt index a53aa56d3..e9288142b 100644 --- a/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreenSheet.kt +++ b/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreenSheet.kt @@ -3,60 +3,166 @@ 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 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.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() Button( - onClick = onStartMigration, + onClick = { onStartMigration(extraSearchQuery) }, modifier = Modifier .fillMaxWidth() .padding( @@ -71,17 +177,65 @@ fun MigrationConfigScreenSheet( } @Composable -private fun MigrationSheetItem( +private fun MigrationSheetSwitchItem( title: String, subtitle: String?, - action: @Composable () -> Unit, + preference: Preference, +) { + 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, ) { 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, + modifier = Modifier, + ) + }, + colors = ListItemDefaults.colors(containerColor = Color.Transparent), + ) +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 34f5860e6..484611076 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -1004,10 +1004,16 @@ Select enabled sources Select pinned sources Continue - Skip migration config - To show this screen again in the future, disable skipping in Settings → Browse - - Migration - Skip migration config - Use last used sources and preferences for migration + Data to migrate + Delete downloads after migration + Additional search keywords + e.g. language:english + Appends keywords to the search query + Hide unmatched manga + Settings below can be slower and may lead to temporary source restrictions + Hide if no new chapters + Show only if the match offers newer chapters + Deep search mode + More detailed keyword search + Prioritize by newer chapters