Add extended filter view

This commit is contained in:
MM20 2024-04-21 17:29:28 +02:00
parent 0ebf0f872d
commit 9b29696757
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
12 changed files with 534 additions and 257 deletions

View File

@ -250,7 +250,7 @@ fun PagerScaffold(
} else {
pagerState.animateScrollToPage(0)
}
searchVM.search("")
searchVM.reset()
}
}
@ -262,7 +262,7 @@ fun PagerScaffold(
when {
isSearchOpen -> {
viewModel.closeSearch()
searchVM.search("")
searchVM.reset()
true
}

View File

@ -252,7 +252,7 @@ fun PullDownScaffold(
animationSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
} else {
searchVM.search("")
searchVM.reset()
if (viewModel.skipNextSearchAnimation) {
pagerState.scrollToPage(0)
viewModel.skipNextSearchAnimation = false
@ -273,7 +273,7 @@ fun PullDownScaffold(
when {
isSearchOpen -> {
viewModel.closeSearch()
searchVM.search("")
searchVM.reset()
true
}

View File

@ -279,7 +279,7 @@ abstract class SharedLauncherActivity(
super.onResume()
if (System.currentTimeMillis() - pauseTime > 20000) {
viewModel.closeSearchWithoutAnimation()
searchVM.search("")
searchVM.reset()
}
}

View File

@ -1,10 +1,13 @@
package de.mm20.launcher2.ui.launcher.search
import androidx.activity.compose.BackHandler
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -28,10 +31,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.platform.LocalContext
@ -39,7 +42,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.FavoritesTagSelector
@ -51,6 +53,7 @@ import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorItem
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.favorites.SearchFavoritesVM
import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItem
@ -116,253 +119,290 @@ fun SearchColumn(
val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false)
val favoritesTagsExpanded by favoritesVM.tagsExpanded.collectAsState(false)
LazyColumn(
state = state,
modifier = modifier,
userScrollEnabled = userScrollEnabled,
contentPadding = paddingValues,
reverseLayout = reverse,
) {
if (!hideFavs && favoritesEnabled) {
GridResults(
items = favorites.toImmutableList(),
columns = columns,
key = "favorites",
reverse = reverse,
before = if (favorites.isEmpty()) {
{
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(
if (selectedTag == null) R.string.favorites_empty else R.string.favorites_empty_tag
),
icon = if (selectedTag == null) Icons.Rounded.Star else Icons.Rounded.Tag,
)
}
} else null,
after = if (pinnedTags.isEmpty() && !favoritesEditButton) {
null
} else {
{
FavoritesTagSelector(
tags = pinnedTags,
selectedTag = selectedTag,
editButton = favoritesEditButton,
reverse = reverse,
onSelectTag = { favoritesVM.selectTag(it) },
scrollState = tagsScrollState,
expanded = favoritesTagsExpanded,
onExpand = { favoritesVM.setTagsExpanded(it) }
)
}
},
highlightedItem = bestMatch as? SavableSearchable
)
}
val showFilters by viewModel.showFilters
GridResults(
items = if (separateWorkProfile) if ((showWorkProfileApps || apps.isEmpty()) && workApps.isNotEmpty()) workApps.toImmutableList() else apps.toImmutableList() else listOf(apps, workApps).flatten().sorted().toImmutableList(),
columns = columns,
reverse = reverse,
key = "apps",
before = if (separateWorkProfile && workApps.isNotEmpty() && apps.isNotEmpty()) {
{
Row(
AnimatedContent(showFilters) {
if (it) {
BackHandler {
viewModel.showFilters.value = false
}
Box(
modifier = modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = if (reverse) Alignment.BottomCenter else Alignment.TopCenter,
) {
LauncherCard(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
SearchFilters(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.padding(
top = if (reverse) 4.dp else 8.dp,
bottom = if (reverse) 8.dp else 4.dp
),
) {
FilterChip(
modifier = Modifier.padding(horizontal = 8.dp),
selected = !showWorkProfileApps,
onClick = { showWorkProfileApps = false },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Person,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
},
label = {
Text(
stringResource(R.string.apps_profile_main),
maxLines = 1,
overflow = TextOverflow.Ellipsis
.padding(12.dp),
filters = viewModel.filters.value,
onFiltersChange = {
viewModel.setFilters(it)
}
)
}
}
} else {
LazyColumn(
state = state,
modifier = modifier,
userScrollEnabled = userScrollEnabled,
contentPadding = paddingValues,
reverseLayout = reverse,
) {
if (!hideFavs && favoritesEnabled) {
GridResults(
items = favorites.toImmutableList(),
columns = columns,
key = "favorites",
reverse = reverse,
before = if (favorites.isEmpty()) {
{
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(
if (selectedTag == null) R.string.favorites_empty else R.string.favorites_empty_tag
),
icon = if (selectedTag == null) Icons.Rounded.Star else Icons.Rounded.Tag,
)
}
)
FilterChip(
selected = showWorkProfileApps,
onClick = { showWorkProfileApps = true },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Work,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
},
label = {
Text(
stringResource(R.string.apps_profile_work),
maxLines = 1,
overflow = TextOverflow.Ellipsis
} else null,
after = if (pinnedTags.isEmpty() && !favoritesEditButton) {
null
} else {
{
FavoritesTagSelector(
tags = pinnedTags,
selectedTag = selectedTag,
editButton = favoritesEditButton,
reverse = reverse,
onSelectTag = { favoritesVM.selectTag(it) },
scrollState = tagsScrollState,
expanded = favoritesTagsExpanded,
onExpand = { favoritesVM.setTagsExpanded(it) }
)
}
)
},
highlightedItem = bestMatch as? SavableSearchable
)
}
GridResults(
items = if (separateWorkProfile) if ((showWorkProfileApps || apps.isEmpty()) && workApps.isNotEmpty()) workApps.toImmutableList() else apps.toImmutableList() else listOf(
apps,
workApps
).flatten().sorted().toImmutableList(),
columns = columns,
reverse = reverse,
key = "apps",
before = if (separateWorkProfile && workApps.isNotEmpty() && apps.isNotEmpty()) {
{
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.padding(
top = if (reverse) 4.dp else 8.dp,
bottom = if (reverse) 8.dp else 4.dp
),
) {
FilterChip(
modifier = Modifier.padding(horizontal = 8.dp),
selected = !showWorkProfileApps,
onClick = { showWorkProfileApps = false },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Person,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
},
label = {
Text(
stringResource(R.string.apps_profile_main),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
)
FilterChip(
selected = showWorkProfileApps,
onClick = { showWorkProfileApps = true },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Work,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
},
label = {
Text(
stringResource(R.string.apps_profile_work),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
)
}
}
} else null,
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingShortcutsPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(
R.string.missing_permission_appshortcuts_search,
stringResource(R.string.app_name)
),
onClick = { viewModel.requestAppShortcutPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableAppShortcutSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = appShortcuts.toImmutableList(),
reverse = reverse,
key = "shortcuts",
highlightedItem = bestMatch as? SavableSearchable
)
for (conv in unitConverter) {
SingleResult {
UnitConverterItem(unitConverter = conv)
}
}
} else null,
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingShortcutsPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(
R.string.missing_permission_appshortcuts_search,
stringResource(R.string.app_name)
),
onClick = { viewModel.requestAppShortcutPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableAppShortcutSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
for (calc in calculator) {
SingleResult {
CalculatorItem(calculator = calc)
}
}
} else null,
items = appShortcuts.toImmutableList(),
reverse = reverse,
key = "shortcuts",
highlightedItem = bestMatch as? SavableSearchable
)
for (conv in unitConverter) {
SingleResult {
UnitConverterItem(unitConverter = conv)
ListResults(
before = if (missingCalendarPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_calendar_search),
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableCalendarSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = events.toImmutableList(),
reverse = reverse,
key = "events",
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingContactsPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_contact_search),
onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableContactsSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = contacts.toImmutableList(),
reverse = reverse,
key = "contacts",
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingLocationPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_location_search),
onClick = { viewModel.requestLocationPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableLocationSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = locations.toImmutableList(),
reverse = reverse,
key = "locations",
highlightedItem = bestMatch as? SavableSearchable
)
for (wiki in wikipedia) {
SingleResult(highlight = bestMatch == wiki) {
ArticleItem(article = wiki)
}
}
for (ws in website) {
SingleResult(highlight = bestMatch == ws) {
WebsiteItem(website = ws)
}
}
ListResults(
before = if (missingFilesPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_files_search),
onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableFilesSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = files.toImmutableList(),
reverse = reverse,
key = "files",
highlightedItem = bestMatch as? SavableSearchable
)
}
}
for (calc in calculator) {
SingleResult {
CalculatorItem(calculator = calc)
}
}
ListResults(
before = if (missingCalendarPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_calendar_search),
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableCalendarSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = events.toImmutableList(),
reverse = reverse,
key = "events",
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingContactsPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_contact_search),
onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableContactsSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = contacts.toImmutableList(),
reverse = reverse,
key = "contacts",
highlightedItem = bestMatch as? SavableSearchable
)
ListResults(
before = if (missingLocationPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_location_search),
onClick = { viewModel.requestLocationPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableLocationSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = locations.toImmutableList(),
reverse = reverse,
key = "locations",
highlightedItem = bestMatch as? SavableSearchable
)
for (wiki in wikipedia) {
SingleResult(highlight = bestMatch == wiki) {
ArticleItem(article = wiki)
}
}
for (ws in website) {
SingleResult(highlight = bestMatch == ws) {
WebsiteItem(website = ws)
}
}
ListResults(
before = if (missingFilesPermission && !isSearchEmpty) {
{
MissingPermissionBanner(
modifier = Modifier.padding(8.dp),
text = stringResource(R.string.missing_permission_files_search),
onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) },
secondaryAction = {
OutlinedButton(onClick = {
viewModel.disableFilesSearch()
}) {
Text(
stringResource(R.string.turn_off),
)
}
}
)
}
} else null,
items = files.toImmutableList(),
reverse = reverse,
key = "files",
highlightedItem = bestMatch as? SavableSearchable
)
}
val sheetManager = LocalBottomSheetManager.current
if (sheetManager.hiddenItemsSheetShown.value) {
HiddenItemsSheet(

View File

@ -91,6 +91,8 @@ class SearchVM : ViewModel(), KoinComponent {
val favoritesEnabled = searchUiSettings.favorites
val hideFavorites = mutableStateOf(false)
val showFilters = mutableStateOf(false)
val filters = mutableStateOf(SearchFilters())
val separateWorkProfile = searchUiSettings.separateWorkProfile
@ -125,9 +127,21 @@ class SearchVM : ViewModel(), KoinComponent {
search(searchQuery.value, forceRestart = true)
}
fun closeFilters() {
showFilters.value = false
}
fun reset() {
closeFilters()
search("")
}
private var searchJob: Job? = null
fun search(query: String, forceRestart: Boolean = false) {
if (searchQuery.value == query && !forceRestart) return
if (searchQuery.value != query) {
showFilters.value = false
}
searchQuery.value = query
isSearchEmpty.value = query.isEmpty()
hiddenResults.value = emptyList()
@ -145,7 +159,7 @@ class SearchVM : ViewModel(), KoinComponent {
searchUiSettings.resultOrder.collectLatest { resultOrder ->
searchService.search(
query,
filters = filters,
filters = if (query.isEmpty()) filters.copy(apps = true) else filters,
).collectLatest { results ->
var resultsList = withContext(Dispatchers.Default) {
listOfNotNull(

View File

@ -1,4 +1,4 @@
package de.mm20.launcher2.ui.launcher.searchbar
package de.mm20.launcher2.ui.launcher.search.filters
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
@ -91,7 +91,7 @@ fun KeyboardFilterBar(filters: SearchFilters, onFiltersChange: (SearchFilters) -
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
},
label = { Text("Apps") }
label = { Text(stringResource(R.string.search_filter_apps)) }
)
FilterChip(
modifier = Modifier.padding(end = 8.dp),

View File

@ -0,0 +1,212 @@
package de.mm20.launcher2.ui.launcher.search.filters
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.Text
import androidx.compose.runtime.Composable
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
@Composable
fun SearchFilters(
filters: SearchFilters,
onFiltersChange: (SearchFilters) -> Unit,
modifier: Modifier = Modifier
) {
val allCategoriesEnabled = filters.allCategoriesEnabled
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = 4.dp),
) {
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)) }
)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
FlowRow {
FilterChip(
modifier = Modifier.padding(end = 16.dp),
selected = filters.apps && !allCategoriesEnabled,
onClick = {
onFiltersChange(filters.toggleApps())
},
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Apps,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
},
label = { Text(stringResource(R.string.search_filter_apps)) }
)
FilterChip(
modifier = Modifier.padding(end = 16.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 = 16.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 = 16.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 = 16.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 = 16.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 = 16.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 = 16.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)) }
)
}
HorizontalDivider(modifier = Modifier.padding(vertical = 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)) }
)
}
}

View File

@ -1,4 +1,4 @@
package de.mm20.launcher2.ui.launcher.searchbar
package de.mm20.launcher2.ui.launcher.search.filters
import de.mm20.launcher2.search.SearchFilters

View File

@ -16,7 +16,6 @@ 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
@ -33,6 +32,7 @@ import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.component.SearchBar
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.filters.KeyboardFilterBar
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
@Composable
@ -54,19 +54,14 @@ fun LauncherSearchBar(
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
val sheetManager = LocalBottomSheetManager.current
val searchVM: SearchVM = viewModel()
val hiddenItemsButtonEnabled by searchVM.hiddenResultsButton.collectAsState(false)
val hiddenItems by searchVM.hiddenResults
LaunchedEffect(focused) {
if (focused) focusRequester.requestFocus()
else focusManager.clearFocus()
}
if (isSearchOpen && WindowInsets.isImeVisible) {
if (isSearchOpen && !searchVM.showFilters.value && WindowInsets.isImeVisible) {
KeyboardFilterBar(
filters = searchVM.filters.value,
onFiltersChange = {
@ -89,8 +84,11 @@ fun LauncherSearchBar(
exit = scaleOut(tween(100))
) {
FilledIconButton(
onClick = { },
colors = IconButtonDefaults.iconButtonColors()
onClick = {
searchVM.showFilters.value = !searchVM.showFilters.value
},
colors = if (searchVM.showFilters.value) IconButtonDefaults.filledTonalIconButtonColors()
else IconButtonDefaults.iconButtonColors()
) {
Box {
Icon(imageVector = Icons.Rounded.FilterAlt, contentDescription = null)
@ -98,7 +96,9 @@ fun LauncherSearchBar(
!searchVM.filters.value.allCategoriesEnabled,
enter = scaleIn(tween(100)),
exit = scaleOut(tween(100)),
modifier = Modifier.align(Alignment.BottomEnd).offset(-3.dp, -3.dp)
modifier = Modifier
.align(Alignment.BottomEnd)
.offset(-3.dp, -3.dp)
) {
Badge(
containerColor = MaterialTheme.colorScheme.tertiary,

View File

@ -1,2 +0,0 @@
package de.mm20.launcher2.ui.launcher.searchbar

View File

@ -917,4 +917,5 @@
<string name="clock_variant_outlined">Outlined</string>
<string name="search_filter_tools">Tools</string>
<string name="search_filter_online">Online results</string>
<string name="search_filter_apps">Apps</string>
</resources>

View File

@ -172,7 +172,19 @@ internal class SearchServiceImpl(
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(other = r.toImmutableList())
it.copy(other = r
.filter {
filters.apps && it is Application ||
filters.shortcuts && it is AppShortcut ||
filters.contacts && it is Contact ||
filters.events && it is CalendarEvent ||
filters.files && it is File ||
filters.websites && it is Website ||
filters.articles && it is Article ||
filters.places && it is Location
}
.toImmutableList()
)
}
}
}