diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt
index 19cef02a..3d1d5eeb 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt
@@ -59,8 +59,10 @@ fun AssistantScaffold(
val searchState = rememberLazyListState()
+ val filterBar by searchVM.filterBar.collectAsState(false)
+
val keyboardFilterBarPadding by animateDpAsState(
- if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value) 50.dp else 0.dp
+ if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value && filterBar) 50.dp else 0.dp
)
val isSearchAtStart by remember {
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 da937c03..cd04dcd6 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
@@ -129,8 +129,10 @@ fun PagerScaffold(
val pagerState = rememberPagerState { 2 }
+ val filterBar by searchVM.filterBar.collectAsState(false)
+
val keyboardFilterBarPadding by animateDpAsState(
- if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value) 50.dp else 0.dp
+ if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value && filterBar) 50.dp else 0.dp
)
val isSearchAtBottom by remember {
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 01cb6155..74cdf35f 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
@@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.launcher
+import android.util.Log
import android.view.HapticFeedbackConstants
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
@@ -114,8 +115,9 @@ fun PullDownScaffold(
val maxOffset = with(density) { 64.dp.toPx() }
val toggleSearchThreshold = with(density) { 48.dp.toPx() }
+ val filterBar by searchVM.filterBar.collectAsState(false)
val keyboardFilterBarPadding by animateDpAsState(
- if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value) 50.dp else 0.dp
+ if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value && filterBar) 50.dp else 0.dp
)
val isSearchAtTop by remember {
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 11d4cd33..d5c645cd 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
@@ -93,8 +93,9 @@ class SearchVM : ViewModel(), KoinComponent {
val showFilters = mutableStateOf(false)
- val defaultFilters = searchFilterSettings.defaultFilter.stateIn(viewModelScope, SharingStarted.Eagerly, SearchFilters())
+ private val defaultFilters = searchFilterSettings.defaultFilter.stateIn(viewModelScope, SharingStarted.Eagerly, SearchFilters())
val filters = mutableStateOf(defaultFilters.value)
+ val filterBar = searchFilterSettings.filterBar
val separateWorkProfile = searchUiSettings.separateWorkProfile
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
index 12df4394..c6b7142b 100644
--- 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
@@ -34,7 +34,8 @@ import de.mm20.launcher2.ui.icons.Wikipedia
fun SearchFilters(
filters: SearchFilters,
onFiltersChange: (SearchFilters) -> Unit,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
+ settings: Boolean = false
) {
val allCategoriesEnabled = filters.allCategoriesEnabled
Column(
@@ -60,9 +61,13 @@ fun SearchFilters(
FlowRow {
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.apps && !allCategoriesEnabled,
+ selected = filters.apps && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleApps())
+ if (settings) {
+ onFiltersChange(filters.copy(apps = !filters.apps))
+ } else {
+ onFiltersChange(filters.toggleApps())
+ }
},
leadingIcon = {
Icon(
@@ -75,9 +80,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.files && !allCategoriesEnabled,
+ selected = filters.files && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleFiles())
+ if (settings) {
+ onFiltersChange(filters.copy(files = !filters.files))
+ } else {
+ onFiltersChange(filters.toggleFiles())
+ }
},
leadingIcon = {
Icon(
@@ -90,9 +99,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.contacts && !allCategoriesEnabled,
+ selected = filters.contacts && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleContacts())
+ if (settings) {
+ onFiltersChange(filters.copy(contacts = !filters.contacts))
+ } else {
+ onFiltersChange(filters.toggleContacts())
+ }
},
leadingIcon = {
Icon(
@@ -105,9 +118,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.events && !allCategoriesEnabled,
+ selected = filters.events && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleEvents())
+ if (settings) {
+ onFiltersChange(filters.copy(events = !filters.events))
+ } else {
+ onFiltersChange(filters.toggleEvents())
+ }
},
leadingIcon = {
Icon(
@@ -120,9 +137,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.shortcuts && !allCategoriesEnabled,
+ selected = filters.shortcuts && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleShortcuts())
+ if (settings) {
+ onFiltersChange(filters.copy(shortcuts = !filters.shortcuts))
+ } else {
+ onFiltersChange(filters.toggleShortcuts())
+ }
},
leadingIcon = {
Icon(
@@ -135,9 +156,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.articles && !allCategoriesEnabled,
+ selected = filters.articles && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleArticles())
+ if (settings) {
+ onFiltersChange(filters.copy(articles = !filters.articles))
+ } else {
+ onFiltersChange(filters.toggleArticles())
+ }
},
leadingIcon = {
Icon(
@@ -150,9 +175,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.websites && !allCategoriesEnabled,
+ selected = filters.websites && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleWebsites())
+ if (settings) {
+ onFiltersChange(filters.copy(websites = !filters.websites))
+ } else {
+ onFiltersChange(filters.toggleWebsites())
+ }
},
leadingIcon = {
Icon(
@@ -165,9 +194,13 @@ fun SearchFilters(
)
FilterChip(
modifier = Modifier.padding(end = 16.dp),
- selected = filters.places && !allCategoriesEnabled,
+ selected = filters.places && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.togglePlaces())
+ if (settings) {
+ onFiltersChange(filters.copy(places = !filters.places))
+ } else {
+ onFiltersChange(filters.togglePlaces())
+ }
},
leadingIcon = {
Icon(
@@ -179,9 +212,13 @@ fun SearchFilters(
label = { Text(stringResource(R.string.preference_search_locations)) }
)
FilterChip(
- selected = filters.tools && !allCategoriesEnabled,
+ selected = filters.tools && (!allCategoriesEnabled || settings),
onClick = {
- onFiltersChange(filters.toggleTools())
+ if (settings) {
+ onFiltersChange(filters.copy(tools = !filters.tools))
+ } else {
+ onFiltersChange(filters.toggleTools())
+ }
},
leadingIcon = {
Icon(
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 841169f4..e1b661e3 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
@@ -23,6 +23,8 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
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
@@ -65,6 +67,7 @@ fun LauncherSearchBar(
else focusManager.clearFocus()
}
+ val filterBar by searchVM.filterBar.collectAsState(false)
val _value = value()
@@ -127,7 +130,7 @@ fun LauncherSearchBar(
onKeyboardActionGo = onKeyboardActionGo
)
- AnimatedVisibility (isSearchOpen && !searchVM.showFilters.value
+ AnimatedVisibility (filterBar && isSearchOpen && !searchVM.showFilters.value
// Use imeAnimationTarget instead of isImeVisible to animate the filter bar at the same time as the keyboard
&& WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0,
enter = fadeIn(),
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt
index e00c93ea..e962039a 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreen.kt
@@ -1,37 +1,58 @@
package de.mm20.launcher2.ui.settings.search
-import android.content.Context
-import android.content.pm.LauncherApps
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.*
+import androidx.compose.material.icons.rounded.AppShortcut
+import androidx.compose.material.icons.rounded.ArrowOutward
+import androidx.compose.material.icons.rounded.Calculate
+import androidx.compose.material.icons.rounded.Description
+import androidx.compose.material.icons.rounded.FilterAlt
+import androidx.compose.material.icons.rounded.Keyboard
+import androidx.compose.material.icons.rounded.Loop
+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.Sort
+import androidx.compose.material.icons.rounded.Star
+import androidx.compose.material.icons.rounded.Tag
+import androidx.compose.material.icons.rounded.Today
+import androidx.compose.material.icons.rounded.VisibilityOff
+import androidx.compose.material.icons.rounded.Warning
+import androidx.compose.material.icons.rounded.Work
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
-import de.mm20.launcher2.preferences.LegacySettings
import de.mm20.launcher2.preferences.SearchResultOrder
import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner
-import de.mm20.launcher2.ui.component.preferences.*
+import de.mm20.launcher2.ui.component.SmallMessage
+import de.mm20.launcher2.ui.component.preferences.ListPreference
+import de.mm20.launcher2.ui.component.preferences.Preference
+import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
+import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
+import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
+import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.icons.Wikipedia
+import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
import de.mm20.launcher2.ui.locals.LocalNavController
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
@Composable
fun SearchSettingsScreen() {
@@ -50,6 +71,10 @@ fun SearchSettingsScreen() {
}
}
+ var showFilterEditor by remember {
+ mutableStateOf(false)
+ }
+
PreferenceScreen(title = stringResource(R.string.preference_screen_search)) {
item {
@@ -246,6 +271,27 @@ fun SearchSettingsScreen() {
)
}
}
+ item {
+ val filterBar by viewModel.filterBar.collectAsStateWithLifecycle(null)
+ PreferenceCategory {
+ Preference(
+ title = stringResource(R.string.preference_default_filter),
+ summary = stringResource(R.string.preference_default_filter_summary),
+ icon = Icons.Rounded.FilterAlt,
+ onClick = {
+ showFilterEditor = true
+ },
+ )
+ SwitchPreference(
+ title = stringResource(R.string.preference_filter_bar),
+ summary = stringResource(R.string.preference_filter_bar_summary),
+ value = filterBar == true,
+ onValueChanged = {
+ viewModel.setFilterBar(it)
+ }
+ )
+ }
+ }
if (hasWorkProfile) {
item {
PreferenceCategory {
@@ -322,4 +368,28 @@ fun SearchSettingsScreen() {
}
}
}
+
+ if (showFilterEditor) {
+ val filters by viewModel.searchFilters.collectAsStateWithLifecycle()
+ BottomSheetDialog(onDismissRequest = { showFilterEditor = false }) {
+ Column(
+ modifier = Modifier.padding(it)
+ ) {
+ AnimatedVisibility(filters.allowNetwork) {
+ SmallMessage(
+ modifier = Modifier.padding(bottom = 16.dp),
+ icon = Icons.Rounded.Warning,
+ text = stringResource(R.string.filter_settings_network_warning)
+ )
+ }
+ SearchFilters(
+ filters = filters,
+ onFiltersChange = {
+ viewModel.setSearchFilters(it)
+ },
+ settings = true,
+ )
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt
index 7f2fcd9e..fbdd95d2 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/search/SearchSettingsScreenVM.kt
@@ -14,11 +14,13 @@ import de.mm20.launcher2.preferences.search.CalculatorSearchSettings
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
import de.mm20.launcher2.preferences.search.ContactSearchSettings
import de.mm20.launcher2.preferences.search.LocationSearchSettings
+import de.mm20.launcher2.preferences.search.SearchFilterSettings
import de.mm20.launcher2.preferences.search.ShortcutSearchSettings
import de.mm20.launcher2.preferences.search.UnitConverterSettings
import de.mm20.launcher2.preferences.search.WebsiteSearchSettings
import de.mm20.launcher2.preferences.search.WikipediaSearchSettings
import de.mm20.launcher2.preferences.ui.SearchUiSettings
+import de.mm20.launcher2.search.SearchFilters
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -34,6 +36,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
private val websiteSearchSettings: WebsiteSearchSettings by inject()
private val unitConverterSettings: UnitConverterSettings by inject()
private val calculatorSearchSettings: CalculatorSearchSettings by inject()
+ private val searchFilterSettings: SearchFilterSettings by inject()
private val permissionsManager: PermissionsManager by inject()
private val locationSearchSettings: LocationSearchSettings by inject()
@@ -160,4 +163,18 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
fun requestAppShortcutsPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
}
+
+ val filterBar = searchFilterSettings.filterBar
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+
+ fun setFilterBar(filterBar: Boolean) {
+ searchFilterSettings.setFilterBar(filterBar)
+ }
+
+ val searchFilters = searchFilterSettings.defaultFilter
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), SearchFilters())
+
+ fun setSearchFilters(searchFilters: SearchFilters) {
+ searchFilterSettings.setDefaultFilter(searchFilters)
+ }
}
\ No newline at end of file
diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml
index 16a3fadf..bbcaf834 100644
--- a/core/i18n/src/main/res/values/strings.xml
+++ b/core/i18n/src/main/res/values/strings.xml
@@ -918,4 +918,9 @@
Tools
Online results
Apps
+ Default filter
+ Customize the default filter for searches
+ Show filter bar
+ Show quick filters above the keyboard
+ The current filter enables online results by default. Your search queries might unintentionally be sent to external web services. For privacy reasons, this is not recommended.
\ No newline at end of file
diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/search/SearchFilterSettings.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/search/SearchFilterSettings.kt
index fc5fc76d..01e76180 100644
--- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/search/SearchFilterSettings.kt
+++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/search/SearchFilterSettings.kt
@@ -1,6 +1,7 @@
package de.mm20.launcher2.preferences.search
import de.mm20.launcher2.preferences.LauncherDataStore
+import de.mm20.launcher2.search.SearchFilters
import kotlinx.coroutines.flow.map
class SearchFilterSettings internal constructor(
@@ -8,4 +9,20 @@ class SearchFilterSettings internal constructor(
) {
val defaultFilter
get() = launcherDataStore.data.map { it.searchFilter }
+
+ fun setDefaultFilter(filter: SearchFilters) {
+ launcherDataStore.update {
+ it.copy(searchFilter = filter)
+ }
+ }
+
+ val filterBar
+ get() = launcherDataStore.data.map { it.searchFilterBar }
+
+ fun setFilterBar(filterBar: Boolean) {
+ launcherDataStore.update {
+ it.copy(searchFilterBar = filterBar)
+ }
+ }
+
}
\ No newline at end of file