Tweak global search source filtering

Pinned only setting is removed in favor of the UI in the global search screen itself, which defaults to pinned only.
This needs more UX improvements, but I'm not really sure what it should be like right now.
This commit is contained in:
arkon 2023-07-15 10:09:46 -04:00
parent 54733e6ceb
commit 12e7ee9d0c
8 changed files with 70 additions and 77 deletions

View file

@ -30,7 +30,5 @@ class SourcePreferences(
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet()) fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false) fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
} }

View file

@ -30,23 +30,25 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchFilter import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.VerticalDivider
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
@Composable @Composable
fun GlobalSearchScreen( fun GlobalSearchScreen(
state: GlobalSearchState, state: GlobalSearchScreenModel.State,
items: Map<CatalogueSource, SearchItemResult>, items: Map<CatalogueSource, SearchItemResult>,
navigateUp: () -> Unit, navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
onChangeFilter: (GlobalSearchFilter) -> Unit, onChangeSearchFilter: (SourceFilter) -> Unit,
onToggleResults: () -> Unit,
getManga: @Composable (Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
@ -71,13 +73,29 @@ fun GlobalSearchScreen(
.padding(horizontal = MaterialTheme.padding.small), .padding(horizontal = MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) { ) {
// TODO: make this UX better; it only applies when triggering a new search
FilterChip( FilterChip(
selected = state.searchFilter == GlobalSearchFilter.All, selected = state.sourceFilter == SourceFilter.PinnedOnly,
onClick = { onChangeFilter(GlobalSearchFilter.All) }, onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.PushPin,
contentDescription = null,
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.pinned_sources))
},
)
FilterChip(
selected = state.sourceFilter == SourceFilter.All,
onClick = { onChangeSearchFilter(SourceFilter.All) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.DoneAll, imageVector = Icons.Outlined.DoneAll,
contentDescription = "", contentDescription = null,
modifier = Modifier modifier = Modifier
.size(FilterChipDefaults.IconSize), .size(FilterChipDefaults.IconSize),
) )
@ -87,29 +105,15 @@ fun GlobalSearchScreen(
}, },
) )
FilterChip( VerticalDivider()
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( FilterChip(
selected = state.searchFilter == GlobalSearchFilter.AvailableOnly, selected = state.onlyShowHasResults,
onClick = { onChangeFilter(GlobalSearchFilter.AvailableOnly) }, onClick = { onToggleResults() },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.FilterList, imageVector = Icons.Outlined.FilterList,
contentDescription = "", contentDescription = null,
modifier = Modifier modifier = Modifier
.size(FilterChipDefaults.IconSize), .size(FilterChipDefaults.IconSize),
) )

View file

@ -29,10 +29,6 @@ object SettingsBrowseScreen : SearchableSettings {
Preference.PreferenceGroup( Preference.PreferenceGroup(
title = stringResource(R.string.label_sources), title = stringResource(R.string.label_sources),
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.searchPinnedSourcesOnly(),
title = stringResource(R.string.pref_search_pinned_sources_only),
),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.hideInLibraryItems(), pref = sourcePreferences.hideInLibraryItems(),
title = stringResource(R.string.pref_hide_in_library_items), title = stringResource(R.string.pref_hide_in_library_items),

View file

@ -10,6 +10,7 @@ import eu.kanade.presentation.browse.MigrateSearchScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
class MigrateSearchScreen(private val mangaId: Long) : Screen() { class MigrateSearchScreen(private val mangaId: Long) : Screen() {
@Composable @Composable

View file

@ -63,7 +63,8 @@ class GlobalSearchScreen(
onChangeSearchQuery = screenModel::updateSearchQuery, onChangeSearchQuery = screenModel::updateSearchQuery,
onSearch = screenModel::search, onSearch = screenModel::search,
getManga = { screenModel.getManga(it) }, getManga = { screenModel.getManga(it) },
onChangeFilter = screenModel::setFilter, onChangeSearchFilter = screenModel::setSourceFilter,
onToggleResults = screenModel::toggleFilterResults,
onClickSource = { onClickSource = {
if (!screenModel.incognitoMode.get()) { if (!screenModel.incognitoMode.get()) {
screenModel.lastUsedSourceId.set(it.id) screenModel.lastUsedSourceId.set(it.id)

View file

@ -20,17 +20,17 @@ class GlobalSearchScreenModel(
preferences: BasePreferences = Injekt.get(), preferences: BasePreferences = Injekt.get(),
private val sourcePreferences: SourcePreferences = Injekt.get(), private val sourcePreferences: SourcePreferences = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
) : SearchScreenModel<GlobalSearchState>(GlobalSearchState(searchQuery = initialQuery)) { ) : SearchScreenModel<GlobalSearchScreenModel.State>(State(searchQuery = initialQuery)) {
val incognitoMode = preferences.incognitoMode() val incognitoMode = preferences.incognitoMode()
val lastUsedSourceId = sourcePreferences.lastUsedSource() val lastUsedSourceId = sourcePreferences.lastUsedSource()
val searchPagerFlow = state.map { Pair(it.searchFilter, it.items) } val searchPagerFlow = state.map { Pair(it.onlyShowHasResults, it.items) }
.distinctUntilChanged() .distinctUntilChanged()
.map { (filter, items) -> .map { (onlyShowHasResults, items) ->
items items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
.filter { (source, result) -> isSourceVisible(filter, source, result) } }
}.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items) .stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items)
init { init {
extensionFilter = initialExtensionFilter extensionFilter = initialExtensionFilter
@ -45,19 +45,12 @@ class GlobalSearchScreenModel(
val pinnedSources = sourcePreferences.pinnedSources().get() val pinnedSources = sourcePreferences.pinnedSources().get()
return sourceManager.getCatalogueSources() return sourceManager.getCatalogueSources()
.filter { mutableState.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
.filter { it.lang in enabledLanguages } .filter { it.lang in enabledLanguages }
.filterNot { "${it.id}" in disabledSources } .filterNot { "${it.id}" in disabledSources }
.sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" })) .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?) { override fun updateSearchQuery(query: String?) {
mutableState.update { mutableState.update {
it.copy(searchQuery = query) it.copy(searchQuery = query)
@ -70,27 +63,32 @@ class GlobalSearchScreenModel(
} }
} }
fun setFilter(filter: GlobalSearchFilter) {
mutableState.update { it.copy(searchFilter = filter) }
}
override fun getItems(): Map<CatalogueSource, SearchItemResult> { override fun getItems(): Map<CatalogueSource, SearchItemResult> {
return mutableState.value.items return mutableState.value.items
} }
}
enum class GlobalSearchFilter { fun setSourceFilter(filter: SourceFilter) {
All, PinnedOnly, AvailableOnly mutableState.update { it.copy(sourceFilter = filter) }
} }
@Immutable fun toggleFilterResults() {
data class GlobalSearchState( mutableState.update {
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
}
}
private fun SearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
return !onlyShowHasResults || (this is SearchItemResult.Success && !this.isEmpty)
}
@Immutable
data class State(
val searchQuery: String? = null, val searchQuery: String? = null,
val searchFilter: GlobalSearchFilter = GlobalSearchFilter.All, val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
val onlyShowHasResults: Boolean = false,
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(), val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
) { ) {
val progress: Int = items.count { it.value !is SearchItemResult.Loading } val progress: Int = items.count { it.value !is SearchItemResult.Loading }
val total: Int = items.size val total: Int = items.size
}
} }

View file

@ -68,16 +68,7 @@ abstract class SearchScreenModel<T>(
val enabledSources = getEnabledSources() val enabledSources = getEnabledSources()
if (filter.isEmpty()) { if (filter.isEmpty()) {
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get() return enabledSources
val pinnedSources = sourcePreferences.pinnedSources().get()
return enabledSources.filter {
if (shouldSearchPinnedOnly) {
"${it.id}" in pinnedSources
} else {
true
}
}
} }
return extensionManager.installedExtensionsFlow.value return extensionManager.installedExtensionsFlow.value
@ -137,6 +128,11 @@ abstract class SearchScreenModel<T>(
} }
} }
enum class SourceFilter {
All,
PinnedOnly,
}
sealed class SearchItemResult { sealed class SearchItemResult {
object Loading : SearchItemResult() object Loading : SearchItemResult()

View file

@ -480,7 +480,6 @@
<string name="action_track">Track</string> <string name="action_track">Track</string>
<!-- Browse section --> <!-- Browse section -->
<string name="pref_search_pinned_sources_only">Only search pinned sources in global search</string>
<string name="pref_hide_in_library_items">Hide entries already in library</string> <string name="pref_hide_in_library_items">Hide entries already in library</string>
<!-- Backup section --> <!-- Backup section -->