diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt index ea1965b8..16c23c51 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt @@ -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 } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt index 20f12f81..09814745 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt @@ -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 } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt index c6c83e3a..aafcf1f4 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt @@ -279,7 +279,7 @@ abstract class SharedLauncherActivity( super.onResume() if (System.currentTimeMillis() - pauseTime > 20000) { viewModel.closeSearchWithoutAnimation() - searchVM.search("") + searchVM.reset() } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt index aea6cb75..4f25ee4a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt @@ -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( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt index 063ba62b..e3c10721 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt @@ -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( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/KeyboardFilterBar.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/KeyboardFilterBar.kt similarity index 98% rename from app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/KeyboardFilterBar.kt rename to app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/KeyboardFilterBar.kt index 2dd86537..3180d036 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/KeyboardFilterBar.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/KeyboardFilterBar.kt @@ -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), diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFilters.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFilters.kt new file mode 100644 index 00000000..12df4394 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFilters.kt @@ -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)) } + ) + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchFilters.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFiltersExt.kt similarity index 98% rename from app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchFilters.kt rename to app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFiltersExt.kt index b0eb3c79..35e3f50e 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchFilters.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFiltersExt.kt @@ -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 diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/LauncherSearchBar.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/LauncherSearchBar.kt index a71a8473..bbec3ae8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/LauncherSearchBar.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/LauncherSearchBar.kt @@ -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, diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchBarFilters.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchBarFilters.kt deleted file mode 100644 index bd35cb31..00000000 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchBarFilters.kt +++ /dev/null @@ -1,2 +0,0 @@ -package de.mm20.launcher2.ui.launcher.searchbar - diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index c928419a..16a3fadf 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -917,4 +917,5 @@ Outlined Tools Online results + Apps \ No newline at end of file diff --git a/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt b/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt index 3e199a27..99ecb29d 100644 --- a/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt +++ b/services/search/src/main/java/de/mm20/launcher2/search/SearchService.kt @@ -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() + ) } } }