mirror of
https://github.com/mihonapp/mihon.git
synced 2025-10-09 12:59:34 +02:00
Add migration config screen to select and prioritize target sources (#2144)
This commit is contained in:
@@ -5,6 +5,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(
|
||||
@@ -20,6 +21,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())
|
||||
|
@@ -40,6 +40,7 @@ fun GlobalSearchScreen(
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
hideSourceFilter = false,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
|
@@ -32,6 +32,7 @@ fun MigrateSearchScreen(
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
hideSourceFilter = true,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
|
@@ -40,6 +40,7 @@ fun GlobalSearchToolbar(
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
hideSourceFilter: Boolean,
|
||||
sourceFilter: SourceFilter,
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onlyShowHasResults: Boolean,
|
||||
@@ -73,6 +74,7 @@ fun GlobalSearchToolbar(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
// TODO: make this UX better; it only applies when triggering a new search
|
||||
if (!hideSourceFilter) {
|
||||
FilterChip(
|
||||
selected = sourceFilter == SourceFilter.PinnedOnly,
|
||||
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
||||
@@ -105,6 +107,7 @@ fun GlobalSearchToolbar(
|
||||
)
|
||||
|
||||
VerticalDivider()
|
||||
}
|
||||
|
||||
FilterChip(
|
||||
selected = onlyShowHasResults,
|
||||
|
@@ -10,10 +10,10 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.MigrateMangaScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||
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.presentation.core.screens.LoadingScreen
|
||||
|
||||
@@ -38,7 +38,7 @@ data class MigrateMangaScreen(
|
||||
navigateUp = navigator::pop,
|
||||
title = state.source!!.name,
|
||||
state = state,
|
||||
onClickItem = { navigator.push(MigrateSearchScreen(it.id)) },
|
||||
onClickItem = { navigator.push(MigrateMangaConfigScreen(it.id)) },
|
||||
onClickCover = { navigator.push(MangaScreen(it.id)) },
|
||||
)
|
||||
|
||||
|
@@ -1,20 +1,33 @@
|
||||
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
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MigrateSearchScreenModel(
|
||||
val mangaId: Long,
|
||||
getManga: GetManga = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
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)!!
|
||||
@@ -29,14 +42,6 @@ class MigrateSearchScreenModel(
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<CatalogueSource> {
|
||||
return super.getEnabledSources()
|
||||
.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})" },
|
||||
),
|
||||
)
|
||||
return migrationSources.mapNotNull { sourceManager.get(it) as? CatalogueSource }
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,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 },
|
||||
|
@@ -45,7 +45,6 @@ import eu.kanade.tachiyomi.source.isLocalOrStub
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialog
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateDialogScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
@@ -60,6 +59,7 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import mihon.feature.migration.MigrateMangaConfigScreen
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
@@ -163,7 +163,7 @@ class MangaScreen(
|
||||
successState.manga.favorite
|
||||
},
|
||||
onMigrateClicked = {
|
||||
navigator.push(MigrateSearchScreen(successState.manga.id))
|
||||
navigator.push(MigrateMangaConfigScreen(successState.manga.id))
|
||||
}.takeIf { successState.manga.favorite },
|
||||
onEditNotesClicked = { navigator.push(MangaNotesScreen(manga = successState.manga)) },
|
||||
onMultiBookmarkClicked = screenModel::bookmarkChapters,
|
||||
|
@@ -0,0 +1,396 @@
|
||||
package mihon.feature.migration
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.SelectAll
|
||||
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.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.remember
|
||||
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.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
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.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.Pill
|
||||
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MigrateMangaConfigScreen(private val mangaId: Long) : Screen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { ScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
val (selectedSources, availableSources) = state.sources.partition { it.isSelected }
|
||||
val showLanguage by remember(state) {
|
||||
derivedStateOf {
|
||||
state.sources.distinctBy { it.source.lang }.size > 1
|
||||
}
|
||||
}
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = null,
|
||||
navigateUp = navigator::pop,
|
||||
scrollBehavior = it,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.migrationConfigScreen_selectAllLabel),
|
||||
icon = Icons.Outlined.SelectAll,
|
||||
onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.All) },
|
||||
),
|
||||
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_selectPinnedLabel),
|
||||
onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.Pinned) },
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) },
|
||||
icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) },
|
||||
onClick = { navigator.replace(MigrateSearchScreen(mangaId)) },
|
||||
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)
|
||||
}
|
||||
|
||||
FastScrollLazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = lazyListState,
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 = 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()) {
|
||||
|
||||
init {
|
||||
screenModelScope.launchIO {
|
||||
initSources()
|
||||
}
|
||||
}
|
||||
|
||||
private val sourcesComparator = { includedSources: List<Long> ->
|
||||
compareBy<MigrationSource>(
|
||||
{ !it.isSelected },
|
||||
{ includedSources.indexOf(it.source.id) },
|
||||
{ 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 }
|
||||
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 pinnedSources = sourcePreferences.pinnedSources().get().mapNotNull { it.toLongOrNull() }
|
||||
val includedSources = sourcePreferences.migrationSources().get()
|
||||
val disabledSources = sourcePreferences.disabledSources().get()
|
||||
.mapNotNull { it.toLongOrNull() }
|
||||
val sources = sourceManager.getCatalogueSources()
|
||||
.asSequence()
|
||||
.filterIsInstance<HttpSource>()
|
||||
.filter { it.lang in languages }
|
||||
.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 pinnedSources = sourcePreferences.pinnedSources().get().mapNotNull { it.toLongOrNull() }
|
||||
val disabledSources = sourcePreferences.disabledSources().get().mapNotNull { it.toLongOrNull() }
|
||||
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
|
||||
}
|
||||
}
|
@@ -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,
|
||||
|
@@ -995,4 +995,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>
|
||||
|
Reference in New Issue
Block a user