Add extended filter view
This commit is contained in:
parent
0ebf0f872d
commit
9b29696757
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -279,7 +279,7 @@ abstract class SharedLauncherActivity(
|
||||
super.onResume()
|
||||
if (System.currentTimeMillis() - pauseTime > 20000) {
|
||||
viewModel.closeSearchWithoutAnimation()
|
||||
searchVM.search("")
|
||||
searchVM.reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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),
|
||||
@ -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)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
package de.mm20.launcher2.ui.launcher.searchbar
|
||||
|
||||
@ -917,4 +917,5 @@
|
||||
<string name="clock_variant_outlined">Outlined</string>
|
||||
<string name="search_filter_tools">Tools</string>
|
||||
<string name="search_filter_online">Online results</string>
|
||||
<string name="search_filter_apps">Apps</string>
|
||||
</resources>
|
||||
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user