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 1e0fda823..2dfb000d8 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 @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.getEnum +import tachiyomi.core.common.preference.getLongArray import tachiyomi.domain.library.model.LibraryDisplayMode class SourcePreferences( @@ -21,6 +22,8 @@ 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()) @@ -111,10 +114,6 @@ class SourcePreferences( fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE) - fun defaultMangaOrder() = preferenceStore.getString("default_manga_order", "") - - fun migrationSources() = preferenceStore.getString("migrate_sources", "") - fun smartMigration() = preferenceStore.getBoolean("smart_migrate", false) fun useSourceWithMost() = preferenceStore.getBoolean("use_source_with_most", false) diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt index 54ce83c29..89ef7324a 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt @@ -77,6 +77,7 @@ fun GlobalSearchScreen( navigateUp = navigateUp, onChangeSearchQuery = onChangeSearchQuery, onSearch = onSearch, + hideSourceFilter = false, sourceFilter = state.sourceFilter, onChangeSearchFilter = onChangeSearchFilter, onlyShowHasResults = state.onlyShowHasResults, diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt index 831c2afb0..915a082c7 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt @@ -67,6 +67,7 @@ fun MigrateSearchScreen( navigateUp = navigateUp, onChangeSearchQuery = onChangeSearchQuery, onSearch = onSearch, + hideSourceFilter = true, sourceFilter = state.sourceFilter, onChangeSearchFilter = onChangeSearchFilter, onlyShowHasResults = state.onlyShowHasResults, diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchToolbar.kt b/app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchToolbar.kt index cb4304833..a37fedd42 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchToolbar.kt @@ -42,6 +42,7 @@ fun GlobalSearchToolbar( navigateUp: () -> Unit, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, + hideSourceFilter: Boolean, sourceFilter: SourceFilter, onChangeSearchFilter: (SourceFilter) -> Unit, onlyShowHasResults: Boolean, @@ -89,42 +90,44 @@ fun GlobalSearchToolbar( horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { // TODO: make this UX better; it only applies when triggering a new search - // KMK --> - if (hasPinnedSources) { - // KMK <-- + if (!hideSourceFilter) { + // KMK --> + if (hasPinnedSources) { + // KMK <-- + FilterChip( + selected = sourceFilter == SourceFilter.PinnedOnly, + onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.PushPin, + contentDescription = null, + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(MR.strings.pinned_sources)) + }, + ) + } FilterChip( - selected = sourceFilter == SourceFilter.PinnedOnly, - onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) }, + selected = sourceFilter == SourceFilter.All, + onClick = { onChangeSearchFilter(SourceFilter.All) }, leadingIcon = { Icon( - imageVector = Icons.Outlined.PushPin, + imageVector = Icons.Outlined.DoneAll, contentDescription = null, modifier = Modifier .size(FilterChipDefaults.IconSize), ) }, label = { - Text(text = stringResource(MR.strings.pinned_sources)) + Text(text = stringResource(MR.strings.all)) }, ) - } - FilterChip( - selected = sourceFilter == SourceFilter.All, - onClick = { onChangeSearchFilter(SourceFilter.All) }, - leadingIcon = { - Icon( - imageVector = Icons.Outlined.DoneAll, - contentDescription = null, - modifier = Modifier - .size(FilterChipDefaults.IconSize), - ) - }, - label = { - Text(text = stringResource(MR.strings.all)) - }, - ) - VerticalDivider() + VerticalDivider() + } FilterChip( selected = onlyShowHasResults, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt index c514b8b54..6f58b324e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt @@ -320,7 +320,6 @@ class AboutScreen : Screen() { is GetApplicationRelease.Result.OsTooOld -> { context.toast(MR.strings.update_check_eol) } - else -> {} } } catch (e: Exception) { context.toast(e.message) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceAdapter.kt deleted file mode 100644 index a3b0376ab..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceAdapter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.kanade.domain.source.service.SourcePreferences -import tachiyomi.domain.source.service.SourceManager -import uy.kohesive.injekt.injectLazy - -class MigrationSourceAdapter( - listener: FlexibleAdapter.OnItemClickListener, -) : FlexibleAdapter( - null, - listener, - true, -) { - val sourceManager: SourceManager by injectLazy() - - // SY _-> - val sourcePreferences: SourcePreferences by injectLazy() - // SY <-- -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceHolder.kt deleted file mode 100644 index 10be35d54..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceHolder.kt +++ /dev/null @@ -1,49 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG -import android.view.View -import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.databinding.MigrationSourceItemBinding -import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.source.getNameForMangaInfo -import eu.kanade.tachiyomi.source.online.HttpSource -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) : - FlexibleViewHolder(view, adapter) { - val binding = MigrationSourceItemBinding.bind(view) - init { - setDragHandleView(binding.reorder) - } - - fun bind(source: HttpSource, sourceEnabled: Boolean) { - // Set capitalized title. - val sourceName = - // KMK --> - source.getNameForMangaInfo() - // KMK <-- - binding.title.text = sourceName - // Update circle letter image. - itemView.post { - val icon = Injekt.get().getAppIconForSource(source.id) - if (icon != null) { - binding.image.setImageDrawable(icon) - } - } - - if (sourceEnabled) { - binding.title.alpha = 1.0f - binding.image.alpha = 1.0f - binding.title.paintFlags = binding.title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() - } else { - binding.title.alpha = DISABLED_ALPHA - binding.image.alpha = DISABLED_ALPHA - binding.title.paintFlags = binding.title.paintFlags or STRIKE_THRU_TEXT_FLAG - } - } - - companion object { - private const val DISABLED_ALPHA = 0.3f - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceItem.kt deleted file mode 100644 index 59dd54fd4..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/MigrationSourceItem.kt +++ /dev/null @@ -1,74 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import android.os.Parcelable -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.parcelize.Parcelize -import tachiyomi.domain.source.service.SourceManager - -class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : AbstractFlexibleItem() { - override fun getLayoutRes() = R.layout.migration_source_item - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MigrationSourceHolder { - return MigrationSourceHolder(view, adapter as MigrationSourceAdapter) - } - - /** - * Binds the given view holder with this item. - * - * @param adapter The adapter of this item. - * @param holder The holder to bind. - * @param position The position of this item in the adapter. - * @param payloads List of partial changes. - */ - override fun bindViewHolder( - adapter: FlexibleAdapter>, - holder: MigrationSourceHolder, - position: Int, - payloads: List?, - ) { - holder.bind(source, sourceEnabled) - } - - /** - * Returns true if this item is draggable. - */ - override fun isDraggable(): Boolean { - return true - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other is MigrationSourceItem) { - return source.id == other.source.id - } - return false - } - - override fun hashCode(): Int { - return source.id.hashCode() - } - - @Parcelize - data class MigrationSource(val sourceId: Long, val sourceEnabled: Boolean) : Parcelable - - fun asParcelable(): MigrationSource { - return MigrationSource(source.id, sourceEnabled) - } - - companion object { - fun fromParcelable(sourceManager: SourceManager, migrationSource: MigrationSource): MigrationSourceItem? { - val source = sourceManager.get(migrationSource.sourceId) as? HttpSource ?: return null - - return MigrationSourceItem( - source, - migrationSource.sourceEnabled, - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt deleted file mode 100644 index de136e6a9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt +++ /dev/null @@ -1,409 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import android.view.LayoutInflater -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.ZeroCornerSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowForward -import androidx.compose.material.icons.outlined.Deselect -import androidx.compose.material.icons.outlined.PushPin -import androidx.compose.material.icons.outlined.SelectAll -import androidx.compose.material.icons.outlined.ToggleOn -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallExtendedFloatingActionButton -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.material3.ripple -import androidx.compose.material3.surfaceColorAtElevation -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.ViewCompat -import androidx.core.view.updatePadding -import androidx.recyclerview.widget.LinearLayoutManager -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.components.AppBar -import eu.kanade.presentation.components.SOURCE_SEARCH_BOX_HEIGHT -import eu.kanade.presentation.components.SourcesSearchBox -import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.databinding.PreMigrationListBinding -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import tachiyomi.i18n.MR -import tachiyomi.i18n.kmk.KMR -import tachiyomi.i18n.sy.SYMR -import tachiyomi.presentation.core.components.material.Scaffold -import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.i18n.stringResource -import java.io.Serializable -import kotlin.math.roundToInt -import kotlin.time.Duration.Companion.seconds - -sealed class MigrationType : Serializable { - data class MangaList(val mangaIds: List) : MigrationType() - data class MangaSingle(val fromMangaId: Long, val toManga: Long?) : MigrationType() -} - -/** - * The screen showing a list of selectable sources used for migration, with migration setting dialog. - */ -class PreMigrationScreen(val migration: MigrationType) : Screen() { - @Composable - override fun Content() { - val screenModel = rememberScreenModel { PreMigrationScreenModel() } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - val navigator = LocalNavigator.currentOrThrow - var fabExpanded by remember { mutableStateOf(true) } - val items by screenModel.state.collectAsState() - val adapter by screenModel.adapter.collectAsState() - // KMK --> - var searchQuery by remember { mutableStateOf("") } - BackHandler(enabled = searchQuery.isNotBlank()) { - searchQuery = "" - } - // KMK <-- - LaunchedEffect(items.isNotEmpty(), adapter != null/* KMK --> */, searchQuery/* KMK <-- */) { - if (adapter != null && items.isNotEmpty()) { - adapter?.updateDataSet( - items - // KMK --> - .filter { migrationSource -> - if (searchQuery.isBlank()) return@filter true - val source = migrationSource.source - searchQuery.split(",").any { - val input = it.trim() - if (input.isEmpty()) return@any false - source.name.contains(input, ignoreCase = true) || - source.id == input.toLongOrNull() - } - }, - // KMK <-- - ) - } - } - - val nestedScrollConnection = remember { - // All this lines just for fab state :/ - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - fabExpanded = available.y >= 0 - return scrollBehavior.nestedScrollConnection.onPreScroll(available, source) - } - - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source) - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPreFling(available) - } - - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available) - } - } - } - Scaffold( - topBar = { - AppBar( - title = stringResource(SYMR.strings.select_sources), - navigateUp = navigator::pop, - scrollBehavior = scrollBehavior, - ) - }, - // KMK --> - bottomBar = { - PreMigrationScreenBottomBar( - modifier = Modifier, - onSelectAll = { screenModel.massSelect(true) }, - onSelectNone = { screenModel.massSelect(false) }, - onSelectPinned = { screenModel.matchSelection(false) }, - onSelectEnabled = { screenModel.matchSelection(true) }, - ) - }, - // KMK <-- - floatingActionButton = { - SmallExtendedFloatingActionButton( - text = { Text(text = stringResource(MR.strings.action_migrate)) }, - icon = { - Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowForward, - contentDescription = stringResource(MR.strings.action_migrate), - ) - }, - onClick = { - screenModel.onMigrationSheet(true) - }, - expanded = fabExpanded, - ) - }, - ) { contentPadding -> - // KMK --> - val density = LocalDensity.current - Box(modifier = Modifier.padding(top = contentPadding.calculateTopPadding())) { - var searchBoxHeight by remember { mutableIntStateOf(with(density) { SOURCE_SEARCH_BOX_HEIGHT.roundToPx() }) } - // KMK <-- - val layoutDirection = LocalLayoutDirection.current - val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } - // KMK --> - val top = searchBoxHeight - // val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() } - // KMK <-- - val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() } - val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() } - Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { - AndroidView( - modifier = Modifier.fillMaxWidth(), - factory = { context -> - screenModel.controllerBinding = - PreMigrationListBinding.inflate(LayoutInflater.from(context)) - screenModel.adapter.value = MigrationSourceAdapter(screenModel.clickListener) - screenModel.controllerBinding.root.adapter = screenModel.adapter.value - screenModel.adapter.value?.isHandleDragEnabled = true - screenModel.controllerBinding.root.layoutManager = LinearLayoutManager(context) - - ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true) - - screenModel.controllerBinding.root - }, - update = { - screenModel.controllerBinding.root - .updatePadding( - left = left, - top = top, - right = right, - bottom = bottom, - ) - }, - ) - } - // KMK --> - SourcesSearchBox( - searchQuery = searchQuery, - onChangeSearchQuery = { searchQuery = it ?: "" }, - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = MaterialTheme.padding.small), - onGloballyPositioned = { layoutCoordinates -> - searchBoxHeight = layoutCoordinates.size.height - }, - placeholderText = stringResource(KMR.strings.action_search_for_source), - ) - // KMK <-- - } - } - - val migrationSheetOpen by screenModel.migrationSheetOpen.collectAsState() - if (migrationSheetOpen) { - MigrationBottomSheetDialog( - onDismissRequest = { screenModel.onMigrationSheet(false) }, - onStartMigration = { extraParam -> - screenModel.onMigrationSheet(false) - screenModel.saveEnabledSources() - - navigator replace MigrationListScreen(MigrationProcedureConfig(migration, extraParam)) - }, - ) - } - } - - // KMK --> - @Composable - fun PreMigrationScreenBottomBar( - modifier: Modifier, - onSelectAll: () -> Unit, - onSelectNone: () -> Unit, - onSelectPinned: () -> Unit, - onSelectEnabled: () -> Unit, - ) { - val scope = rememberCoroutineScope() - Surface( - modifier = modifier, - shape = MaterialTheme.shapes.large.copy( - bottomEnd = ZeroCornerSize, - bottomStart = ZeroCornerSize, - ), - color = MaterialTheme.colorScheme.surfaceColorAtElevation(elevation = 0.dp), - ) { - val haptic = LocalHapticFeedback.current - val confirm = remember { mutableStateListOf(false, false, false, false) } - var resetJob: Job? = remember { null } - val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - (0 until 4).forEach { i -> confirm[i] = i == toConfirmIndex } - resetJob?.cancel() - resetJob = scope.launch { - delay(1.seconds) - if (isActive) confirm[toConfirmIndex] = false - } - } - Row( - modifier = Modifier - .padding( - WindowInsets.navigationBars - .only(WindowInsetsSides.Bottom) - .asPaddingValues(), - ) - .padding(horizontal = 8.dp, vertical = 12.dp), - ) { - Button( - title = stringResource(MR.strings.action_select_all), - icon = Icons.Outlined.SelectAll, - onClick = onSelectAll, - toConfirm = confirm[0], - onLongClick = { onLongClickItem(0) }, - ) - Button( - title = stringResource(SYMR.strings.select_none), - icon = Icons.Outlined.Deselect, - onClick = onSelectNone, - toConfirm = confirm[1], - onLongClick = { onLongClickItem(1) }, - ) - Button( - title = stringResource(SYMR.strings.match_enabled_sources), - icon = Icons.Outlined.ToggleOn, - onClick = onSelectEnabled, - toConfirm = confirm[2], - onLongClick = { onLongClickItem(2) }, - ) - Button( - title = stringResource(SYMR.strings.match_pinned_sources), - icon = Icons.Outlined.PushPin, - onClick = onSelectPinned, - toConfirm = confirm[3], - onLongClick = { onLongClickItem(3) }, - ) - } - } - } - - @Composable - private fun RowScope.Button( - title: String, - icon: ImageVector, - toConfirm: Boolean, - onLongClick: () -> Unit, - onClick: (() -> Unit), - content: (@Composable () -> Unit)? = null, - ) { - val animatedWeight by animateFloatAsState( - targetValue = if (toConfirm) 2f else 1f, - label = "weight", - ) - Column( - modifier = Modifier - .size(48.dp) - .weight(animatedWeight) - .combinedClickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = false), - onLongClick = onLongClick, - onClick = onClick, - ), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Icon( - imageVector = icon, - contentDescription = title, - ) - AnimatedVisibility( - visible = toConfirm, - enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(), - exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(), - ) { - Text( - text = title, - overflow = TextOverflow.Visible, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.labelSmall, - ) - } - content?.invoke() - } - } - // KMK <-- - - companion object { - fun navigateToMigration(skipPre: Boolean, navigator: Navigator, mangaIds: List) { - navigator.push( - if (skipPre) { - MigrationListScreen( - MigrationProcedureConfig(MigrationType.MangaList(mangaIds), null), - ) - } else { - PreMigrationScreen(MigrationType.MangaList(mangaIds)) - }, - ) - } - - /* All usages have been replaced by original Mihon's migration dialog */ - 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)) - }, - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt deleted file mode 100644 index 6dad92d9f..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreenModel.kt +++ /dev/null @@ -1,137 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.advanced.design - -import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.tachiyomi.databinding.PreMigrationListBinding -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import tachiyomi.core.common.util.lang.launchIO -import tachiyomi.domain.source.service.SourceManager -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class PreMigrationScreenModel( - private val sourceManager: SourceManager = Injekt.get(), - private val sourcePreferences: SourcePreferences = Injekt.get(), -) : ScreenModel { - - private val _state = MutableStateFlow(emptyList()) - val state = _state.asStateFlow() - - private val _migrationSheetOpen = MutableStateFlow(false) - val migrationSheetOpen = _migrationSheetOpen.asStateFlow() - - lateinit var controllerBinding: PreMigrationListBinding - var adapter: MutableStateFlow = MutableStateFlow(null) - - val clickListener = FlexibleAdapter.OnItemClickListener { _, position -> - val adapter = adapter.value ?: return@OnItemClickListener false - adapter.getItem(position)?.let { - it.sourceEnabled = !it.sourceEnabled - } - adapter.notifyItemChanged(position) - false - } - - init { - screenModelScope.launchIO { - val enabledSources = getEnabledSources() - _state.update { enabledSources } - } - } - - /** - * Returns a list of enabled sources ordered by language and name. - * - * @return list containing enabled sources. - */ - private fun getEnabledSources(): List { - val languages = sourcePreferences.enabledLanguages().get() - val sourcesSaved = sourcePreferences.migrationSources().get().split("/") - .mapNotNull { it.toLongOrNull() } - val disabledSources = sourcePreferences.disabledSources().get() - .mapNotNull { it.toLongOrNull() } - val sources = sourceManager.getVisibleCatalogueSources() - .asSequence() - .filterIsInstance() - .filter { it.lang in languages } - .sortedBy { "(${it.lang}) ${it.name}" } - .map { - MigrationSourceItem( - it, - isEnabled( - sourcesSaved, - disabledSources, - it.id, - ), - ) - } - .toList() - - return sources - .filter { it.sourceEnabled } - .sortedBy { sourcesSaved.indexOf(it.source.id) } - .plus( - sources.filterNot { it.sourceEnabled }, - ) - } - - fun isEnabled( - sourcesSaved: List, - disabledSources: List, - id: Long, - ): Boolean { - return if (sourcesSaved.isEmpty()) { - id !in disabledSources - } else { - id in sourcesSaved - } - } - - fun massSelect(selectAll: Boolean) { - val adapter = adapter.value ?: return - adapter.currentItems.forEach { - it.sourceEnabled = selectAll - } - adapter.notifyDataSetChanged() - } - - fun matchSelection(matchEnabled: Boolean) { - val adapter = adapter.value ?: return - val enabledSources = if (matchEnabled) { - sourcePreferences.disabledSources().get().mapNotNull { it.toLongOrNull() } - } else { - sourcePreferences.pinnedSources().get().mapNotNull { it.toLongOrNull() } - } - val items = adapter.currentItems.toList() - items.forEach { - it.sourceEnabled = if (matchEnabled) { - it.source.id !in enabledSources - } else { - it.source.id in enabledSources - } - } - val sortedItems = items.sortedBy { it.source.name }.sortedBy { !it.sourceEnabled } - adapter.updateDataSet(sortedItems) - } - - fun onMigrationSheet(isOpen: Boolean) { - _migrationSheetOpen.value = isOpen - } - - fun saveEnabledSources() { - val listOfSources = adapter.value?.currentItems - ?.filterIsInstance() - ?.filter { - it.sourceEnabled - } - ?.joinToString("/") { it.source.id.toString() } - .orEmpty() - - sourcePreferences.migrationSources().set(listOfSources) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt index 16ddef8cb..2e373acae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreen.kt @@ -15,13 +15,13 @@ import eu.kanade.presentation.browse.components.MigrationMangaDialog import eu.kanade.presentation.browse.components.MigrationProgressDialog import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationBottomSheetDialog -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.system.toast import exh.util.overEq import exh.util.underEq import kotlinx.collections.immutable.persistentListOf +import mihon.feature.migration.MigrateMangaConfigScreen import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.i18n.sy.SYMR @@ -76,7 +76,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen val newStack = navigator.items.filter { it !is MangaScreen && it !is MigrationListScreen && - it !is PreMigrationScreen + it !is MigrateMangaConfigScreen } + MangaScreen(mangaId) navigator replaceAll newStack.first() navigator.push(newStack.drop(1)) @@ -113,14 +113,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen openMigrationOptionsDialog = screenModel::openMigrationOptionsDialog, // KMK <-- searchManually = { migrationItem -> - val sources = screenModel.getMigrationSources() - val validSources = if (sources.size == 1) { - sources - } else { - sources.filter { it.id != migrationItem.manga.source } - } - val searchScreen = MigrateSearchScreen(migrationItem.manga.id, validSources.map { it.id }) - navigator push searchScreen + navigator push MigrateSearchScreen(migrationItem.manga.id) }, migrateNow = { screenModel.migrateManga(it, true) }, copyNow = { screenModel.migrateManga(it, false) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt index edae327ff..33e0c929f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListScreenModel.kt @@ -11,7 +11,6 @@ import eu.kanade.domain.source.service.SourcePreferences 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.advanced.design.MigrationType import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.SearchResult import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialogScreenModel.Companion.migrateMangaInternal import eu.kanade.tachiyomi.util.system.toast @@ -130,9 +129,8 @@ class MigrationListScreenModel( } fun getSourceName(manga: Manga) = sourceManager.getOrStub(manga.source).getNameForMangaInfo() - fun getMigrationSources() = preferences.migrationSources().get().split("/").mapNotNull { - val value = it.toLongOrNull() ?: return@mapNotNull null - sourceManager.get(value) as? CatalogueSource + fun getMigrationSources() = preferences.migrationSources().get().mapNotNull { + sourceManager.get(it) as? CatalogueSource } private suspend fun runMigrations(mangas: List) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt index 7a57710a1..6ef174660 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcedureConfig.kt @@ -1,8 +1,12 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationType import java.io.Serializable +sealed class MigrationType : Serializable { + data class MangaList(val mangaIds: List) : MigrationType() + data class MangaSingle(val fromMangaId: Long, val toManga: Long?) : MigrationType() +} + data class MigrationProcedureConfig( var migration: MigrationType, val extraSearchParams: String?, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreen.kt index af7c7352d..ad3cf01f3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreen.kt @@ -8,18 +8,14 @@ import androidx.compose.ui.platform.LocalContext import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.browse.MigrateMangaScreen import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest +import mihon.feature.migration.MigrateMangaConfigScreen import tachiyomi.i18n.MR -import tachiyomi.i18n.kmk.KMR import tachiyomi.presentation.core.screens.LoadingScreen -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get data class MigrateMangaScreen( private val sourceId: Long, @@ -42,31 +38,14 @@ data class MigrateMangaScreen( navigateUp = navigator::pop, title = state.source?.name ?: "???", state = state, - onClickItem = { - // SY --> - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - listOf(it.id), - ) - // SY <-- - }, + onClickItem = { navigator.push(MigrateMangaConfigScreen(listOf(it.id))) }, onClickCover = { navigator.push(MangaScreen(it.id)) }, // KMK --> onMultiMigrateClicked = { if (state.selectionMode) { - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - state.selected.map { it.manga.id }, - ) + navigator.push(MigrateMangaConfigScreen(state.selected.map { it.manga.id })) } else { - context.toast(KMR.strings.migrating_all_entries) - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - state.titles.map { it.manga.id }, - ) + navigator.push(MigrateMangaConfigScreen(state.titles.map { it.manga.id })) } }, onSelectAll = screenModel::toggleAllSelection, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt index 4da96cce0..000f61fe7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt @@ -14,16 +14,13 @@ import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen -/** - * Manual search [validSources] for manga to migrate to. - */ -class MigrateSearchScreen(private val mangaId: Long, private val validSources: List) : Screen() { +class MigrateSearchScreen(private val mangaId: Long) : Screen() { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow val screenModel = - rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId, validSources = validSources) } + rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) } val state by screenModel.state.collectAsState() val dialogScreenModel = rememberScreenModel { MigrateSearchScreenDialogScreenModel(mangaId = mangaId) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt index 17e7ffd18..af2907f3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt @@ -1,9 +1,10 @@ package eu.kanade.tachiyomi.ui.browse.migration.search import cafe.adriel.voyager.core.model.screenModelScope +import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import tachiyomi.domain.manga.interactor.GetManga @@ -13,15 +14,20 @@ import uy.kohesive.injekt.api.get class MigrateSearchScreenModel( val mangaId: Long, - // SY --> - val validSources: List, - // SY <-- getManga: GetManga = Injekt.get(), - // SY --> private val sourceManager: SourceManager = Injekt.get(), - // SY <-- + private val sourcePreferences: SourcePreferences = Injekt.get(), ) : SearchScreenModel() { + private val migrationSources by lazy { sourcePreferences.migrationSources().get() } + + override val sortComparator = { map: Map -> + compareBy( + { (map[it] as? SearchItemResult.Success)?.isEmpty ?: true }, + { migrationSources.indexOf(it.id) }, + ) + } + init { screenModelScope.launch { val manga = getManga.await(mangaId)!! @@ -40,17 +46,6 @@ class MigrateSearchScreenModel( } override fun getEnabledSources(): List { - // SY --> - return validSources.mapNotNull { sourceManager.get(it) } - .filterIsInstance() - .filter { state.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources } - .sortedWith( - compareBy( - { it.id != state.value.fromSourceId }, - { "${it.id}" !in pinnedSources }, - { "${it.name.lowercase()} (${it.lang})" }, - ), - ) - // SY <-- + return migrationSources.mapNotNull { sourceManager.get(it) as? CatalogueSource } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 4bd01e941..5c91d0291 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -57,7 +57,7 @@ abstract class SearchScreenModel( protected var extensionFilter: String? = null - private val sortComparator = { map: Map -> + open val sortComparator = { map: Map -> compareBy( { (map[it] as? SearchItemResult.Success)?.isEmpty ?: true }, { "${it.id}" !in pinnedSources }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index a49c8c570..2bb6a47bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -29,7 +29,6 @@ import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.category.components.ChangeCategoryDialog import eu.kanade.presentation.library.DeleteLibraryMangaDialog import eu.kanade.presentation.library.LibrarySettingsDialog @@ -47,7 +46,6 @@ import eu.kanade.tachiyomi.data.connections.discord.DiscordScreen import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.sync.SyncDataJob -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.globalsearch.GlobalSearchScreen import eu.kanade.tachiyomi.ui.category.CategoryScreen @@ -68,6 +66,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import mihon.feature.migration.MigrateMangaConfigScreen import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.category.model.Category @@ -87,6 +86,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get data object LibraryTab : Tab { + @Suppress("unused") private fun readResolve(): Any = LibraryTab override val options: TabOptions @@ -210,11 +210,7 @@ data object LibraryTab : Tab { .map { it.id } screenModel.clearSelection() if (selectedMangaIds.isNotEmpty()) { - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - selectedMangaIds, - ) + navigator.push(MigrateMangaConfigScreen(selectedMangaIds)) } else { context.toast(SYMR.strings.no_valid_entry) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt index 01277932f..5f60720f6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt @@ -6,13 +6,10 @@ import androidx.compose.runtime.getValue import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.libraryUpdateError.LibraryUpdateErrorScreen import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get +import mihon.feature.migration.MigrateMangaConfigScreen class LibraryUpdateErrorScreen : Screen() { @@ -25,19 +22,11 @@ class LibraryUpdateErrorScreen : Screen() { LibraryUpdateErrorScreen( state = state, onClick = { item -> - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - listOf(item.error.mangaId), - ) + navigator.push(MigrateMangaConfigScreen(listOf(item.error.mangaId))) }, onClickCover = { item -> navigator.push(MangaScreen(item.error.mangaId)) }, onMultiMigrateClicked = { - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - state.selected.map { it.error.mangaId }, - ) + navigator.push(MigrateMangaConfigScreen(state.selected.map { it.error.mangaId })) }, onSelectAll = screenModel::toggleAllSelection, onInvertSelection = screenModel::invertSelection, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 3b2ad4391..0c1ddda3c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -45,7 +45,6 @@ import dev.icerock.moko.resources.StringResource import eu.kanade.core.util.ifSourcesLoaded import eu.kanade.domain.manga.model.hasCustomCover import eu.kanade.domain.manga.model.toSManga -import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.browse.components.BulkFavoriteDialogs import eu.kanade.presentation.category.components.ChangeCategoryDialog import eu.kanade.presentation.components.NavigatorAdaptiveSheet @@ -70,7 +69,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreen 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.migration.search.MigrateDialog import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialogScreenModel import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen @@ -105,6 +103,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import logcat.LogPriority +import mihon.feature.migration.MigrateMangaConfigScreen import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchUI @@ -367,13 +366,7 @@ class MangaScreen( successState.manga.favorite }, onMigrateClicked = { - // SY --> - PreMigrationScreen.navigateToMigration( - Injekt.get().skipPreMigration().get(), - navigator, - listOfNotNull(successState.manga.id), - ) - // SY <-- + navigator.push(MigrateMangaConfigScreen(listOf(successState.manga.id))) }.takeIf { successState.manga.favorite }, // SY --> previewsRowCount = successState.previewsRowCount, diff --git a/app/src/main/java/mihon/feature/migration/MigrateMangaConfigScreen.kt b/app/src/main/java/mihon/feature/migration/MigrateMangaConfigScreen.kt new file mode 100644 index 000000000..aa9c0ead3 --- /dev/null +++ b/app/src/main/java/mihon/feature/migration/MigrateMangaConfigScreen.kt @@ -0,0 +1,528 @@ +package mihon.feature.migration + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowForward +import androidx.compose.material.icons.outlined.Deselect +import androidx.compose.material.icons.outlined.DragHandle +import androidx.compose.material.icons.outlined.PushPin +import androidx.compose.material3.ElevatedCard +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.SmallExtendedFloatingActionButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times +import androidx.compose.ui.util.fastForEachIndexed +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.browse.components.SourceIcon +import eu.kanade.presentation.components.AnimatedFloatingSearchBox +import eu.kanade.presentation.components.AppBar +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 +import eu.kanade.tachiyomi.util.system.LocaleHelper +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.updateAndGet +import sh.calvin.reorderable.ReorderableCollectionItemScope +import sh.calvin.reorderable.ReorderableItem +import sh.calvin.reorderable.ReorderableLazyListState +import sh.calvin.reorderable.rememberReorderableLazyListState +import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.domain.source.model.Source +import tachiyomi.domain.source.service.SourceManager +import tachiyomi.i18n.MR +import tachiyomi.i18n.kmk.KMR +import tachiyomi.i18n.sy.SYMR +import tachiyomi.presentation.core.components.FastScrollLazyColumn +import tachiyomi.presentation.core.components.Pill +import tachiyomi.presentation.core.components.material.DISABLED_ALPHA +import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.icons.FlagEmoji +import tachiyomi.presentation.core.util.shouldExpandFAB +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +/** + * Screen that allows the user to configure which sources should be used when migrating manga. + * + * It displays available sources, supports searching and bulk selection, and opens a migration + * configuration bottom sheet before starting the actual migration flow for the selected sources. + * + * @param mangaIds IDs of the manga that will be migrated using the sources configured on this screen. + */ +class MigrateMangaConfigScreen(private val mangaIds: List) : Screen() { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { ScreenModel() } + // KMK --> + var searchQuery by remember { mutableStateOf("") } + BackHandler(enabled = searchQuery.isNotBlank()) { + searchQuery = "" + } + // KMK <-- + val state by screenModel.state.collectAsState() + val (selectedSources, availableSources) = state.sources + // KMK --> + .filter { sources -> + if (searchQuery.isBlank()) return@filter true + val source = sources.source + searchQuery.split(",").any { + val input = it.trim() + if (input.isEmpty()) return@any false + source.name.contains(input, ignoreCase = true) || + source.id == input.toLongOrNull() + } + } + // KMK <-- + .partition { it.isSelected } + val showLanguage by remember(state) { + derivedStateOf { + state.sources.distinctBy { it.source.lang }.size > 1 + } + } + + val lazyListState = rememberLazyListState() + Scaffold( + topBar = { + AppBar( + title = stringResource(SYMR.strings.select_sources), + navigateUp = navigator::pop, + scrollBehavior = it, + actions = { + AppBarActions( + persistentListOf( + AppBar.Action( + title = stringResource(MR.strings.migrationConfigScreen_selectPinnedLabel), + icon = Icons.Outlined.PushPin, + onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.Pinned) }, + ), + AppBar.Action( + title = stringResource(MR.strings.migrationConfigScreen_selectNoneLabel), + icon = Icons.Outlined.Deselect, + onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.None) }, + ), + AppBar.OverflowAction( + title = stringResource(MR.strings.migrationConfigScreen_selectEnabledLabel), + onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.Enabled) }, + ), + AppBar.OverflowAction( + title = stringResource(MR.strings.migrationConfigScreen_selectAllLabel), + onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.All) }, + ), + ), + ) + }, + ) + }, + floatingActionButton = { + // KMK --> + AnimatedVisibility( + visible = selectedSources.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + content = { + // KMK <-- + SmallExtendedFloatingActionButton( + text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) }, + icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) }, + onClick = { + // KMK --> + screenModel.onMigrationSheet(true) + // KMK <-- + }, + expanded = lazyListState.shouldExpandFAB(), + ) + }, + ) + }, + ) { contentPadding -> + val reorderableState = rememberReorderableLazyListState(lazyListState, contentPadding) { from, to -> + val fromIndex = selectedSources.indexOfFirst { it.id == from.key } + val toIndex = selectedSources.indexOfFirst { it.id == to.key } + if (fromIndex == -1 || toIndex == -1) return@rememberReorderableLazyListState + screenModel.orderSource(fromIndex, toIndex) + } + // KMK --> + Box( + modifier = Modifier.padding(contentPadding), + ) { + val density = LocalDensity.current + var searchBoxHeight by remember { mutableStateOf(SOURCE_SEARCH_BOX_HEIGHT) } + // KMK <-- + + FastScrollLazyColumn( + modifier = Modifier.fillMaxSize(), + state = lazyListState, + // KMK --> + contentPadding = PaddingValues(top = searchBoxHeight), + // KMK <-- + ) { + listOf(selectedSources, availableSources).fastForEachIndexed { listIndex, sources -> + val selectedSourceList = listIndex == 0 + if (sources.isNotEmpty()) { + val headerPrefix = if (selectedSourceList) "selected" else "available" + item("$headerPrefix-header") { + Text( + text = stringResource( + resource = if (selectedSourceList) { + MR.strings.migrationConfigScreen_selectedHeader + } else { + MR.strings.migrationConfigScreen_availableHeader + }, + ), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .padding(MaterialTheme.padding.medium) + .animateItem(), + ) + } + } + itemsIndexed( + items = sources, + key = { _, item -> item.id }, + ) { index, item -> + SourceItemContainer( + firstItem = index == 0, + lastItem = index == (sources.size - 1), + source = item.source, + showLanguage = showLanguage, + isSelected = item.isSelected, + dragEnabled = selectedSourceList && sources.size > 1, + state = reorderableState, + key = { if (selectedSourceList) it.id else "available-${it.id}" }, + onClick = { screenModel.toggleSelection(item.id) }, + ) + } + } + } + + // KMK --> + AnimatedFloatingSearchBox( + listState = lazyListState, + searchQuery = searchQuery, + onChangeSearchQuery = { searchQuery = it ?: "" }, + placeholderText = stringResource(KMR.strings.action_search_for_source), + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .padding( + horizontal = MaterialTheme.padding.medium, + vertical = MaterialTheme.padding.small, + ) + .align(Alignment.TopCenter), + onGloballyPositioned = { layoutCoordinates -> + searchBoxHeight = with(density) { layoutCoordinates.size.height.toDp() + 2 * MaterialTheme.padding.small } + }, + ) + // KMK <-- + } + } + + // KMK --> + val migrationSheetOpen by screenModel.migrationSheetOpen.collectAsState() + if (migrationSheetOpen) { + MigrationBottomSheetDialog( + onDismissRequest = { screenModel.onMigrationSheet(false) }, + onStartMigration = { extraParam -> + screenModel.onMigrationSheet(false) + navigator.replace( + // KMK --> + MigrationListScreen( + MigrationProcedureConfig(MigrationType.MangaList(mangaIds), extraParam), + ), + // KMK <-- + ) + }, + ) + } + // KMK <-- + } + + @Composable + private fun LazyItemScope.SourceItemContainer( + firstItem: Boolean, + lastItem: Boolean, + source: Source, + showLanguage: Boolean, + isSelected: Boolean, + dragEnabled: Boolean, + state: ReorderableLazyListState, + key: (Source) -> Any, + onClick: () -> Unit, + ) { + val shape = remember(firstItem, lastItem) { + val top = if (firstItem) 12.dp else 0.dp + val bottom = if (lastItem) 12.dp else 0.dp + RoundedCornerShape(top, top, bottom, bottom) + } + + ReorderableItem( + state = state, + key = key(source), + enabled = dragEnabled, + ) { _ -> + ElevatedCard( + shape = shape, + modifier = Modifier + .padding(horizontal = MaterialTheme.padding.medium) + .animateItem(), + ) { + SourceItem( + source = source, + showLanguage = showLanguage, + isSelected = isSelected, + dragEnabled = dragEnabled, + scope = this@ReorderableItem, + onClick = onClick, + ) + } + } + + if (!lastItem) { + HorizontalDivider(modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium)) + } + } + + @Composable + private fun SourceItem( + source: Source, + showLanguage: Boolean, + isSelected: Boolean, + dragEnabled: Boolean, + scope: ReorderableCollectionItemScope, + onClick: () -> Unit, + ) { + ListItem( + headlineContent = { + Row( + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + verticalAlignment = Alignment.CenterVertically, + ) { + SourceIcon(source = source) + Text( + text = source.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.weight(1f), + ) + if (showLanguage) { + Pill( + text = FlagEmoji.getEmojiLangFlag(source.lang) + " (${LocaleHelper.getLocalizedDisplayName(source.lang)})", + style = MaterialTheme.typography.bodySmall, + ) + } + } + }, + trailingContent = if (dragEnabled) { + { + Icon( + imageVector = Icons.Outlined.DragHandle, + contentDescription = null, + modifier = with(scope) { + Modifier.draggableHandle() + }, + ) + } + } else { + null + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + modifier = Modifier + .clickable(onClick = onClick) + .alpha(if (isSelected) 1f else DISABLED_ALPHA), + ) + } + + private class ScreenModel( + private val sourceManager: SourceManager = Injekt.get(), + private val sourcePreferences: SourcePreferences = Injekt.get(), + ) : StateScreenModel(State()) { + + // KMK --> + private val pinnedSources by lazy { sourcePreferences.pinnedSources().get().mapNotNull { it.toLongOrNull() } } + private val disabledSources by lazy { sourcePreferences.disabledSources().get().mapNotNull { it.toLongOrNull() } } + // KMK <-- + + init { + screenModelScope.launchIO { + initSources() + } + } + + // KMK --> + private val _migrationSheetOpen = MutableStateFlow(false) + val migrationSheetOpen = _migrationSheetOpen.asStateFlow() + + fun onMigrationSheet(isOpen: Boolean) { + _migrationSheetOpen.value = isOpen + } + // KMK <-- + + private val sourcesComparator = { includedSources: /* KMK --> */ Map /* KMK <-- */ -> + compareBy( + // KMK --> + // { !it.isSelected }, + { includedSources[it.source.id] ?: Int.MAX_VALUE }, + // KMK <-- + { with(it.source) { "$name (${LocaleHelper.getLocalizedDisplayName(lang)})" } }, + ) + } + + private fun updateSources(save: Boolean = true, action: (List) -> List) { + val state = mutableState.updateAndGet { state -> + val updatedSources = action(state.sources) + val includedSources = updatedSources.mapNotNull { if (!it.isSelected) null else it.id } + // KMK --> + .mapIndexed { index, id -> id to index }.toMap() + // KMK <-- + state.copy(sources = updatedSources.sortedWith(sourcesComparator(includedSources))) + } + if (!save) return + state.sources + .filter { source -> source.isSelected } + .map { source -> source.source.id } + .let { sources -> sourcePreferences.migrationSources().set(sources) } + } + + private fun initSources() { + val languages = sourcePreferences.enabledLanguages().get() + val includedSources = sourcePreferences.migrationSources().get() + // KMK --> + .mapIndexed { index, id -> id to index }.toMap() + // KMK <-- + val sources = sourceManager + // KMK --> + .getVisibleCatalogueSources() + // KMK <-- + .asSequence() + .filterIsInstance() + .filter { it.lang in languages } + // KMK --> + .sortedWith(compareBy { includedSources[it.id] ?: Int.MAX_VALUE }) + // KMK <-- + .map { + val source = Source( + id = it.id, + lang = it.lang, + name = it.name, + supportsLatest = false, + isStub = false, + ) + MigrationSource( + source = source, + isSelected = when { + includedSources.isNotEmpty() -> source.id in includedSources + pinnedSources.isNotEmpty() -> source.id in pinnedSources + else -> source.id !in disabledSources + }, + ) + } + .toList() + + updateSources(save = false) { sources } + } + + fun toggleSelection(id: Long) { + updateSources { sources -> + sources.map { source -> + source.copy(isSelected = if (source.source.id == id) !source.isSelected else source.isSelected) + } + } + } + + fun toggleSelection(config: SelectionConfig) { + val isSelected: (Long) -> Boolean = { + when (config) { + SelectionConfig.All -> true + SelectionConfig.None -> false + SelectionConfig.Pinned -> it in pinnedSources + SelectionConfig.Enabled -> it !in disabledSources + } + } + updateSources { sources -> + sources.map { source -> + source.copy(isSelected = isSelected(source.source.id)) + } + } + } + + fun orderSource(from: Int, to: Int) { + updateSources { + it.toMutableList() + .apply { + add(to, removeAt(from)) + } + .toList() + } + } + + data class State( + val sources: List = emptyList(), + ) + + enum class SelectionConfig { + All, + None, + Pinned, + Enabled, + } + } + + data class MigrationSource( + val source: Source, + val isSelected: Boolean, + ) { + val id = source.id + val visualName = source.visualName + } +} diff --git a/app/src/main/res/layout/migration_source_item.xml b/app/src/main/res/layout/migration_source_item.xml deleted file mode 100644 index ba006f2d8..000000000 --- a/app/src/main/res/layout/migration_source_item.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/pre_migration_list.xml b/app/src/main/res/layout/pre_migration_list.xml deleted file mode 100644 index c1b058442..000000000 --- a/app/src/main/res/layout/pre_migration_list.xml +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt b/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt index 0cd3a21bf..0b1f11d27 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/preference/PreferenceStore.kt @@ -24,6 +24,18 @@ interface PreferenceStore { fun getAll(): Map } +fun PreferenceStore.getLongArray( + key: String, + defaultValue: List, +): Preference> { + return getObject( + key = key, + defaultValue = defaultValue, + serializer = { it.joinToString(",") }, + deserializer = { it.split(",").mapNotNull { l -> l.toLongOrNull() } }, + ) +} + inline fun > PreferenceStore.getEnum( key: String, defaultValue: T, @@ -35,7 +47,7 @@ inline fun > PreferenceStore.getEnum( deserializer = { try { enumValueOf(it) - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { defaultValue } }, diff --git a/i18n-kmk/src/commonMain/moko-resources/ar/strings.xml b/i18n-kmk/src/commonMain/moko-resources/ar/strings.xml index 8a5afaf20..4514da4c4 100644 --- a/i18n-kmk/src/commonMain/moko-resources/ar/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/ar/strings.xml @@ -108,7 +108,6 @@ التمرير إلى الأسفل التمرير إلى السابق التمرير إلى التالي - ترحيل جميع المدخلات من المصدر سيتم تحديثها معلومات المانجا تنزيل يدوي diff --git a/i18n-kmk/src/commonMain/moko-resources/as/strings.xml b/i18n-kmk/src/commonMain/moko-resources/as/strings.xml index 50c777491..a1e3edb9e 100644 --- a/i18n-kmk/src/commonMain/moko-resources/as/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/as/strings.xml @@ -134,7 +134,6 @@ ফিড ছৰ্ট কৰক আপুনি ফিডবোৰ বৰ্ণানুক্ৰমে ছৰ্ট কৰিব বিচাৰে নেকি? বৰ্তমান: %1$s - উৎসৰ পৰা সকলো এণ্ট্ৰি মাইগ্ৰেট কৰি আছে আপডেট কৰিবলগীয়া মোক স্পনছৰ কৰক সমৰ্থন: MangaDex, E-H, ExH, nH, 8Muses, Tsumino diff --git a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml index e886352ed..505493df8 100644 --- a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml @@ -171,7 +171,6 @@ Would you like to sort the feeds alphabetically? Current: %1$s - Migrating all entries from source Failed to schedule automatic library update: %s Failed to schedule automatic backup: %s diff --git a/i18n-kmk/src/commonMain/moko-resources/cs/strings.xml b/i18n-kmk/src/commonMain/moko-resources/cs/strings.xml index 7e84c1efd..0b020bcd7 100644 --- a/i18n-kmk/src/commonMain/moko-resources/cs/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/cs/strings.xml @@ -102,7 +102,6 @@ Přejít na konec Přejít na předchozí Přejít na další - Migrovat všechny záznamy ze zdroje Bude aktualizováno Manga Info Ruční stahování diff --git a/i18n-kmk/src/commonMain/moko-resources/da/strings.xml b/i18n-kmk/src/commonMain/moko-resources/da/strings.xml index a48d4e56a..0df4bf402 100644 --- a/i18n-kmk/src/commonMain/moko-resources/da/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/da/strings.xml @@ -102,7 +102,6 @@ Rul til bunden Rul til forrige Rul til næste - Migrerer alle poster fra kilde Skal opdateres Manga Info Manuel download diff --git a/i18n-kmk/src/commonMain/moko-resources/de/strings.xml b/i18n-kmk/src/commonMain/moko-resources/de/strings.xml index 992f0cf05..d7e2903cc 100644 --- a/i18n-kmk/src/commonMain/moko-resources/de/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/de/strings.xml @@ -103,7 +103,6 @@ Nach unten scrollen Zum Vorherigen scrollen Zum Nächsten scrollen - Alle Einträge aus der Quelle migrieren Zu aktualisieren Manga-Info Manueller Download diff --git a/i18n-kmk/src/commonMain/moko-resources/el/strings.xml b/i18n-kmk/src/commonMain/moko-resources/el/strings.xml index 95afa884e..306c70fa9 100644 --- a/i18n-kmk/src/commonMain/moko-resources/el/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/el/strings.xml @@ -103,7 +103,6 @@ Κύλιση προς τα κάτω Κύλιση στο προηγούμενο Κύλιση στο επόμενο - Μεταφορά όλων των καταχωρήσεων από την πηγή Να ενημερωθεί Manga Info Χειροκίνητη λήψη diff --git a/i18n-kmk/src/commonMain/moko-resources/es/strings.xml b/i18n-kmk/src/commonMain/moko-resources/es/strings.xml index f16b84d02..5c6664ebb 100644 --- a/i18n-kmk/src/commonMain/moko-resources/es/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/es/strings.xml @@ -103,7 +103,6 @@ Desplazar al final Desplazar al anterior Desplazar al siguiente - Migrando todas las entradas desde la fuente Para actualizar Información del manga Descarga manual diff --git a/i18n-kmk/src/commonMain/moko-resources/fi/strings.xml b/i18n-kmk/src/commonMain/moko-resources/fi/strings.xml index 8b5b07338..a57321d05 100644 --- a/i18n-kmk/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/fi/strings.xml @@ -101,7 +101,6 @@ Siirry alareunaan Vieritä edelliseen Vieritä seuraavaan - Siirretään kaikki merkinnät lähteestä Päivittääksesi Manga Info Manuaalinen lataus diff --git a/i18n-kmk/src/commonMain/moko-resources/fil/strings.xml b/i18n-kmk/src/commonMain/moko-resources/fil/strings.xml index bdd07cd21..f595e7799 100644 --- a/i18n-kmk/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/fil/strings.xml @@ -84,7 +84,6 @@ Mag-scroll paibaba Mag-scroll sa nakaraan Mag-scroll sa susunod - Nililipat ang lahat ng entry mula sa source Para ma-update Impo ng Manga Mano-manong I-downloa diff --git a/i18n-kmk/src/commonMain/moko-resources/fr/strings.xml b/i18n-kmk/src/commonMain/moko-resources/fr/strings.xml index 496cbcf3b..8faef7c7e 100644 --- a/i18n-kmk/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/fr/strings.xml @@ -101,7 +101,6 @@ Défiler vers le bas Défiler vers le précédent Défiler vers le suivant - Migrer toutes les entrées de la source A mettre à jour Manga Info Téléchargement manuel diff --git a/i18n-kmk/src/commonMain/moko-resources/in/strings.xml b/i18n-kmk/src/commonMain/moko-resources/in/strings.xml index 44f2716f7..1c362be11 100644 --- a/i18n-kmk/src/commonMain/moko-resources/in/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/in/strings.xml @@ -146,7 +146,6 @@ Gulir ke sebelumnya Urutkan feed Apakah Anda ingin mengurutkan feed berdasarkan abjad? - Memigrasikan semua entri dari sumber Gagal menjadwalkan pembaruan perpustakaan otomatis: %s Gagal menjadwalkan pencadangan otomatis: %s Untuk diperbarui diff --git a/i18n-kmk/src/commonMain/moko-resources/it/strings.xml b/i18n-kmk/src/commonMain/moko-resources/it/strings.xml index 75d3f3615..0d1cc6c70 100644 --- a/i18n-kmk/src/commonMain/moko-resources/it/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/it/strings.xml @@ -103,7 +103,6 @@ Scorri verso il basso Scorri al precedente Scorri al successivo - Migrazione di tutte le voci dalla sorgente Da aggiornare Manga Info Download manuale diff --git a/i18n-kmk/src/commonMain/moko-resources/ja/strings.xml b/i18n-kmk/src/commonMain/moko-resources/ja/strings.xml index 2756300e7..b0a828099 100644 --- a/i18n-kmk/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/ja/strings.xml @@ -103,7 +103,6 @@ 一番下までスクロール 前までスクロール 次までスクロール - すべての項目をソースから移行中 更新予定 マンガ情報 手動でダウンロード diff --git a/i18n-kmk/src/commonMain/moko-resources/nl/strings.xml b/i18n-kmk/src/commonMain/moko-resources/nl/strings.xml index e99ef2098..ce9d4e2c2 100644 --- a/i18n-kmk/src/commonMain/moko-resources/nl/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/nl/strings.xml @@ -103,7 +103,6 @@ Scroll naar beneden Scroll naar vorige Scroll naar volgende - Alle invoergegevens vanuit bron migreren Te updaten Manga Info Handmatige download diff --git a/i18n-kmk/src/commonMain/moko-resources/pl/strings.xml b/i18n-kmk/src/commonMain/moko-resources/pl/strings.xml index cf6df24b2..c1397db18 100644 --- a/i18n-kmk/src/commonMain/moko-resources/pl/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/pl/strings.xml @@ -101,7 +101,6 @@ Przewiń na dół Przewiń do poprzedniego Przewiń do następnego - Migracja wszystkich wpisów ze źródła Do aktualizacji Manga Info Ręczne pobieranie diff --git a/i18n-kmk/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n-kmk/src/commonMain/moko-resources/pt-rBR/strings.xml index 94152e1fd..c1d3e3645 100644 --- a/i18n-kmk/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -105,7 +105,6 @@ Rolar para o final Rolar para o anterior Rolar para o próximo - Migrando todas as entradas da fonte A ser atualizado Manga Info Download Manual diff --git a/i18n-kmk/src/commonMain/moko-resources/pt/strings.xml b/i18n-kmk/src/commonMain/moko-resources/pt/strings.xml index bdd534dfa..9435f1e1e 100644 --- a/i18n-kmk/src/commonMain/moko-resources/pt/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/pt/strings.xml @@ -105,7 +105,6 @@ Rolar para o final Rolar para o anterior Rolar para o próximo - Migrando todas as entradas da fonte A ser atualizado Manga Info Download manual diff --git a/i18n-kmk/src/commonMain/moko-resources/ro/strings.xml b/i18n-kmk/src/commonMain/moko-resources/ro/strings.xml index e601d8994..4187dc493 100644 --- a/i18n-kmk/src/commonMain/moko-resources/ro/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/ro/strings.xml @@ -102,7 +102,6 @@ Derulează până jos Derulează până la anterioară Derulează mai departe - Migrarea tuturor intrărilor de la sursă A se actualiza Manga Info Descărcare manuală diff --git a/i18n-kmk/src/commonMain/moko-resources/ru/strings.xml b/i18n-kmk/src/commonMain/moko-resources/ru/strings.xml index 9707d1dee..fa85fdd63 100644 --- a/i18n-kmk/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/ru/strings.xml @@ -106,7 +106,6 @@ Прокрутка вниз Прокрутка до предыдущей Прокрутка до следующего - Миграция всех записей из источника Будет обновлено Информация о манге Ручная загрузка diff --git a/i18n-kmk/src/commonMain/moko-resources/sv/strings.xml b/i18n-kmk/src/commonMain/moko-resources/sv/strings.xml index b8b542b62..7596a3375 100644 --- a/i18n-kmk/src/commonMain/moko-resources/sv/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/sv/strings.xml @@ -103,7 +103,6 @@ Rulla till botten Bläddra till föregående Bläddra till nästa - Migrerar alla poster från källan Att uppdateras Manga Info Manuell nedladdning diff --git a/i18n-kmk/src/commonMain/moko-resources/uk/strings.xml b/i18n-kmk/src/commonMain/moko-resources/uk/strings.xml index a9bd1895f..fe28b796d 100644 --- a/i18n-kmk/src/commonMain/moko-resources/uk/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/uk/strings.xml @@ -105,7 +105,6 @@ Прокрутити до кінця Прокрутити до попереднього Перейти до наступного - Міграція всіх записів з джерела Оновлювати Manga Info Ручне завантаження diff --git a/i18n-kmk/src/commonMain/moko-resources/vi/strings.xml b/i18n-kmk/src/commonMain/moko-resources/vi/strings.xml index 1c4f46a5c..cc02a045a 100644 --- a/i18n-kmk/src/commonMain/moko-resources/vi/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/vi/strings.xml @@ -109,7 +109,6 @@ Cuộn xuống dưới cùng Nhảy tới nhóm trước Nhảy tới nhóm sau - Di chuyển tất cả truyện của nguồn Truyện sẽ được cập nhật Trang thông tin truyện Tải về thủ công diff --git a/i18n-kmk/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n-kmk/src/commonMain/moko-resources/zh-rCN/strings.xml index 3f085e73a..5758f29a2 100644 --- a/i18n-kmk/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -109,7 +109,6 @@ 滚动到底部 滚动到上一个 滚动到下一个 - 从源代码迁移所有条目 待更新 漫画信息 手动下载 diff --git a/i18n-kmk/src/commonMain/moko-resources/zh-rTW/strings.xml b/i18n-kmk/src/commonMain/moko-resources/zh-rTW/strings.xml index 56b2612fa..853daf744 100644 --- a/i18n-kmk/src/commonMain/moko-resources/zh-rTW/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/zh-rTW/strings.xml @@ -109,7 +109,6 @@ 移至底部 移至上一類 移至下一類 - 正在從來源遷移所有項目 待更新 漫畫資訊 手動下載 diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index fca7c387c..287e49541 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -1007,4 +1007,12 @@ Enjoyed the part where… + + Selected + Available + Select all + Select none + Select enabled sources + Select pinned sources + Continue