diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt index 5f7dae988c..a507e0bcab 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.browse +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -7,6 +8,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowDownward import androidx.compose.material.icons.outlined.ArrowUpward @@ -26,14 +28,16 @@ import androidx.compose.ui.text.style.TextOverflow import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.browse.components.SourceIcon +import eu.kanade.presentation.util.animateItemFastScroll import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.collections.immutable.ImmutableList import tachiyomi.domain.source.model.Source import tachiyomi.i18n.MR +import tachiyomi.i18n.kmk.KMR import tachiyomi.presentation.core.components.Badge import tachiyomi.presentation.core.components.BadgeGroup -import tachiyomi.presentation.core.components.ScrollbarLazyColumn +import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.topSmallPaddingValues @@ -54,11 +58,17 @@ fun MigrateSourceScreen( // SY --> onClickAll: (Source) -> Unit, // SY <-- + // KMK --> + onChangeSearchQuery: (String?) -> Unit, + // KMK <-- ) { val context = LocalContext.current when { state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) - state.isEmpty -> EmptyScreen( + // KMK --> + state.searchQuery.isNullOrBlank() && + // KMK <-- + state.isEmpty -> EmptyScreen( stringRes = MR.strings.information_empty_library, modifier = Modifier.padding(contentPadding), ) @@ -78,6 +88,10 @@ fun MigrateSourceScreen( // SY --> onClickAll = onClickAll, // SY <-- + // KMK --> + state = state, + onChangeSearchQuery = onChangeSearchQuery, + // KMK <-- ) } } @@ -95,64 +109,94 @@ private fun MigrateSourceList( // SY --> onClickAll: (Source) -> Unit, // SY <-- + // KMK --> + state: MigrateSourceScreenModel.State, + onChangeSearchQuery: (String?) -> Unit, + // KMK <-- ) { - ScrollbarLazyColumn( - contentPadding = contentPadding + topSmallPaddingValues, + // KMK --> + val lazyListState = rememberLazyListState() + + BackHandler(enabled = !state.searchQuery.isNullOrBlank()) { + onChangeSearchQuery(null) + } + + Column( + // Wrap around so we can use stickyHeader + modifier = Modifier.padding(contentPadding + topSmallPaddingValues), ) { - stickyHeader(key = STICKY_HEADER_KEY_PREFIX) { - Row( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .padding(start = MaterialTheme.padding.medium), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(MR.strings.migration_selection_prompt), - modifier = Modifier.weight(1f), - style = MaterialTheme.typography.header, - ) + AnimatedFloatingSearchBox( + listState = lazyListState, + searchQuery = state.searchQuery, + onChangeSearchQuery = onChangeSearchQuery, + placeholderText = stringResource(KMR.strings.action_search_for_source), + ) - IconButton(onClick = onToggleSortingMode) { - when (sortingMode) { - SetMigrateSorting.Mode.ALPHABETICAL -> Icon( - Icons.Outlined.SortByAlpha, - contentDescription = stringResource(MR.strings.action_sort_alpha), - ) - SetMigrateSorting.Mode.TOTAL -> Icon( - Icons.Outlined.Numbers, - contentDescription = stringResource(MR.strings.action_sort_count), - ) + FastScrollLazyColumn( + state = lazyListState, + // contentPadding = contentPadding + topSmallPaddingValues, + // KMK <-- + ) { + stickyHeader(key = STICKY_HEADER_KEY_PREFIX) { + Row( + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .padding(start = MaterialTheme.padding.medium), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(MR.strings.migration_selection_prompt), + modifier = Modifier.weight(1f), + style = MaterialTheme.typography.header, + ) + + IconButton(onClick = onToggleSortingMode) { + when (sortingMode) { + SetMigrateSorting.Mode.ALPHABETICAL -> Icon( + Icons.Outlined.SortByAlpha, + contentDescription = stringResource(MR.strings.action_sort_alpha), + ) + + SetMigrateSorting.Mode.TOTAL -> Icon( + Icons.Outlined.Numbers, + contentDescription = stringResource(MR.strings.action_sort_count), + ) + } } - } - IconButton(onClick = onToggleSortingDirection) { - when (sortingDirection) { - SetMigrateSorting.Direction.ASCENDING -> Icon( - Icons.Outlined.ArrowUpward, - contentDescription = stringResource(MR.strings.action_asc), - ) - SetMigrateSorting.Direction.DESCENDING -> Icon( - Icons.Outlined.ArrowDownward, - contentDescription = stringResource(MR.strings.action_desc), - ) + IconButton(onClick = onToggleSortingDirection) { + when (sortingDirection) { + SetMigrateSorting.Direction.ASCENDING -> Icon( + Icons.Outlined.ArrowUpward, + contentDescription = stringResource(MR.strings.action_asc), + ) + + SetMigrateSorting.Direction.DESCENDING -> Icon( + Icons.Outlined.ArrowDownward, + contentDescription = stringResource(MR.strings.action_desc), + ) + } } } } - } - items( - items = list, - key = { (source, _) -> "migrate-${source.id}" }, - ) { (source, count) -> - MigrateSourceItem( - modifier = Modifier.animateItem(), - source = source, - count = count, - onClickItem = { onClickItem(source) }, - onLongClickItem = { onLongClickItem(source) }, - // SY --> - onClickAll = { onClickAll(source) }, - // SY <-- - ) + items( + items = list, + key = { (source, _) -> "migrate-${source.id}" }, + ) { (source, count) -> + MigrateSourceItem( + // KMK --> + // modifier = Modifier.animateItem(), + modifier = Modifier.animateItemFastScroll(), + // KMK <-- + source = source, + count = count, + onClickItem = { onClickItem(source) }, + onLongClickItem = { onLongClickItem(source) }, + // SY --> + onClickAll = { onClickAll(source) }, + // SY <-- + ) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt index 8968d62b82..4575e94ea4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt @@ -5,14 +5,20 @@ import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount import eu.kanade.domain.source.interactor.SetMigrateSorting +import eu.kanade.domain.source.model.installedExtension import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update @@ -34,7 +40,29 @@ class MigrateSourceScreenModel( init { screenModelScope.launchIO { - getSourcesWithFavoriteCount.subscribe() + // KMK --> + combine( + state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + // KMK <-- + getSourcesWithFavoriteCount.subscribe(), + // KMK --> + ) { searchQuery, sourceCounts -> + val queryFilter: (String?) -> ((Pair) -> Boolean) = { query -> + filter@{ pair -> + val source = pair.first + if (query.isNullOrBlank()) return@filter true + query.split(",").any { + val input = it.trim() + if (input.isEmpty()) return@any false + source.installedExtension?.name?.contains(input, ignoreCase = true) == true || + source.name.contains(input, ignoreCase = true) || + source.id == input.toLongOrNull() + } + } + } + sourceCounts.filter(queryFilter(searchQuery)) + } + // KMK <-- .catch { logcat(LogPriority.ERROR, it) _channel.send(Event.FailedFetchingSourcesWithCount) @@ -80,12 +108,23 @@ class MigrateSourceScreenModel( } } + // KMK --> + fun search(query: String?) { + mutableState.update { + it.copy(searchQuery = query) + } + } + // KMK <-- + @Immutable data class State( val isLoading: Boolean = true, val items: ImmutableList> = persistentListOf(), val sortingMode: SetMigrateSorting.Mode = SetMigrateSorting.Mode.ALPHABETICAL, val sortingDirection: SetMigrateSorting.Direction = SetMigrateSorting.Direction.ASCENDING, + // KMK --> + val searchQuery: String? = null, + // KMK <-- ) { val isEmpty = items.isEmpty() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt index 444ccf5799..563dff5862 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt @@ -71,6 +71,9 @@ fun Screen.migrateSourceTab(): TabContent { } }, // SY <-- + // KMK --> + onChangeSearchQuery = screenModel::search, + // KMK <-- ) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt index 952fe9df50..5197f6b589 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt @@ -196,6 +196,14 @@ class SourcesScreenModel( } // SY <-- + fun showSourceDialog(source: Source) { + mutableState.update { it.copy(dialog = Dialog.SourceLongClick(source)) } + } + + fun closeDialog() { + mutableState.update { it.copy(dialog = null) } + } + // KMK --> fun search(query: String?) { mutableState.update { @@ -210,14 +218,6 @@ class SourcesScreenModel( } // KMK <-- - fun showSourceDialog(source: Source) { - mutableState.update { it.copy(dialog = Dialog.SourceLongClick(source)) } - } - - fun closeDialog() { - mutableState.update { it.copy(dialog = null) } - } - sealed interface Event { data object FailedFetchingSources : Event }