From 9b29696757c8bd849f4b44dca8ada05031e14811 Mon Sep 17 00:00:00 2001
From: MM20 <15646950+MM2-0@users.noreply.github.com>
Date: Sun, 21 Apr 2024 17:29:28 +0200
Subject: [PATCH] Add extended filter view
---
.../launcher2/ui/launcher/PagerScaffold.kt | 4 +-
.../launcher2/ui/launcher/PullDownScaffold.kt | 4 +-
.../ui/launcher/SharedLauncherActivity.kt | 2 +-
.../ui/launcher/search/SearchColumn.kt | 510 ++++++++++--------
.../launcher2/ui/launcher/search/SearchVM.kt | 16 +-
.../filters}/KeyboardFilterBar.kt | 4 +-
.../launcher/search/filters/SearchFilters.kt | 212 ++++++++
.../filters/SearchFiltersExt.kt} | 2 +-
.../launcher/searchbar/LauncherSearchBar.kt | 20 +-
.../ui/launcher/searchbar/SearchBarFilters.kt | 2 -
core/i18n/src/main/res/values/strings.xml | 1 +
.../de/mm20/launcher2/search/SearchService.kt | 14 +-
12 files changed, 534 insertions(+), 257 deletions(-)
rename app/ui/src/main/java/de/mm20/launcher2/ui/launcher/{searchbar => search/filters}/KeyboardFilterBar.kt (98%)
create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/filters/SearchFilters.kt
rename app/ui/src/main/java/de/mm20/launcher2/ui/launcher/{searchbar/SearchFilters.kt => search/filters/SearchFiltersExt.kt} (98%)
delete mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/launcher/searchbar/SearchBarFilters.kt
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()
+ )
}
}
}