From cbcec8c4d9d6f08ecb6ab50eff9aa51d4a3728a0 Mon Sep 17 00:00:00 2001 From: zaghdaneh <46049558+zaghdaneh@users.noreply.github.com> Date: Sat, 15 Jul 2023 04:49:14 +0200 Subject: [PATCH] Add filters to Global search (#9691) * add pinned and available filter chips to global search * split filter predicate into seperate function * change the global search available filter to has Results * reordering of imports --- .../presentation/browse/GlobalSearchScreen.kt | 97 +++++++++++++++++-- .../source/globalsearch/GlobalSearchScreen.kt | 3 + .../globalsearch/GlobalSearchScreenModel.kt | 29 ++++++ i18n/src/main/res/values/strings.xml | 1 + 4 files changed, 120 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt index 7900530639..886f9960a2 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt @@ -1,8 +1,22 @@ package eu.kanade.presentation.browse +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.DoneAll +import androidx.compose.material.icons.outlined.FilterList +import androidx.compose.material.icons.outlined.PushPin +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -16,19 +30,23 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchFilter import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.util.system.LocaleHelper import tachiyomi.domain.manga.model.Manga +import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.padding @Composable fun GlobalSearchScreen( state: GlobalSearchState, + items: Map, navigateUp: () -> Unit, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, + onChangeFilter: (GlobalSearchFilter) -> Unit, getManga: @Composable (Manga) -> State, onClickSource: (CatalogueSource) -> Unit, onClickItem: (Manga) -> Unit, @@ -36,19 +54,78 @@ fun GlobalSearchScreen( ) { Scaffold( topBar = { scrollBehavior -> - GlobalSearchToolbar( - searchQuery = state.searchQuery, - progress = state.progress, - total = state.total, - navigateUp = navigateUp, - onChangeSearchQuery = onChangeSearchQuery, - onSearch = onSearch, - scrollBehavior = scrollBehavior, - ) + Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { + GlobalSearchToolbar( + searchQuery = state.searchQuery, + progress = state.progress, + total = state.total, + navigateUp = navigateUp, + onChangeSearchQuery = onChangeSearchQuery, + onSearch = onSearch, + scrollBehavior = scrollBehavior, + ) + + Row( + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .padding(horizontal = MaterialTheme.padding.small), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + FilterChip( + selected = state.searchFilter == GlobalSearchFilter.All, + onClick = { onChangeFilter(GlobalSearchFilter.All) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.DoneAll, + contentDescription = "", + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(id = R.string.all)) + }, + ) + + FilterChip( + selected = state.searchFilter == GlobalSearchFilter.PinnedOnly, + onClick = { onChangeFilter(GlobalSearchFilter.PinnedOnly) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.PushPin, + contentDescription = "", + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(id = R.string.pinned_sources)) + }, + ) + + FilterChip( + selected = state.searchFilter == GlobalSearchFilter.AvailableOnly, + onClick = { onChangeFilter(GlobalSearchFilter.AvailableOnly) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.FilterList, + contentDescription = "", + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(id = R.string.has_results)) + }, + ) + } + + Divider() + } }, ) { paddingValues -> GlobalSearchContent( - items = state.items, + items = items, contentPadding = paddingValues, getManga = getManga, onClickSource = onClickSource, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt index 82ed21be7b..5f1999378c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt @@ -35,6 +35,7 @@ class GlobalSearchScreen( var showSingleLoadingScreen by remember { mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1) } + val filteredSources by screenModel.searchPagerFlow.collectAsState() if (showSingleLoadingScreen) { LoadingScreen() @@ -57,10 +58,12 @@ class GlobalSearchScreen( } else { GlobalSearchScreen( state = state, + items = filteredSources, navigateUp = navigator::pop, onChangeSearchQuery = screenModel::updateSearchQuery, onSearch = screenModel::search, getManga = { screenModel.getManga(it) }, + onChangeFilter = screenModel::setFilter, onClickSource = { if (!screenModel.incognitoMode.get()) { screenModel.lastUsedSourceId.set(it.id) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt index 5c3d2b00b6..7190987f6f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt @@ -3,7 +3,12 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch import androidx.compose.runtime.Immutable import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.source.CatalogueSource +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt @@ -20,6 +25,13 @@ class GlobalSearchScreenModel( val incognitoMode = preferences.incognitoMode() val lastUsedSourceId = sourcePreferences.lastUsedSource() + val searchPagerFlow = state.map { Pair(it.searchFilter, it.items) } + .distinctUntilChanged() + .map { (filter, items) -> + items + .filter { (source, result) -> isSourceVisible(filter, source, result) } + }.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items) + init { extensionFilter = initialExtensionFilter if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) { @@ -38,6 +50,14 @@ class GlobalSearchScreenModel( .sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" })) } + private fun isSourceVisible(filter: GlobalSearchFilter, source: CatalogueSource, result: SearchItemResult): Boolean { + return when (filter) { + GlobalSearchFilter.AvailableOnly -> result is SearchItemResult.Success && !result.isEmpty + GlobalSearchFilter.PinnedOnly -> "${source.id}" in sourcePreferences.pinnedSources().get() + GlobalSearchFilter.All -> true + } + } + override fun updateSearchQuery(query: String?) { mutableState.update { it.copy(searchQuery = query) @@ -50,14 +70,23 @@ class GlobalSearchScreenModel( } } + fun setFilter(filter: GlobalSearchFilter) { + mutableState.update { it.copy(searchFilter = filter) } + } + override fun getItems(): Map { return mutableState.value.items } } +enum class GlobalSearchFilter { + All, PinnedOnly, AvailableOnly +} + @Immutable data class GlobalSearchState( val searchQuery: String? = null, + val searchFilter: GlobalSearchFilter = GlobalSearchFilter.All, val items: Map = emptyMap(), ) { diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index aed9b32eea..6984ac54a6 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -627,6 +627,7 @@ Latest Popular Browse + Has results Local source guide You have no pinned sources Chapter not found