Add search filters
This commit is contained in:
parent
3c489fc648
commit
58ebd5646b
@ -199,7 +199,7 @@ fun AssistantScaffold(
|
||||
},
|
||||
actions = actions,
|
||||
highlightedAction = searchVM.bestMatch.value as? SearchAction,
|
||||
showHiddenItemsButton = true,
|
||||
isSearchOpen = true,
|
||||
value = { value },
|
||||
onValueChange = { searchVM.search(it) },
|
||||
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
||||
|
||||
@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.icons.IconService
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import de.mm20.launcher2.search.SearchService
|
||||
import de.mm20.launcher2.search.toList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -40,7 +41,7 @@ class SearchablePickerVM : ViewModel(), KoinComponent {
|
||||
searchJob = viewModelScope.launch {
|
||||
searchService.search(
|
||||
query = query,
|
||||
allowNetwork = true,
|
||||
filters = SearchFilters(allowNetwork = true)
|
||||
).collectLatest {
|
||||
if (searchQuery != query) return@collectLatest
|
||||
items = withContext(Dispatchers.Default) {
|
||||
|
||||
@ -2,9 +2,7 @@ package de.mm20.launcher2.ui.launcher
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.LocalOverscrollConfiguration
|
||||
@ -627,7 +625,7 @@ fun PagerScaffold(
|
||||
},
|
||||
actions = actions,
|
||||
highlightedAction = searchVM.bestMatch.value as? SearchAction,
|
||||
showHiddenItemsButton = isSearchOpen,
|
||||
isSearchOpen = isSearchOpen,
|
||||
value = { value },
|
||||
onValueChange = { searchVM.search(it) },
|
||||
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
||||
|
||||
@ -603,7 +603,7 @@ fun PullDownScaffold(
|
||||
},
|
||||
actions = actions,
|
||||
highlightedAction = searchVM.bestMatch.value as? SearchAction,
|
||||
showHiddenItemsButton = isSearchOpen,
|
||||
isSearchOpen = isSearchOpen,
|
||||
value = { value },
|
||||
onValueChange = { searchVM.search(it) },
|
||||
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
||||
|
||||
@ -27,6 +27,7 @@ import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchService
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import de.mm20.launcher2.search.Location
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import de.mm20.launcher2.search.Website
|
||||
import de.mm20.launcher2.search.data.Calculator
|
||||
import de.mm20.launcher2.search.data.UnitConverter
|
||||
@ -90,6 +91,8 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
val favoritesEnabled = searchUiSettings.favorites
|
||||
val hideFavorites = mutableStateOf(false)
|
||||
|
||||
val filters = mutableStateOf(SearchFilters())
|
||||
|
||||
val separateWorkProfile = searchUiSettings.separateWorkProfile
|
||||
|
||||
private val hiddenItemKeys = searchableRepository
|
||||
@ -117,6 +120,11 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
fun setFilters(filters: SearchFilters) {
|
||||
this.filters.value = filters
|
||||
search(searchQuery.value, forceRestart = true)
|
||||
}
|
||||
|
||||
private var searchJob: Job? = null
|
||||
fun search(query: String, forceRestart: Boolean = false) {
|
||||
if (searchQuery.value == query && !forceRestart) return
|
||||
@ -124,9 +132,10 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
isSearchEmpty.value = query.isEmpty()
|
||||
hiddenResults.value = emptyList()
|
||||
|
||||
val filters = filters.value
|
||||
|
||||
if (isSearchEmpty.value)
|
||||
bestMatch.value = null
|
||||
|
||||
try {
|
||||
searchJob?.cancel()
|
||||
} catch (_: CancellationException) {
|
||||
@ -136,7 +145,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
searchUiSettings.resultOrder.collectLatest { resultOrder ->
|
||||
searchService.search(
|
||||
query,
|
||||
allowNetwork = true,
|
||||
filters = filters,
|
||||
).collectLatest { results ->
|
||||
var resultsList = withContext(Dispatchers.Default) {
|
||||
listOfNotNull(
|
||||
@ -219,10 +228,9 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
val actions = mutableListOf<SearchAction>()
|
||||
for (r in resultsList) {
|
||||
when {
|
||||
r is SavableSearchable && hiddenKeys.contains(r.key) -> {
|
||||
r is SavableSearchable && hiddenKeys.contains(r.key) && !filters.hiddenItems -> {
|
||||
hidden.add(r)
|
||||
}
|
||||
|
||||
r is Application && r.profile == AppProfile.Work -> workApps.add(r)
|
||||
r is Application -> apps.add(r)
|
||||
r is AppShortcut -> shortcuts.add(r)
|
||||
|
||||
@ -0,0 +1,239 @@
|
||||
package de.mm20.launcher2.ui.launcher.searchbar
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AppShortcut
|
||||
import androidx.compose.material.icons.rounded.Apps
|
||||
import androidx.compose.material.icons.rounded.Description
|
||||
import androidx.compose.material.icons.rounded.Handyman
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material.icons.rounded.Person
|
||||
import androidx.compose.material.icons.rounded.Place
|
||||
import androidx.compose.material.icons.rounded.Public
|
||||
import androidx.compose.material.icons.rounded.Today
|
||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.icons.Wikipedia
|
||||
import de.mm20.launcher2.ui.overlays.Overlay
|
||||
|
||||
@Composable
|
||||
fun KeyboardFilterBar(filters: SearchFilters, onFiltersChange: (SearchFilters) -> Unit) {
|
||||
Overlay {
|
||||
val allCategoriesEnabled = filters.allCategoriesEnabled
|
||||
Box(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding(), contentAlignment = Alignment.BottomCenter) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||
) {
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FilterChip(
|
||||
selected = filters.allowNetwork,
|
||||
onClick = {
|
||||
onFiltersChange(filters.copy(allowNetwork = !filters.allowNetwork))
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Language,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.search_filter_online)) }
|
||||
)
|
||||
VerticalDivider(
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.apps && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleApps())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Apps,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text("Apps") }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.files && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleFiles())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Description,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_files)) }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.contacts && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleContacts())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Person,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_contacts)) }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.events && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleEvents())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Today,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_calendar)) }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.shortcuts && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleShortcuts())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.AppShortcut,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_appshortcuts)) }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.articles && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleArticles())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Wikipedia,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_wikipedia)) }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.websites && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleWebsites())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Public,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_websites)) }
|
||||
)
|
||||
FilterChip(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
selected = filters.places && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.togglePlaces())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Place,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_search_locations)) }
|
||||
)
|
||||
FilterChip(
|
||||
selected = filters.tools && !allCategoriesEnabled,
|
||||
onClick = {
|
||||
onFiltersChange(filters.toggleTools())
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Handyman,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.search_filter_tools)) }
|
||||
)
|
||||
VerticalDivider(
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
FilterChip(
|
||||
selected = filters.hiddenItems,
|
||||
onClick = {
|
||||
onFiltersChange(filters.copy(hiddenItems = !filters.hiddenItems))
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.VisibilityOff,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preference_hidden_items)) }
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,20 +4,29 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.isImeVisible
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.text.KeyboardActionScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material.icons.rounded.FilterAlt
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
@ -37,7 +46,7 @@ fun LauncherSearchBar(
|
||||
onFocusChange: (Boolean) -> Unit,
|
||||
actions: List<SearchAction>,
|
||||
highlightedAction: SearchAction?,
|
||||
showHiddenItemsButton: Boolean = false,
|
||||
isSearchOpen: Boolean = false,
|
||||
reverse: Boolean = false,
|
||||
darkColors: Boolean = false,
|
||||
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
|
||||
@ -57,6 +66,15 @@ fun LauncherSearchBar(
|
||||
else focusManager.clearFocus()
|
||||
}
|
||||
|
||||
if (isSearchOpen && WindowInsets.isImeVisible) {
|
||||
KeyboardFilterBar(
|
||||
filters = searchVM.filters.value,
|
||||
onFiltersChange = {
|
||||
searchVM.setFilters(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val _value = value()
|
||||
|
||||
SearchBar(
|
||||
@ -66,21 +84,37 @@ fun LauncherSearchBar(
|
||||
darkColors = darkColors,
|
||||
menu = {
|
||||
AnimatedVisibility(
|
||||
hiddenItemsButtonEnabled && showHiddenItemsButton && hiddenItems.isNotEmpty(),
|
||||
isSearchOpen,
|
||||
enter = scaleIn(tween(100)),
|
||||
exit = scaleOut(tween(100))
|
||||
) {
|
||||
FilledIconButton(
|
||||
onClick = { sheetManager.showHiddenItemsSheet() },
|
||||
colors = if (sheetManager.hiddenItemsSheetShown.value) IconButtonDefaults.filledTonalIconButtonColors() else IconButtonDefaults.iconButtonColors()
|
||||
onClick = { },
|
||||
colors = IconButtonDefaults.iconButtonColors()
|
||||
) {
|
||||
Icon(imageVector = Icons.Rounded.VisibilityOff, contentDescription = null)
|
||||
Box {
|
||||
Icon(imageVector = Icons.Rounded.FilterAlt, contentDescription = null)
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
!searchVM.filters.value.allCategoriesEnabled,
|
||||
enter = scaleIn(tween(100)),
|
||||
exit = scaleOut(tween(100)),
|
||||
modifier = Modifier.align(Alignment.BottomEnd).offset(-3.dp, -3.dp)
|
||||
) {
|
||||
Badge(
|
||||
containerColor = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SearchBarMenu(searchBarValue = _value, onSearchBarValueChange = onValueChange)
|
||||
},
|
||||
actions = {
|
||||
SearchBarActions(actions = actions, reverse = reverse, highlightedAction = highlightedAction)
|
||||
SearchBarActions(
|
||||
actions = actions,
|
||||
reverse = reverse,
|
||||
highlightedAction = highlightedAction
|
||||
)
|
||||
},
|
||||
focusRequester = focusRequester,
|
||||
onFocus = { onFocusChange(true) },
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
package de.mm20.launcher2.ui.launcher.searchbar
|
||||
|
||||
@ -0,0 +1,146 @@
|
||||
package de.mm20.launcher2.ui.launcher.searchbar
|
||||
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
|
||||
fun SearchFilters.withAllCategories(): SearchFilters {
|
||||
return copy(
|
||||
apps = true,
|
||||
websites = true,
|
||||
articles = true,
|
||||
places = true,
|
||||
files = true,
|
||||
shortcuts = true,
|
||||
contacts = true,
|
||||
events = true,
|
||||
tools = true
|
||||
)
|
||||
}
|
||||
|
||||
fun SearchFilters.withOnlyCategory(
|
||||
apps: Boolean = false,
|
||||
websites: Boolean = false,
|
||||
articles: Boolean = false,
|
||||
places: Boolean = false,
|
||||
files: Boolean = false,
|
||||
shortcuts: Boolean = false,
|
||||
contacts: Boolean = false,
|
||||
events: Boolean = false,
|
||||
utilities: Boolean = false
|
||||
): SearchFilters {
|
||||
return copy(
|
||||
apps = apps,
|
||||
websites = websites,
|
||||
articles = articles,
|
||||
places = places,
|
||||
files = files,
|
||||
shortcuts = shortcuts,
|
||||
contacts = contacts,
|
||||
events = events,
|
||||
tools = utilities
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [SearchFilters] object with the [apps] property update, according to the following rules:
|
||||
* - If all categories are enabled, disable all categories except for apps.
|
||||
* - If apps is the only enabled category, enable all categories.
|
||||
* - Otherwise, toggle the apps category.
|
||||
*/
|
||||
fun SearchFilters.toggleApps(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(apps = true)
|
||||
}
|
||||
if (apps && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(apps = !apps)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleWebsites(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(websites = true)
|
||||
}
|
||||
if (websites && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(websites = !websites)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleArticles(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(articles = true)
|
||||
}
|
||||
if (articles && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(articles = !articles)
|
||||
}
|
||||
|
||||
fun SearchFilters.togglePlaces(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(places = true)
|
||||
}
|
||||
if (places && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(places = !places)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleFiles(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(files = true)
|
||||
}
|
||||
if (files && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(files = !files)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleShortcuts(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(shortcuts = true)
|
||||
}
|
||||
if (shortcuts && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(shortcuts = !shortcuts)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleContacts(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(contacts = true)
|
||||
}
|
||||
if (contacts && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(contacts = !contacts)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleEvents(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(events = true)
|
||||
}
|
||||
if (events && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(events = !events)
|
||||
}
|
||||
|
||||
fun SearchFilters.toggleTools(): SearchFilters {
|
||||
if (allCategoriesEnabled) {
|
||||
return withOnlyCategory(utilities = true)
|
||||
}
|
||||
if (tools && enabledCategories == 1) {
|
||||
return withAllCategories()
|
||||
}
|
||||
|
||||
return copy(tools = !tools)
|
||||
}
|
||||
@ -195,7 +195,7 @@ fun SearchSettingsScreen() {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_websites),
|
||||
summary = stringResource(R.string.preference_search_websites_summary),
|
||||
icon = Icons.Rounded.Language,
|
||||
icon = Icons.Rounded.Public,
|
||||
value = websites == true,
|
||||
onValueChanged = {
|
||||
viewModel.setWebsites(it)
|
||||
|
||||
@ -915,4 +915,6 @@
|
||||
<string name="clock_style_custom">Custom widget</string>
|
||||
<string name="clock_variant_standard">Standard</string>
|
||||
<string name="clock_variant_outlined">Outlined</string>
|
||||
<string name="search_filter_tools">Tools</string>
|
||||
<string name="search_filter_online">Online results</string>
|
||||
</resources>
|
||||
5
core/ktx/src/main/java/de/mm20/launcher2/ktx/Boolean.kt
Normal file
5
core/ktx/src/main/java/de/mm20/launcher2/ktx/Boolean.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package de.mm20.launcher2.ktx
|
||||
|
||||
inline fun Boolean.toInt(): Int {
|
||||
return if (this) 1 else 0
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package de.mm20.launcher2.search
|
||||
|
||||
import de.mm20.launcher2.ktx.toInt
|
||||
|
||||
data class SearchFilters(
|
||||
val allowNetwork: Boolean = false,
|
||||
val hiddenItems: Boolean = false,
|
||||
val apps: Boolean = true,
|
||||
val websites: Boolean = true,
|
||||
val articles: Boolean = true,
|
||||
val places: Boolean = true,
|
||||
val files: Boolean = true,
|
||||
val shortcuts: Boolean = true,
|
||||
val contacts: Boolean = true,
|
||||
val events: Boolean = true,
|
||||
val tools: Boolean = true,
|
||||
) {
|
||||
val allCategoriesEnabled
|
||||
get() = apps && websites && articles && places && files && shortcuts && contacts && events && tools
|
||||
|
||||
val enabledCategories: Int
|
||||
get() = apps.toInt() + websites.toInt() + articles.toInt() + places.toInt() + files.toInt() + shortcuts.toInt() + contacts.toInt() + events.toInt() + tools.toInt()
|
||||
}
|
||||
@ -23,7 +23,7 @@ import kotlinx.coroutines.supervisorScope
|
||||
interface SearchService {
|
||||
fun search(
|
||||
query: String,
|
||||
allowNetwork: Boolean = false,
|
||||
filters: SearchFilters,
|
||||
): Flow<SearchResults>
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ internal class SearchServiceImpl(
|
||||
|
||||
override fun search(
|
||||
query: String,
|
||||
allowNetwork: Boolean,
|
||||
filters: SearchFilters,
|
||||
): Flow<SearchResults> = channelFlow {
|
||||
val results = MutableStateFlow(SearchResults())
|
||||
supervisorScope {
|
||||
@ -56,8 +56,9 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.apps) {
|
||||
launch {
|
||||
appRepository.search(query, allowNetwork)
|
||||
appRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -65,8 +66,10 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.shortcuts) {
|
||||
launch {
|
||||
appShortcutRepository.search(query, allowNetwork)
|
||||
appShortcutRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -74,8 +77,10 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.contacts) {
|
||||
launch {
|
||||
contactRepository.search(query, allowNetwork)
|
||||
contactRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -83,8 +88,10 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.events) {
|
||||
launch {
|
||||
calendarRepository.search(query, allowNetwork)
|
||||
calendarRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -92,6 +99,8 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.tools) {
|
||||
launch {
|
||||
calculatorRepository.search(query).collectLatest { r ->
|
||||
results.update {
|
||||
@ -109,8 +118,10 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.websites) {
|
||||
launch {
|
||||
websiteRepository.search(query, allowNetwork)
|
||||
websiteRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -118,9 +129,11 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.articles) {
|
||||
launch {
|
||||
delay(750)
|
||||
articleRepository.search(query, allowNetwork)
|
||||
articleRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -128,8 +141,10 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.places) {
|
||||
launch {
|
||||
locationRepository.search(query, allowNetwork)
|
||||
locationRepository.search(query, filters.allowNetwork)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
results.update {
|
||||
@ -137,10 +152,12 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters.files) {
|
||||
launch {
|
||||
fileRepository.search(
|
||||
query,
|
||||
allowNetwork
|
||||
filters.allowNetwork
|
||||
)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.collectLatest { r ->
|
||||
@ -149,6 +166,7 @@ internal class SearchServiceImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
customAttributesRepository.search(query)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user