Add preferences to hide filter bar and to configure the default filter

This commit is contained in:
MM20 2024-04-23 21:39:58 +02:00
parent 15f58352d9
commit 069b21a118
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
10 changed files with 190 additions and 34 deletions

View File

@ -59,8 +59,10 @@ fun AssistantScaffold(
val searchState = rememberLazyListState() val searchState = rememberLazyListState()
val filterBar by searchVM.filterBar.collectAsState(false)
val keyboardFilterBarPadding by animateDpAsState( 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 { val isSearchAtStart by remember {

View File

@ -129,8 +129,10 @@ fun PagerScaffold(
val pagerState = rememberPagerState { 2 } val pagerState = rememberPagerState { 2 }
val filterBar by searchVM.filterBar.collectAsState(false)
val keyboardFilterBarPadding by animateDpAsState( 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 { val isSearchAtBottom by remember {

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.launcher package de.mm20.launcher2.ui.launcher
import android.util.Log
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@ -114,8 +115,9 @@ fun PullDownScaffold(
val maxOffset = with(density) { 64.dp.toPx() } val maxOffset = with(density) { 64.dp.toPx() }
val toggleSearchThreshold = with(density) { 48.dp.toPx() } val toggleSearchThreshold = with(density) { 48.dp.toPx() }
val filterBar by searchVM.filterBar.collectAsState(false)
val keyboardFilterBarPadding by animateDpAsState( 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 { val isSearchAtTop by remember {

View File

@ -93,8 +93,9 @@ class SearchVM : ViewModel(), KoinComponent {
val showFilters = mutableStateOf(false) 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 filters = mutableStateOf(defaultFilters.value)
val filterBar = searchFilterSettings.filterBar
val separateWorkProfile = searchUiSettings.separateWorkProfile val separateWorkProfile = searchUiSettings.separateWorkProfile

View File

@ -34,7 +34,8 @@ import de.mm20.launcher2.ui.icons.Wikipedia
fun SearchFilters( fun SearchFilters(
filters: SearchFilters, filters: SearchFilters,
onFiltersChange: (SearchFilters) -> Unit, onFiltersChange: (SearchFilters) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
settings: Boolean = false
) { ) {
val allCategoriesEnabled = filters.allCategoriesEnabled val allCategoriesEnabled = filters.allCategoriesEnabled
Column( Column(
@ -60,9 +61,13 @@ fun SearchFilters(
FlowRow { FlowRow {
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.apps && !allCategoriesEnabled, selected = filters.apps && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(apps = !filters.apps))
} else {
onFiltersChange(filters.toggleApps()) onFiltersChange(filters.toggleApps())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -75,9 +80,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.files && !allCategoriesEnabled, selected = filters.files && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(files = !filters.files))
} else {
onFiltersChange(filters.toggleFiles()) onFiltersChange(filters.toggleFiles())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -90,9 +99,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.contacts && !allCategoriesEnabled, selected = filters.contacts && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(contacts = !filters.contacts))
} else {
onFiltersChange(filters.toggleContacts()) onFiltersChange(filters.toggleContacts())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -105,9 +118,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.events && !allCategoriesEnabled, selected = filters.events && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(events = !filters.events))
} else {
onFiltersChange(filters.toggleEvents()) onFiltersChange(filters.toggleEvents())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -120,9 +137,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.shortcuts && !allCategoriesEnabled, selected = filters.shortcuts && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(shortcuts = !filters.shortcuts))
} else {
onFiltersChange(filters.toggleShortcuts()) onFiltersChange(filters.toggleShortcuts())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -135,9 +156,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.articles && !allCategoriesEnabled, selected = filters.articles && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(articles = !filters.articles))
} else {
onFiltersChange(filters.toggleArticles()) onFiltersChange(filters.toggleArticles())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -150,9 +175,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.websites && !allCategoriesEnabled, selected = filters.websites && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(websites = !filters.websites))
} else {
onFiltersChange(filters.toggleWebsites()) onFiltersChange(filters.toggleWebsites())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -165,9 +194,13 @@ fun SearchFilters(
) )
FilterChip( FilterChip(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
selected = filters.places && !allCategoriesEnabled, selected = filters.places && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(places = !filters.places))
} else {
onFiltersChange(filters.togglePlaces()) onFiltersChange(filters.togglePlaces())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
@ -179,9 +212,13 @@ fun SearchFilters(
label = { Text(stringResource(R.string.preference_search_locations)) } label = { Text(stringResource(R.string.preference_search_locations)) }
) )
FilterChip( FilterChip(
selected = filters.tools && !allCategoriesEnabled, selected = filters.tools && (!allCategoriesEnabled || settings),
onClick = { onClick = {
if (settings) {
onFiltersChange(filters.copy(tools = !filters.tools))
} else {
onFiltersChange(filters.toggleTools()) onFiltersChange(filters.toggleTools())
}
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(

View File

@ -23,6 +23,8 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -65,6 +67,7 @@ fun LauncherSearchBar(
else focusManager.clearFocus() else focusManager.clearFocus()
} }
val filterBar by searchVM.filterBar.collectAsState(false)
val _value = value() val _value = value()
@ -127,7 +130,7 @@ fun LauncherSearchBar(
onKeyboardActionGo = onKeyboardActionGo 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 // Use imeAnimationTarget instead of isImeVisible to animate the filter bar at the same time as the keyboard
&& WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0, && WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0,
enter = fadeIn(), enter = fadeIn(),

View File

@ -1,37 +1,58 @@
package de.mm20.launcher2.ui.settings.search package de.mm20.launcher2.ui.settings.search
import android.content.Context
import android.content.pm.LauncherApps
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons 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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.LegacySettings
import de.mm20.launcher2.preferences.SearchResultOrder import de.mm20.launcher2.preferences.SearchResultOrder
import de.mm20.launcher2.ui.R 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.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.icons.Wikipedia
import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalNavController
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@Composable @Composable
fun SearchSettingsScreen() { fun SearchSettingsScreen() {
@ -50,6 +71,10 @@ fun SearchSettingsScreen() {
} }
} }
var showFilterEditor by remember {
mutableStateOf(false)
}
PreferenceScreen(title = stringResource(R.string.preference_screen_search)) { PreferenceScreen(title = stringResource(R.string.preference_screen_search)) {
item { 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) { if (hasWorkProfile) {
item { item {
PreferenceCategory { 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,
)
}
}
}
} }

View File

@ -14,11 +14,13 @@ import de.mm20.launcher2.preferences.search.CalculatorSearchSettings
import de.mm20.launcher2.preferences.search.CalendarSearchSettings import de.mm20.launcher2.preferences.search.CalendarSearchSettings
import de.mm20.launcher2.preferences.search.ContactSearchSettings import de.mm20.launcher2.preferences.search.ContactSearchSettings
import de.mm20.launcher2.preferences.search.LocationSearchSettings 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.ShortcutSearchSettings
import de.mm20.launcher2.preferences.search.UnitConverterSettings import de.mm20.launcher2.preferences.search.UnitConverterSettings
import de.mm20.launcher2.preferences.search.WebsiteSearchSettings import de.mm20.launcher2.preferences.search.WebsiteSearchSettings
import de.mm20.launcher2.preferences.search.WikipediaSearchSettings import de.mm20.launcher2.preferences.search.WikipediaSearchSettings
import de.mm20.launcher2.preferences.ui.SearchUiSettings import de.mm20.launcher2.preferences.ui.SearchUiSettings
import de.mm20.launcher2.search.SearchFilters
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
@ -34,6 +36,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
private val websiteSearchSettings: WebsiteSearchSettings by inject() private val websiteSearchSettings: WebsiteSearchSettings by inject()
private val unitConverterSettings: UnitConverterSettings by inject() private val unitConverterSettings: UnitConverterSettings by inject()
private val calculatorSearchSettings: CalculatorSearchSettings by inject() private val calculatorSearchSettings: CalculatorSearchSettings by inject()
private val searchFilterSettings: SearchFilterSettings by inject()
private val permissionsManager: PermissionsManager by inject() private val permissionsManager: PermissionsManager by inject()
private val locationSearchSettings: LocationSearchSettings by inject() private val locationSearchSettings: LocationSearchSettings by inject()
@ -160,4 +163,18 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
fun requestAppShortcutsPermission(activity: AppCompatActivity) { fun requestAppShortcutsPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts) 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)
}
} }

View File

@ -918,4 +918,9 @@
<string name="search_filter_tools">Tools</string> <string name="search_filter_tools">Tools</string>
<string name="search_filter_online">Online results</string> <string name="search_filter_online">Online results</string>
<string name="search_filter_apps">Apps</string> <string name="search_filter_apps">Apps</string>
<string name="preference_default_filter">Default filter</string>
<string name="preference_default_filter_summary">Customize the default filter for searches</string>
<string name="preference_filter_bar">Show filter bar</string>
<string name="preference_filter_bar_summary">Show quick filters above the keyboard</string>
<string name="filter_settings_network_warning">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.</string>
</resources> </resources>

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.preferences.search package de.mm20.launcher2.preferences.search
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SearchFilters
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
class SearchFilterSettings internal constructor( class SearchFilterSettings internal constructor(
@ -8,4 +9,20 @@ class SearchFilterSettings internal constructor(
) { ) {
val defaultFilter val defaultFilter
get() = launcherDataStore.data.map { it.searchFilter } 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)
}
}
} }