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 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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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 = {
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 = {
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 = {
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 = {
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 = {
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 = {
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 = {
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 = {
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 = {
if (settings) {
onFiltersChange(filters.copy(tools = !filters.tools))
} else {
onFiltersChange(filters.toggleTools())
}
},
leadingIcon = {
Icon(

View File

@ -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(),

View File

@ -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,
)
}
}
}
}

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.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)
}
}

View File

@ -918,4 +918,9 @@
<string name="search_filter_tools">Tools</string>
<string name="search_filter_online">Online results</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>

View File

@ -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)
}
}
}