Add migration config screen to select and prioritize target sources (mihonapp/mihon#2144)
(cherry picked from commit 2e180005a01f633ad7fafc5cfb3079f0bc858448) Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
This commit is contained in:
parent
147c24c42e
commit
c328b37675
50 changed files with 619 additions and 898 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ fun GlobalSearchScreen(
|
|||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
hideSourceFilter = false,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ fun MigrateSearchScreen(
|
|||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
hideSourceFilter = true,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<MigrationSourceItem>(
|
||||
null,
|
||||
listener,
|
||||
true,
|
||||
) {
|
||||
val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
// SY _->
|
||||
val sourcePreferences: SourcePreferences by injectLazy()
|
||||
// SY <--
|
||||
}
|
||||
|
|
@ -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<ExtensionManager>().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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MigrationSourceHolder>() {
|
||||
override fun getLayoutRes() = R.layout.migration_source_item
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): 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<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: MigrationSourceHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?,
|
||||
) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Long>) : 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<Long>) {
|
||||
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))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MigrationSourceItem>())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
private val _migrationSheetOpen = MutableStateFlow(false)
|
||||
val migrationSheetOpen = _migrationSheetOpen.asStateFlow()
|
||||
|
||||
lateinit var controllerBinding: PreMigrationListBinding
|
||||
var adapter: MutableStateFlow<MigrationSourceAdapter?> = 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<MigrationSourceItem> {
|
||||
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<HttpSource>()
|
||||
.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<Long>,
|
||||
disabledSources: List<Long>,
|
||||
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<MigrationSourceItem>()
|
||||
?.filter {
|
||||
it.sourceEnabled
|
||||
}
|
||||
?.joinToString("/") { it.source.id.toString() }
|
||||
.orEmpty()
|
||||
|
||||
sourcePreferences.migrationSources().set(listOfSources)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -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<MigratingManga>) {
|
||||
|
|
|
|||
|
|
@ -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<Long>) : MigrationType()
|
||||
data class MangaSingle(val fromMangaId: Long, val toManga: Long?) : MigrationType()
|
||||
}
|
||||
|
||||
data class MigrationProcedureConfig(
|
||||
var migration: MigrationType,
|
||||
val extraSearchParams: String?,
|
||||
|
|
|
|||
|
|
@ -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<SourcePreferences>().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<SourcePreferences>().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<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
state.titles.map { it.manga.id },
|
||||
)
|
||||
navigator.push(MigrateMangaConfigScreen(state.titles.map { it.manga.id }))
|
||||
}
|
||||
},
|
||||
onSelectAll = screenModel::toggleAllSelection,
|
||||
|
|
|
|||
|
|
@ -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<Long>) : 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) }
|
||||
|
|
|
|||
|
|
@ -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<Long>,
|
||||
// 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<CatalogueSource, SearchItemResult> ->
|
||||
compareBy<CatalogueSource>(
|
||||
{ (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<CatalogueSource> {
|
||||
// SY -->
|
||||
return validSources.mapNotNull { sourceManager.get(it) }
|
||||
.filterIsInstance<CatalogueSource>()
|
||||
.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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ abstract class SearchScreenModel(
|
|||
|
||||
protected var extensionFilter: String? = null
|
||||
|
||||
private val sortComparator = { map: Map<CatalogueSource, SearchItemResult> ->
|
||||
open val sortComparator = { map: Map<CatalogueSource, SearchItemResult> ->
|
||||
compareBy<CatalogueSource>(
|
||||
{ (map[it] as? SearchItemResult.Success)?.isEmpty ?: true },
|
||||
{ "${it.id}" !in pinnedSources },
|
||||
|
|
|
|||
|
|
@ -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<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
selectedMangaIds,
|
||||
)
|
||||
navigator.push(MigrateMangaConfigScreen(selectedMangaIds))
|
||||
} else {
|
||||
context.toast(SYMR.strings.no_valid_entry)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SourcePreferences>().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<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
state.selected.map { it.error.mangaId },
|
||||
)
|
||||
navigator.push(MigrateMangaConfigScreen(state.selected.map { it.error.mangaId }))
|
||||
},
|
||||
onSelectAll = screenModel::toggleAllSelection,
|
||||
onInvertSelection = screenModel::invertSelection,
|
||||
|
|
|
|||
|
|
@ -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<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
listOfNotNull(successState.manga.id),
|
||||
)
|
||||
// SY <--
|
||||
navigator.push(MigrateMangaConfigScreen(listOf(successState.manga.id)))
|
||||
}.takeIf { successState.manga.favorite },
|
||||
// SY -->
|
||||
previewsRowCount = successState.previewsRowCount,
|
||||
|
|
|
|||
|
|
@ -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<Long>) : 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<ScreenModel.State>(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<Long, Int> /* KMK <-- */ ->
|
||||
compareBy<MigrationSource>(
|
||||
// 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<MigrationSource>) -> List<MigrationSource>) {
|
||||
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<HttpSource>()
|
||||
.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<MigrationSource> = 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/container"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?android:attr/colorBackground"
|
||||
app:cardElevation="0dp"
|
||||
app:cardForegroundColor="@color/draggable_card_foreground">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/anim_browse_enter" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="8dp"
|
||||
android:text="Title"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/reorder"
|
||||
app:layout_constraintStart_toEndOf="@+id/image"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/reorder"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_drag_handle_24dp"
|
||||
app:tint="?android:attr/textColorHint" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/migration_source_item" />
|
||||
|
|
@ -24,6 +24,18 @@ interface PreferenceStore {
|
|||
fun getAll(): Map<String, *>
|
||||
}
|
||||
|
||||
fun PreferenceStore.getLongArray(
|
||||
key: String,
|
||||
defaultValue: List<Long>,
|
||||
): Preference<List<Long>> {
|
||||
return getObject(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = { it.joinToString(",") },
|
||||
deserializer = { it.split(",").mapNotNull { l -> l.toLongOrNull() } },
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
|
|
@ -35,7 +47,7 @@ inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
|
|||
deserializer = {
|
||||
try {
|
||||
enumValueOf(it)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
defaultValue
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@
|
|||
<string name="action_scroll_to_bottom">التمرير إلى الأسفل</string>
|
||||
<string name="action_scroll_to_previous">التمرير إلى السابق</string>
|
||||
<string name="action_scroll_to_next">التمرير إلى التالي</string>
|
||||
<string name="migrating_all_entries">ترحيل جميع المدخلات من المصدر</string>
|
||||
<string name="label_to_be_updated">سيتم تحديثها</string>
|
||||
<string name="pref_manga_info">معلومات المانجا</string>
|
||||
<string name="manual_download">تنزيل يدوي</string>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@
|
|||
<string name="action_sort_feed">ফিড ছৰ্ট কৰক</string>
|
||||
<string name="sort_feed_confirmation">আপুনি ফিডবোৰ বৰ্ণানুক্ৰমে ছৰ্ট কৰিব বিচাৰে নেকি?</string>
|
||||
<string name="current_">বৰ্তমান: %1$s</string>
|
||||
<string name="migrating_all_entries">উৎসৰ পৰা সকলো এণ্ট্ৰি মাইগ্ৰেট কৰি আছে</string>
|
||||
<string name="label_to_be_updated">আপডেট কৰিবলগীয়া</string>
|
||||
<string name="sponsor_me">মোক স্পনছৰ কৰক</string>
|
||||
<string name="batch_add_description">সমৰ্থন: MangaDex, E-H, ExH, nH, 8Muses, Tsumino</string>
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@
|
|||
<string name="sort_feed_confirmation">Would you like to sort the feeds alphabetically?</string>
|
||||
<!-- Migration -->
|
||||
<string name="current_">Current: %1$s</string>
|
||||
<string name="migrating_all_entries">Migrating all entries from source</string>
|
||||
<!-- Misc -->
|
||||
<string name="job_failed_schedule_update_check">Failed to schedule automatic library update: %s</string>
|
||||
<string name="job_failed_schedule_backup_check">Failed to schedule automatic backup: %s</string>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@
|
|||
<string name="action_scroll_to_bottom">Přejít na konec</string>
|
||||
<string name="action_scroll_to_previous">Přejít na předchozí</string>
|
||||
<string name="action_scroll_to_next">Přejít na další</string>
|
||||
<string name="migrating_all_entries">Migrovat všechny záznamy ze zdroje</string>
|
||||
<string name="label_to_be_updated">Bude aktualizováno</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Ruční stahování</string>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@
|
|||
<string name="action_scroll_to_bottom">Rul til bunden</string>
|
||||
<string name="action_scroll_to_previous">Rul til forrige</string>
|
||||
<string name="action_scroll_to_next">Rul til næste</string>
|
||||
<string name="migrating_all_entries">Migrerer alle poster fra kilde</string>
|
||||
<string name="label_to_be_updated">Skal opdateres</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Manuel download</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">Nach unten scrollen</string>
|
||||
<string name="action_scroll_to_previous">Zum Vorherigen scrollen</string>
|
||||
<string name="action_scroll_to_next">Zum Nächsten scrollen</string>
|
||||
<string name="migrating_all_entries">Alle Einträge aus der Quelle migrieren</string>
|
||||
<string name="label_to_be_updated">Zu aktualisieren</string>
|
||||
<string name="pref_manga_info">Manga-Info</string>
|
||||
<string name="manual_download">Manueller Download</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">Κύλιση προς τα κάτω</string>
|
||||
<string name="action_scroll_to_previous">Κύλιση στο προηγούμενο</string>
|
||||
<string name="action_scroll_to_next">Κύλιση στο επόμενο</string>
|
||||
<string name="migrating_all_entries">Μεταφορά όλων των καταχωρήσεων από την πηγή</string>
|
||||
<string name="label_to_be_updated">Να ενημερωθεί</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Χειροκίνητη λήψη</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">Desplazar al final</string>
|
||||
<string name="action_scroll_to_previous">Desplazar al anterior</string>
|
||||
<string name="action_scroll_to_next">Desplazar al siguiente</string>
|
||||
<string name="migrating_all_entries">Migrando todas las entradas desde la fuente</string>
|
||||
<string name="label_to_be_updated">Para actualizar</string>
|
||||
<string name="pref_manga_info">Información del manga</string>
|
||||
<string name="manual_download">Descarga manual</string>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@
|
|||
<string name="action_scroll_to_bottom">Siirry alareunaan</string>
|
||||
<string name="action_scroll_to_previous">Vieritä edelliseen</string>
|
||||
<string name="action_scroll_to_next">Vieritä seuraavaan</string>
|
||||
<string name="migrating_all_entries">Siirretään kaikki merkinnät lähteestä</string>
|
||||
<string name="label_to_be_updated">Päivittääksesi</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Manuaalinen lataus</string>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@
|
|||
<string name="action_scroll_to_bottom">Mag-scroll paibaba</string>
|
||||
<string name="action_scroll_to_previous">Mag-scroll sa nakaraan</string>
|
||||
<string name="action_scroll_to_next">Mag-scroll sa susunod</string>
|
||||
<string name="migrating_all_entries">Nililipat ang lahat ng entry mula sa source</string>
|
||||
<string name="label_to_be_updated">Para ma-update</string>
|
||||
<string name="pref_manga_info">Impo ng Manga</string>
|
||||
<string name="manual_download">Mano-manong I-downloa</string>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@
|
|||
<string name="action_scroll_to_bottom">Défiler vers le bas</string>
|
||||
<string name="action_scroll_to_previous">Défiler vers le précédent</string>
|
||||
<string name="action_scroll_to_next">Défiler vers le suivant</string>
|
||||
<string name="migrating_all_entries">Migrer toutes les entrées de la source</string>
|
||||
<string name="label_to_be_updated">A mettre à jour</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Téléchargement manuel</string>
|
||||
|
|
|
|||
|
|
@ -146,7 +146,6 @@
|
|||
<string name="action_scroll_to_previous">Gulir ke sebelumnya</string>
|
||||
<string name="action_sort_feed">Urutkan feed</string>
|
||||
<string name="sort_feed_confirmation">Apakah Anda ingin mengurutkan feed berdasarkan abjad?</string>
|
||||
<string name="migrating_all_entries">Memigrasikan semua entri dari sumber</string>
|
||||
<string name="job_failed_schedule_update_check">Gagal menjadwalkan pembaruan perpustakaan otomatis: %s</string>
|
||||
<string name="job_failed_schedule_backup_check">Gagal menjadwalkan pencadangan otomatis: %s</string>
|
||||
<string name="label_to_be_updated">Untuk diperbarui</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">Scorri verso il basso</string>
|
||||
<string name="action_scroll_to_previous">Scorri al precedente</string>
|
||||
<string name="action_scroll_to_next">Scorri al successivo</string>
|
||||
<string name="migrating_all_entries">Migrazione di tutte le voci dalla sorgente</string>
|
||||
<string name="label_to_be_updated">Da aggiornare</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Download manuale</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">一番下までスクロール</string>
|
||||
<string name="action_scroll_to_previous">前までスクロール</string>
|
||||
<string name="action_scroll_to_next">次までスクロール</string>
|
||||
<string name="migrating_all_entries">すべての項目をソースから移行中</string>
|
||||
<string name="label_to_be_updated">更新予定</string>
|
||||
<string name="pref_manga_info">マンガ情報</string>
|
||||
<string name="manual_download">手動でダウンロード</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">Scroll naar beneden</string>
|
||||
<string name="action_scroll_to_previous">Scroll naar vorige</string>
|
||||
<string name="action_scroll_to_next">Scroll naar volgende</string>
|
||||
<string name="migrating_all_entries">Alle invoergegevens vanuit bron migreren</string>
|
||||
<string name="label_to_be_updated">Te updaten</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Handmatige download</string>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@
|
|||
<string name="action_scroll_to_bottom">Przewiń na dół</string>
|
||||
<string name="action_scroll_to_previous">Przewiń do poprzedniego</string>
|
||||
<string name="action_scroll_to_next">Przewiń do następnego</string>
|
||||
<string name="migrating_all_entries">Migracja wszystkich wpisów ze źródła</string>
|
||||
<string name="label_to_be_updated">Do aktualizacji</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Ręczne pobieranie</string>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@
|
|||
<string name="action_scroll_to_bottom">Rolar para o final</string>
|
||||
<string name="action_scroll_to_previous">Rolar para o anterior</string>
|
||||
<string name="action_scroll_to_next">Rolar para o próximo</string>
|
||||
<string name="migrating_all_entries">Migrando todas as entradas da fonte</string>
|
||||
<string name="label_to_be_updated">A ser atualizado</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Download Manual</string>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@
|
|||
<string name="action_scroll_to_bottom">Rolar para o final</string>
|
||||
<string name="action_scroll_to_previous">Rolar para o anterior</string>
|
||||
<string name="action_scroll_to_next">Rolar para o próximo</string>
|
||||
<string name="migrating_all_entries">Migrando todas as entradas da fonte</string>
|
||||
<string name="label_to_be_updated">A ser atualizado</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Download manual</string>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@
|
|||
<string name="action_scroll_to_bottom">Derulează până jos</string>
|
||||
<string name="action_scroll_to_previous">Derulează până la anterioară</string>
|
||||
<string name="action_scroll_to_next">Derulează mai departe</string>
|
||||
<string name="migrating_all_entries">Migrarea tuturor intrărilor de la sursă</string>
|
||||
<string name="label_to_be_updated">A se actualiza</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Descărcare manuală</string>
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@
|
|||
<string name="action_scroll_to_bottom">Прокрутка вниз</string>
|
||||
<string name="action_scroll_to_previous">Прокрутка до предыдущей</string>
|
||||
<string name="action_scroll_to_next">Прокрутка до следующего</string>
|
||||
<string name="migrating_all_entries">Миграция всех записей из источника</string>
|
||||
<string name="label_to_be_updated">Будет обновлено</string>
|
||||
<string name="pref_manga_info">Информация о манге</string>
|
||||
<string name="manual_download">Ручная загрузка</string>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
<string name="action_scroll_to_bottom">Rulla till botten</string>
|
||||
<string name="action_scroll_to_previous">Bläddra till föregående</string>
|
||||
<string name="action_scroll_to_next">Bläddra till nästa</string>
|
||||
<string name="migrating_all_entries">Migrerar alla poster från källan</string>
|
||||
<string name="label_to_be_updated">Att uppdateras</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Manuell nedladdning</string>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@
|
|||
<string name="action_scroll_to_bottom">Прокрутити до кінця</string>
|
||||
<string name="action_scroll_to_previous">Прокрутити до попереднього</string>
|
||||
<string name="action_scroll_to_next">Перейти до наступного</string>
|
||||
<string name="migrating_all_entries">Міграція всіх записів з джерела</string>
|
||||
<string name="label_to_be_updated">Оновлювати</string>
|
||||
<string name="pref_manga_info">Manga Info</string>
|
||||
<string name="manual_download">Ручне завантаження</string>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@
|
|||
<string name="action_scroll_to_bottom">Cuộn xuống dưới cùng</string>
|
||||
<string name="action_scroll_to_previous">Nhảy tới nhóm trước</string>
|
||||
<string name="action_scroll_to_next">Nhảy tới nhóm sau</string>
|
||||
<string name="migrating_all_entries">Di chuyển tất cả truyện của nguồn</string>
|
||||
<string name="label_to_be_updated">Truyện sẽ được cập nhật</string>
|
||||
<string name="pref_manga_info">Trang thông tin truyện</string>
|
||||
<string name="manual_download">Tải về thủ công</string>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@
|
|||
<string name="action_scroll_to_bottom">滚动到底部</string>
|
||||
<string name="action_scroll_to_previous">滚动到上一个</string>
|
||||
<string name="action_scroll_to_next">滚动到下一个</string>
|
||||
<string name="migrating_all_entries">从源代码迁移所有条目</string>
|
||||
<string name="label_to_be_updated">待更新</string>
|
||||
<string name="pref_manga_info">漫画信息</string>
|
||||
<string name="manual_download">手动下载</string>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@
|
|||
<string name="action_scroll_to_bottom">移至底部</string>
|
||||
<string name="action_scroll_to_previous">移至上一類</string>
|
||||
<string name="action_scroll_to_next">移至下一類</string>
|
||||
<string name="migrating_all_entries">正在從來源遷移所有項目</string>
|
||||
<string name="label_to_be_updated">待更新</string>
|
||||
<string name="pref_manga_info">漫畫資訊</string>
|
||||
<string name="manual_download">手動下載</string>
|
||||
|
|
|
|||
|
|
@ -1007,4 +1007,12 @@
|
|||
|
||||
<!-- Notes screen -->
|
||||
<string name="notes_placeholder">Enjoyed the part where…</string>
|
||||
|
||||
<string name="migrationConfigScreen.selectedHeader">Selected</string>
|
||||
<string name="migrationConfigScreen.availableHeader">Available</string>
|
||||
<string name="migrationConfigScreen.selectAllLabel">Select all</string>
|
||||
<string name="migrationConfigScreen.selectNoneLabel">Select none</string>
|
||||
<string name="migrationConfigScreen.selectEnabledLabel">Select enabled sources</string>
|
||||
<string name="migrationConfigScreen.selectPinnedLabel">Select pinned sources</string>
|
||||
<string name="migrationConfigScreen.continueButtonText">Continue</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in a new issue