From c7ea840fc5f10aad4723b65078f96f17c9a30eb6 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:59:12 +0200 Subject: [PATCH] Add search-only visibility level to items --- .../mm20/launcher2/ui/common/FavoritesVM.kt | 10 +- .../ui/component/OutlinedTagsInputField.kt | 27 +- .../launcher2/ui/launcher/search/SearchVM.kt | 12 +- .../ui/launcher/search/apps/AppItem.kt | 32 --- .../launcher/search/calendar/CalendarItem.kt | 35 --- .../search/common/SearchableItemVM.kt | 12 - .../launcher/search/contacts/ContactItem.kt | 34 --- .../ui/launcher/search/files/FileItem.kt | 35 --- .../launcher/search/location/LocationItem.kt | 35 --- .../launcher/search/shortcut/ShortcutItem.kt | 35 --- .../sheets/CustomizeSearchableSheet.kt | 172 +++++++++++- .../sheets/CustomizeSearchableSheetVM.kt | 11 + .../launcher/sheets/EditFavoritesSheetVM.kt | 15 +- .../widgets/calendar/CalendarWidgetVM.kt | 7 +- .../clock/parts/FavoritesPartProvider.kt | 5 +- .../hiddenitems/HiddenItemsSettingsScreen.kt | 260 +++++++++++------- .../HiddenItemsSettingsScreenVM.kt | 18 +- .../settings/icons/IconsSettingsScreenVM.kt | 3 - core/i18n/src/main/res/values/strings.xml | 9 +- .../mm20/launcher2/database/SearchableDao.kt | 72 +++-- .../entities/SavedSearchableEntity.kt | 2 +- .../mm20/launcher2/searchable/PinnedLevel.kt | 8 + .../searchable/SavableSearchableRepository.kt | 132 +++++---- .../launcher2/searchable/SavedSearchable.kt | 4 +- .../launcher2/searchable/VisibilityLevel.kt | 32 +++ .../providers/HiddenItemBadgeProvider.kt | 5 +- .../services/favorites/FavoritesService.kt | 30 +- .../services/tags/impl/TagsServiceImpl.kt | 4 +- 28 files changed, 571 insertions(+), 485 deletions(-) create mode 100644 data/searchable/src/main/java/de/mm20/launcher2/searchable/PinnedLevel.kt create mode 100644 data/searchable/src/main/java/de/mm20/launcher2/searchable/VisibilityLevel.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt index c575d799..6dc46057 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt @@ -8,6 +8,7 @@ import de.mm20.launcher2.preferences.search.FavoritesSettings import de.mm20.launcher2.preferences.search.FavoritesSettingsData import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.data.Tag +import de.mm20.launcher2.searchable.PinnedLevel import de.mm20.launcher2.services.favorites.FavoritesService import de.mm20.launcher2.widgets.CalendarWidget import de.mm20.launcher2.widgets.WidgetRepository @@ -29,8 +30,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent { val pinnedTags = favoritesService.getFavorites( includeTypes = listOf("tag"), - manuallySorted = true, - automaticallySorted = true, + minPinnedLevel = PinnedLevel.AutomaticallySorted, ).map { it.filterIsInstance() } @@ -52,15 +52,15 @@ abstract class FavoritesVM : ViewModel(), KoinComponent { val pinned = favoritesService.getFavorites( excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"), - manuallySorted = true, - automaticallySorted = true, + minPinnedLevel = PinnedLevel.AutomaticallySorted, limit = 10 * columns, ) if (includeFrequentlyUsed) { emitAll(pinned.flatMapLatest { pinned -> favoritesService.getFavorites( excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"), - frequentlyUsed = true, + maxPinnedLevel = PinnedLevel.FrequentlyUsed, + minPinnedLevel = PinnedLevel.FrequentlyUsed, limit = frequentlyUsedRows * columns - pinned.size % columns, ).map { pinned + it diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt index 5d8224a3..ed794bd7 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/OutlinedTagsInputField.kt @@ -4,11 +4,14 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState @@ -28,7 +31,6 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -58,9 +60,11 @@ fun OutlinedTagsInputField( modifier: Modifier = Modifier, tags: List, onTagsChange: (tags: List) -> Unit, + label: @Composable (() -> Unit)? = null, placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - textStyle: TextStyle = MaterialTheme.typography.bodyLarge, + textStyle: TextStyle = LocalTextStyle.current, textColor: Color = LocalContentColor.current, onAutocomplete: (suspend (query: String) -> List)? = null ) { @@ -127,14 +131,23 @@ fun OutlinedTagsInputField( }), decorationBox = { innerTextField -> OutlinedTextFieldDefaults.DecorationBox( - contentPadding = PaddingValues(0.dp), - value = value, + contentPadding = PaddingValues( + start = 16.dp, + end = 0.dp, + top = 12.dp, + bottom = 12.dp + ), + value = tags.joinToString() + value, + label = label, + leadingIcon = leadingIcon, innerTextField = { - Box { + Box( + contentAlignment = Alignment.CenterStart, + ) { Row( modifier = Modifier - .horizontalScroll(rememberScrollState()) - .padding(horizontal = 16.dp), + .requiredHeight(32.dp) + .horizontalScroll(rememberScrollState()), verticalAlignment = Alignment.CenterVertically ) { for ((i, tag) in tags.withIndex()) { 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 963fdfa5..e918077f 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 @@ -32,6 +32,7 @@ import de.mm20.launcher2.search.Website import de.mm20.launcher2.search.data.Calculator import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.searchable.SavableSearchableRepository +import de.mm20.launcher2.searchable.VisibilityLevel import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.services.favorites.FavoritesService import kotlinx.coroutines.CancellationException @@ -102,13 +103,6 @@ class SearchVM : ViewModel(), KoinComponent { val separateWorkProfile = searchUiSettings.separateWorkProfile - private val hiddenItemKeys = searchableRepository - .getKeys( - hidden = true, - limit = 9999, - ) - .shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1) - val bestMatch = mutableStateOf(null) init { @@ -251,6 +245,10 @@ class SearchVM : ViewModel(), KoinComponent { } } + val hiddenItemKeys = searchableRepository.getKeys( + maxVisibility = if (query.isEmpty()) VisibilityLevel.SearchOnly else VisibilityLevel.Hidden, + ) + hiddenItemKeys.collectLatest { hiddenKeys -> val hidden = mutableListOf() val apps = mutableListOf() diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt index 27dde7f0..85a5e9d4 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt @@ -379,38 +379,6 @@ fun AppItem( ) } - val isHidden by viewModel.isHidden.collectAsState(false) - val hideAction = if (isHidden) { - DefaultToolbarAction( - label = stringResource(R.string.menu_unhide), - icon = Icons.Rounded.Visibility, - action = { - viewModel.unhide() - onBack() - } - ) - } else { - DefaultToolbarAction( - label = stringResource(R.string.menu_hide), - icon = Icons.Rounded.VisibilityOff, - action = { - viewModel.hide() - onBack() - lifecycleOwner.lifecycleScope.launch { - val result = snackbarHostState.showSnackbar( - message = context.getString(R.string.msg_item_hidden, app.label), - actionLabel = context.getString(R.string.action_undo), - duration = SnackbarDuration.Short, - ) - if (result == SnackbarResult.ActionPerformed) { - viewModel.unhide() - } - } - }) - } - - toolbarActions.add(hideAction) - Toolbar( leftActions = listOf( DefaultToolbarAction( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt index 2d36c2a6..a15f0f31 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt @@ -221,39 +221,6 @@ fun CalendarItem( toolbarActions.add(favAction) } - val isHidden by viewModel.isHidden.collectAsState(false) - val hideAction = if (isHidden) { - DefaultToolbarAction( - label = stringResource(R.string.menu_unhide), - icon = Icons.Rounded.Visibility, - action = { - viewModel.unhide() - onBack() - } - ) - } else { - DefaultToolbarAction( - label = stringResource(R.string.menu_hide), - icon = Icons.Rounded.VisibilityOff, - action = { - viewModel.hide() - onBack() - lifecycleOwner.lifecycleScope.launch { - val result = snackbarHostState.showSnackbar( - message = context.getString( - R.string.msg_item_hidden, - calendar.label - ), - actionLabel = context.getString(R.string.action_undo), - duration = SnackbarDuration.Short, - ) - if (result == SnackbarResult.ActionPerformed) { - viewModel.unhide() - } - } - }) - } - toolbarActions.add( DefaultToolbarAction( label = stringResource(R.string.menu_calendar_open_externally), @@ -272,8 +239,6 @@ fun CalendarItem( action = { sheetManager.showCustomizeSearchableModal(calendar) } )) - toolbarActions.add(hideAction) - Toolbar( leftActions = listOf( DefaultToolbarAction( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt index 52c9ab39..09b7e64a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt @@ -78,18 +78,6 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent { searchable.value?.let { favoritesService.unpinItem(it) } } - val isHidden = searchable.flatMapLatest { - if (it == null) emptyFlow() else favoritesService.isHidden(it) - } - - fun hide() { - searchable.value?.let { favoritesService.hideItem(it) } - } - - fun unhide() { - searchable.value?.let { favoritesService.unhideItem(it) } - } - val badge = searchable.flatMapLatest { if (it == null) emptyFlow() else badgeService.getBadge(it) }.stateIn(viewModelScope, SharingStarted.Lazily, null) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt index 8d5aa867..eb4755b0 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt @@ -205,39 +205,6 @@ fun ContactItem( toolbarActions.add(favAction) } - val isHidden by viewModel.isHidden.collectAsState(false) - val hideAction = if (isHidden) { - DefaultToolbarAction( - label = stringResource(R.string.menu_unhide), - icon = Icons.Rounded.Visibility, - action = { - viewModel.unhide() - onBack() - } - ) - } else { - DefaultToolbarAction( - label = stringResource(R.string.menu_hide), - icon = Icons.Rounded.VisibilityOff, - action = { - viewModel.hide() - onBack() - lifecycleOwner.lifecycleScope.launch { - val result = snackbarHostState.showSnackbar( - message = context.getString( - R.string.msg_item_hidden, - contact.label - ), - actionLabel = context.getString(R.string.action_undo), - duration = SnackbarDuration.Short, - ) - if (result == SnackbarResult.ActionPerformed) { - viewModel.unhide() - } - } - }) - } - toolbarActions.add( DefaultToolbarAction( label = stringResource(R.string.menu_contacts_open_externally), @@ -255,7 +222,6 @@ fun ContactItem( action = { sheetManager.showCustomizeSearchableModal(contact) } )) - toolbarActions.add(hideAction) Toolbar( leftActions = listOf( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt index 36ec0dbd..677aa20c 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt @@ -257,41 +257,6 @@ fun FileItem( action = { sheetManager.showCustomizeSearchableModal(file) } )) - val isHidden by viewModel.isHidden.collectAsState(false) - val hideAction = if (isHidden) { - DefaultToolbarAction( - label = stringResource(R.string.menu_unhide), - icon = Icons.Rounded.Visibility, - action = { - viewModel.unhide() - onBack() - } - ) - } else { - DefaultToolbarAction( - label = stringResource(R.string.menu_hide), - icon = Icons.Rounded.VisibilityOff, - action = { - viewModel.hide() - onBack() - lifecycleOwner.lifecycleScope.launch { - val result = snackbarHostState.showSnackbar( - message = context.getString( - R.string.msg_item_hidden, - file.label - ), - actionLabel = context.getString(R.string.action_undo), - duration = SnackbarDuration.Short, - ) - if (result == SnackbarResult.ActionPerformed) { - viewModel.unhide() - } - } - - }) - } - toolbarActions.add(hideAction) - Toolbar( leftActions = listOf( DefaultToolbarAction( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/location/LocationItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/location/LocationItem.kt index b3eb7276..c2bc06ea 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/location/LocationItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/location/LocationItem.kt @@ -775,41 +775,6 @@ fun LocationItem( } } - val isHidden by viewModel.isHidden.collectAsState(false) - val hideAction = if (isHidden) { - DefaultToolbarAction( - label = stringResource(R.string.menu_unhide), - icon = Icons.Rounded.Visibility, - action = { - viewModel.unhide() - onBack() - } - ) - } else { - DefaultToolbarAction( - label = stringResource(R.string.menu_hide), - icon = Icons.Rounded.VisibilityOff, - action = { - viewModel.hide() - onBack() - lifecycleOwner.lifecycleScope.launch { - val result = snackbarHostState.showSnackbar( - message = context.getString( - R.string.msg_item_hidden, - location.labelOverride ?: location.label - ), - actionLabel = context.getString(R.string.action_undo), - duration = SnackbarDuration.Short, - ) - if (result == SnackbarResult.ActionPerformed) { - viewModel.unhide() - } - } - - }) - } - toolbarActions.add(hideAction) - Toolbar( modifier = Modifier.fillMaxWidth(), leftActions = listOf(DefaultToolbarAction( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt index 372049a3..8a498b65 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt @@ -218,41 +218,6 @@ fun AppShortcutItem( )) } - val isHidden by viewModel.isHidden.collectAsState(false) - val hideAction = if (isHidden) { - DefaultToolbarAction( - label = stringResource(R.string.menu_unhide), - icon = Icons.Rounded.Visibility, - action = { - viewModel.unhide() - onBack() - } - ) - } else { - DefaultToolbarAction( - label = stringResource(R.string.menu_hide), - icon = Icons.Rounded.VisibilityOff, - action = { - viewModel.hide() - onBack() - - lifecycleOwner.lifecycleScope.launch { - val result = snackbarHostState.showSnackbar( - message = context.getString( - R.string.msg_item_hidden, - shortcut.label - ), - actionLabel = context.getString(R.string.action_undo), - duration = SnackbarDuration.Short, - ) - if (result == SnackbarResult.ActionPerformed) { - viewModel.unhide() - } - } - }) - } - toolbarActions.add(hideAction) - Toolbar( leftActions = listOf( DefaultToolbarAction( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt index af94dc05..23730ccd 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt @@ -1,9 +1,7 @@ package de.mm20.launcher2.ui.launcher.sheets import android.content.pm.PackageManager -import android.graphics.drawable.InsetDrawable import androidx.activity.compose.BackHandler -import androidx.appcompat.content.res.AppCompatResources import androidx.compose.animation.animateContentSize import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -19,17 +17,25 @@ import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.Label +import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.rounded.ArrowDropDown import androidx.compose.material.icons.rounded.Edit import androidx.compose.material.icons.rounded.FilterAlt import androidx.compose.material.icons.rounded.Search +import androidx.compose.material.icons.rounded.Tag +import androidx.compose.material.icons.rounded.Visibility +import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -44,7 +50,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -55,7 +60,10 @@ import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.BadgeIcon import de.mm20.launcher2.icons.CustomIconWithPreview import de.mm20.launcher2.icons.IconPack +import de.mm20.launcher2.search.Application +import de.mm20.launcher2.search.CalendarEvent import de.mm20.launcher2.search.SavableSearchable +import de.mm20.launcher2.searchable.VisibilityLevel import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.OutlinedTagsInputField @@ -84,9 +92,11 @@ fun CustomizeSearchableSheet( BottomSheetDialog( onDismissRequest = onDismiss, - title = { - Text(stringResource(if (pickIcon) R.string.icon_picker_title else R.string.menu_customize)) - }, + title = if (pickIcon) { + { + Text(stringResource(R.string.icon_picker_title)) + } + } else null, dismissible = { !pickIcon }, confirmButton = if (pickIcon) { { @@ -126,44 +136,178 @@ fun CustomizeSearchableSheet( mutableStateOf(searchable.labelOverride ?: "") } OutlinedTextField( - modifier = - Modifier + modifier = Modifier .fillMaxWidth() - .padding(top = 24.dp), + .padding(top = 24.dp, bottom = 16.dp), value = customLabelValue, onValueChange = { customLabelValue = it }, singleLine = true, + label = { + Text(stringResource(R.string.customize_item_label)) + }, placeholder = { Text(searchable.label) }, + leadingIcon = { + Icon(Icons.AutoMirrored.Rounded.Label, null) + } ) var tags by remember { mutableStateOf(emptyList()) } + var visibility by remember { mutableStateOf(VisibilityLevel.Default) } LaunchedEffect(searchable.key) { + visibility = viewModel.getVisibility().first() tags = viewModel.getTags().first() } OutlinedTagsInputField( modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), + .padding(top = 8.dp) + .fillMaxWidth(), tags = tags, onTagsChange = { tags = it.distinct() }, - placeholder = { - Text(stringResource(R.string.customize_tags_placeholder)) + label = { + Text(stringResource(R.string.customize_item_tags)) }, - textStyle = MaterialTheme.typography.bodyMedium, onAutocomplete = { viewModel.autocompleteTags(it).minus(tags.toSet()) + }, + leadingIcon = { + Icon(Icons.Rounded.Tag, null) } ) + var showDropdown by remember { + mutableStateOf(false) + } + + ExposedDropdownMenuBox( + expanded = showDropdown, + onExpandedChange = { showDropdown = it }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .menuAnchor(MenuAnchorType.PrimaryNotEditable), + value = when (visibility) { + VisibilityLevel.Default -> { + when (searchable) { + is Application -> stringResource(R.string.item_visibility_app_default) + is CalendarEvent -> stringResource(R.string.item_visibility_calendar_default) + else -> stringResource(R.string.item_visibility_search_only) + } + } + + VisibilityLevel.SearchOnly -> stringResource(R.string.item_visibility_search_only) + VisibilityLevel.Hidden -> stringResource(R.string.item_visibility_hidden) + }, + label = { + Text(stringResource(R.string.customize_item_visibility)) + }, + onValueChange = {}, + readOnly = true, + singleLine = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showDropdown) }, + leadingIcon = { + Icon( + when (visibility) { + VisibilityLevel.Default -> Icons.Rounded.Visibility + VisibilityLevel.SearchOnly -> Icons.Outlined.Visibility + VisibilityLevel.Hidden -> Icons.Rounded.VisibilityOff + }, + null + ) + }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + ) + ExposedDropdownMenu( + expanded = showDropdown, + onDismissRequest = { + showDropdown = false + } + ) { + if (searchable is Application) { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Default + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_app_default)) + }, + leadingIcon = { + Icon(Icons.Rounded.Visibility, null) + } + ) + } else if (searchable is CalendarEvent) { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Default + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_app_default)) + }, + leadingIcon = { + Icon(Icons.Rounded.Visibility, null) + } + ) + } else { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Default + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_search_only)) + }, + leadingIcon = { + Icon(Icons.Rounded.Visibility, null) + } + ) + } + if (searchable is Application || searchable is CalendarEvent) { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.SearchOnly + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_search_only)) + }, + leadingIcon = { + Icon( + Icons.Outlined.Visibility, + null + ) + } + ) + } + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Hidden + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_hidden)) + }, + leadingIcon = { + Icon(Icons.Rounded.VisibilityOff, null) + } + ) + } + } + DisposableEffect(searchable.key) { onDispose { viewModel.setCustomLabel(customLabelValue) viewModel.setTags(tags) + viewModel.setVisibility(visibility) } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt index 5925ead1..2c767b5f 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt @@ -8,6 +8,8 @@ import de.mm20.launcher2.icons.IconPack import de.mm20.launcher2.icons.IconService import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.search.SavableSearchable +import de.mm20.launcher2.searchable.VisibilityLevel +import de.mm20.launcher2.services.favorites.FavoritesService import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay @@ -25,6 +27,7 @@ class CustomizeSearchableSheetVM( ) : KoinComponent { private val iconService: IconService by inject() private val customAttributesRepository: CustomAttributesRepository by inject() + private val favoritesService: FavoritesService by inject() val isIconPickerOpen = mutableStateOf(false) @@ -89,10 +92,18 @@ class CustomizeSearchableSheetVM( customAttributesRepository.setTags(searchable, tags) } + fun setVisibility(visibility: VisibilityLevel) { + favoritesService.setVisibility(searchable, visibility) + } + fun getTags(): Flow> { return customAttributesRepository.getTags(searchable) } + fun getVisibility(): Flow { + return favoritesService.getVisibility(searchable) + } + suspend fun autocompleteTags(query: String): List { return customAttributesRepository.getAllTags(startsWith = query).first() } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt index ea907ae2..2b69100a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt @@ -22,6 +22,7 @@ import de.mm20.launcher2.appshortcuts.AppShortcut import de.mm20.launcher2.preferences.search.FavoritesSettings import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.Tag +import de.mm20.launcher2.searchable.PinnedLevel import de.mm20.launcher2.services.favorites.FavoritesService import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -60,21 +61,22 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent { loading.value = showLoadingIndicator manuallySorted = mutableListOf() manuallySorted = favoritesService.getFavorites( - manuallySorted = true, + minPinnedLevel = PinnedLevel.AutomaticallySorted, excludeTypes = listOf("tag"), ).first().toMutableList() automaticallySorted = favoritesService.getFavorites( - automaticallySorted = true, + minPinnedLevel = PinnedLevel.AutomaticallySorted, + maxPinnedLevel = PinnedLevel.AutomaticallySorted, excludeTypes = listOf("tag"), ).first().toMutableList() frequentlyUsed = favoritesService.getFavorites( - frequentlyUsed = true, + minPinnedLevel = PinnedLevel.FrequentlyUsed, + maxPinnedLevel = PinnedLevel.FrequentlyUsed, excludeTypes = listOf("tag"), ).first().toMutableList() val pinnedTags = favoritesService.getFavorites( includeTypes = listOf("tag"), - manuallySorted = true, - automaticallySorted = true, + minPinnedLevel = PinnedLevel.AutomaticallySorted, ).first().filterIsInstance().toMutableList() availableTags.value = customAttributesRepository @@ -296,7 +298,8 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent { favoritesService.unpinItem(item.item) viewModelScope.launch { frequentlyUsed = favoritesService.getFavorites( - frequentlyUsed = true, + minPinnedLevel = PinnedLevel.FrequentlyUsed, + maxPinnedLevel = PinnedLevel.FrequentlyUsed, excludeTypes = listOf("tag"), ).first().toMutableList() buildItemList() diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt index f007a071..08e42b59 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt @@ -14,6 +14,8 @@ import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.search.CalendarEvent +import de.mm20.launcher2.searchable.PinnedLevel +import de.mm20.launcher2.searchable.VisibilityLevel import de.mm20.launcher2.services.favorites.FavoritesService import de.mm20.launcher2.widgets.CalendarWidget import de.mm20.launcher2.widgets.CalendarWidgetConfig @@ -42,8 +44,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { val pinnedCalendarEvents = favoritesService.getFavorites( includeTypes = listOf("calendar"), - automaticallySorted = true, - manuallySorted = true, + minPinnedLevel = PinnedLevel.AutomaticallySorted, ).stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) val nextEvents = mutableStateOf>(emptyList()) var availableDates = listOf(LocalDate.now()) @@ -172,7 +173,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { ).collectLatest { events -> searchableRepository.getKeys( includeTypes = listOf("calendar"), - hidden = true, + maxVisibility = VisibilityLevel.SearchOnly, limit = 9999, ).collectLatest { hidden -> upcomingEvents = events.filter { !hidden.contains(it.key) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/FavoritesPartProvider.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/FavoritesPartProvider.kt index ba6b6210..98c7a805 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/FavoritesPartProvider.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/FavoritesPartProvider.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import de.mm20.launcher2.preferences.ui.UiSettings +import de.mm20.launcher2.searchable.PinnedLevel import de.mm20.launcher2.services.favorites.FavoritesService import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid import de.mm20.launcher2.widgets.CalendarWidget @@ -44,9 +45,7 @@ class FavoritesPartProvider : PartProvider, KoinComponent { val favorites by remember(columns, excludeCalendar) { favoritesService.getFavorites( excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"), - manuallySorted = true, - automaticallySorted = true, - frequentlyUsed = true, + minPinnedLevel = PinnedLevel.FrequentlyUsed, limit = columns ) }.collectAsState(emptyList()) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt index 886c152a..4b2bde8f 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt @@ -1,7 +1,7 @@ package de.mm20.launcher2.ui.settings.hiddenitems import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material3.DropdownMenu @@ -25,13 +27,15 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.search.Application +import de.mm20.launcher2.search.CalendarEvent +import de.mm20.launcher2.search.SavableSearchable +import de.mm20.launcher2.searchable.VisibilityLevel import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.ShapedLauncherIcon import de.mm20.launcher2.ui.component.preferences.PreferenceCategory @@ -42,7 +46,6 @@ import de.mm20.launcher2.ui.component.preferences.SwitchPreference fun HiddenItemsSettingsScreen() { val viewModel: HiddenItemsSettingsScreenVM = viewModel() - val context = LocalContext.current val density = LocalDensity.current val apps by viewModel.allApps.collectAsState() @@ -66,63 +69,41 @@ fun HiddenItemsSettingsScreen() { viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() }) }.collectAsState(null) - val isHidden by remember(searchable.key) { - viewModel.isHidden(searchable) - }.collectAsState(false) + val visibility by remember(searchable.key) { + viewModel.getVisibility(searchable) + }.collectAsState(null) - var showPopup by remember(searchable.key) { - mutableStateOf(false) - } + var showDropdown by remember { mutableStateOf(false) } Box { HiddenItem( modifier = Modifier .fillMaxWidth() - .combinedClickable( - onClick = { - viewModel.setHidden(searchable, !isHidden) - }, - onLongClick = { - showPopup = true + .clickable { + if (searchable is Application || searchable is CalendarEvent) { + showDropdown = true + } else { + if (visibility == null) return@clickable + viewModel.setVisibility( + searchable, + if (visibility == VisibilityLevel.Default) VisibilityLevel.Hidden else VisibilityLevel.Default + ) } - ), + }, icon = icon, label = searchable.label, - isHidden = isHidden, + visibility = visibility, + ) + VisibilityDropdown( + expanded = showDropdown, + onDismissRequest = { showDropdown = false }, + item = searchable, + value = visibility, + onValueChanged = { + viewModel.setVisibility(searchable, it) + showDropdown = false + } ) - - DropdownMenu( - expanded = showPopup, - onDismissRequest = { showPopup = false }, - offset = DpOffset(16.dp, 0.dp) - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.menu_launch)) }, - onClick = { - viewModel.launch(context, searchable) - showPopup = false - }) - - DropdownMenuItem( - text = { Text(stringResource(R.string.menu_app_info)) }, - onClick = { - viewModel.openAppInfo(context, searchable) - showPopup = false - }) - - DropdownMenuItem( - text = { - Text( - stringResource( - if (isHidden) R.string.menu_unhide else R.string.menu_hide - ) - ) - }, - onClick = { - viewModel.setHidden(searchable, !isHidden) - showPopup = false - }) - } } } @@ -142,67 +123,141 @@ fun HiddenItemsSettingsScreen() { viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() }) }.collectAsState(null) - val isHidden by remember(searchable.key) { - viewModel.isHidden(searchable) - }.collectAsState(false) + val visibility by remember(searchable.key) { + viewModel.getVisibility(searchable) + }.collectAsState(null) - var showPopup by remember(searchable.key) { - mutableStateOf(false) - } + var showDropdown by remember { mutableStateOf(false) } Box { HiddenItem( modifier = Modifier .fillMaxWidth() - .combinedClickable( - onClick = { - viewModel.setHidden(searchable, !isHidden) - }, - onLongClick = { - showPopup = true + .clickable { + if (searchable is Application || searchable is CalendarEvent) { + showDropdown = true + } else { + if (visibility == null) return@clickable + viewModel.setVisibility( + searchable, + if (visibility == VisibilityLevel.Default) VisibilityLevel.Hidden else VisibilityLevel.Default + ) } - ), + }, icon = icon, label = searchable.label, - isHidden = isHidden, + visibility = visibility, + ) + VisibilityDropdown( + expanded = showDropdown, + onDismissRequest = { showDropdown = false }, + item = searchable, + value = visibility, + onValueChanged = { + viewModel.setVisibility(searchable, it) + showDropdown = false + } ) - - DropdownMenu( - expanded = showPopup, - onDismissRequest = { showPopup = false }, - offset = DpOffset(16.dp, 0.dp) - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.menu_open_file)) }, - onClick = { - viewModel.launch(context, searchable) - showPopup = false - }) - - DropdownMenuItem( - text = { - Text( - stringResource( - if (isHidden) R.string.menu_unhide else R.string.menu_hide - ) - ) - }, - onClick = { - viewModel.setHidden(searchable, !isHidden) - showPopup = false - }) - } } } } } @Composable -fun HiddenItem( +private fun VisibilityDropdown( + expanded: Boolean, + onDismissRequest: () -> Unit, + item: SavableSearchable, + value: VisibilityLevel?, + onValueChanged: (VisibilityLevel) -> Unit, +) { + DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) { + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Visibility, + contentDescription = null + ) + }, + text = { + Text( + when (item) { + is Application -> stringResource(R.string.item_visibility_app_default) + is CalendarEvent -> stringResource(R.string.item_visibility_calendar_default) + else -> stringResource(R.string.item_visibility_search_only) + } + ) + }, + onClick = { + onValueChanged(VisibilityLevel.Default) + }, + trailingIcon = { + if (value == VisibilityLevel.Default) { + Icon( + imageVector = Icons.Rounded.Check, + tint = MaterialTheme.colorScheme.primary, + contentDescription = null + ) + } + } + ) + if (item is Application || item is CalendarEvent) { + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Visibility, + contentDescription = null + ) + }, + text = { + Text(stringResource(R.string.item_visibility_search_only)) + }, + onClick = { + onValueChanged(VisibilityLevel.SearchOnly) + }, + trailingIcon = { + if (value == VisibilityLevel.SearchOnly) { + Icon( + imageVector = Icons.Rounded.Check, + tint = MaterialTheme.colorScheme.primary, + contentDescription = null + ) + } + } + ) + } + DropdownMenuItem( + leadingIcon = { + Icon( + imageVector = Icons.Rounded.VisibilityOff, + contentDescription = null + ) + }, + text = { + Text(stringResource(R.string.item_visibility_hidden)) + }, + onClick = { + onValueChanged(VisibilityLevel.Hidden) + }, + trailingIcon = { + if (value == VisibilityLevel.Hidden) { + Icon( + imageVector = Icons.Rounded.Check, + tint = MaterialTheme.colorScheme.primary, + contentDescription = null + ) + } + } + ) + } +} + +@Composable +private fun HiddenItem( modifier: Modifier, icon: LauncherIcon?, label: String, - isHidden: Boolean, + visibility: VisibilityLevel?, ) { Row( modifier = modifier @@ -219,11 +274,18 @@ fun HiddenItem( modifier = Modifier.weight(1f, fill = true), style = MaterialTheme.typography.titleMedium ) - Icon( - modifier = Modifier.alpha(if (isHidden) 0.3f else 1f), - imageVector = if (isHidden) Icons.Rounded.VisibilityOff else Icons.Rounded.Visibility, - tint = if (isHidden) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.primary, - contentDescription = null - ) + if (visibility != null) { + Icon( + modifier = Modifier.alpha(if (visibility == VisibilityLevel.Hidden) 0.3f else 1f), + imageVector = when (visibility) { + VisibilityLevel.Hidden -> Icons.Rounded.VisibilityOff + VisibilityLevel.Default -> Icons.Rounded.Visibility + VisibilityLevel.SearchOnly -> Icons.Outlined.Visibility + }, + tint = if (visibility == VisibilityLevel.Default) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurfaceVariant, + contentDescription = null + ) + } } } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt index 1642ab42..f5a0a29f 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt @@ -12,6 +12,7 @@ import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.preferences.ui.SearchUiSettings import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.Application +import de.mm20.launcher2.searchable.VisibilityLevel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -20,7 +21,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -36,20 +36,18 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent { }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) val hiddenItems: StateFlow> = flow { val hidden = - searchableRepository.get(hidden = true).first().filter { it !is Application }.sorted() + searchableRepository.get( + maxVisibility = VisibilityLevel.SearchOnly, + ).first().filter { it !is Application }.sorted() emit(hidden) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) - fun isHidden(searchable: SavableSearchable): Flow { - return searchableRepository.isHidden(searchable) + fun getVisibility(searchable: SavableSearchable): Flow { + return searchableRepository.getVisibility(searchable) } - fun setHidden(searchable: SavableSearchable, hidden: Boolean) { - if (hidden) { - searchableRepository.upsert(searchable, hidden = true, pinned = false) - } else { - searchableRepository.update(searchable, hidden = false) - } + fun setVisibility(searchable: SavableSearchable, visibilityLevel: VisibilityLevel) { + searchableRepository.upsert(searchable, visibilityLevel) } fun getIcon(searchable: SavableSearchable, size: Int): Flow { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/icons/IconsSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/icons/IconsSettingsScreenVM.kt index c16b37fe..80a3abd0 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/icons/IconsSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/icons/IconsSettingsScreenVM.kt @@ -121,9 +121,6 @@ class IconsSettingsScreenVM( favoritesService.getFavorites( includeTypes = listOf("app"), limit = grid.columnCount, - manuallySorted = true, - automaticallySorted = true, - frequentlyUsed = true, ) }.shareIn(viewModelScope, started = SharingStarted.WhileSubscribed(), 1) diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 0c5f23c5..968e4ea4 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -688,7 +688,7 @@ Create shortcut Show in favorites Number of rows - Tags… + Tags Quick actions Configure quick actions and search shortcuts Call @@ -942,4 +942,11 @@ Courthouse Townhall Large search radii can significantly slow down the search. + Label + Tags + Show in + App grid & search results + Calendar widget & search results + Search results + Never \ No newline at end of file diff --git a/data/database/src/main/java/de/mm20/launcher2/database/SearchableDao.kt b/data/database/src/main/java/de/mm20/launcher2/database/SearchableDao.kt index 3840f9ec..f8febdb2 100644 --- a/data/database/src/main/java/de/mm20/launcher2/database/SearchableDao.kt +++ b/data/database/src/main/java/de/mm20/launcher2/database/SearchableDao.kt @@ -34,16 +34,19 @@ interface SearchableDao { "SELECT * FROM Searchable " + "WHERE (" + "(:manuallySorted AND pinPosition > 1) OR " + - "(:automaticallySorted AND pinPosition = 1) OR" + - "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0 AND hidden = 0) OR " + - "(:hidden AND hidden = 1)" + - ") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" + "(:automaticallySorted AND pinPosition = 1) OR " + + "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " + + "(:unused AND pinPosition = 0 AND launchCount = 0)" + + ") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " + + "ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" ) fun get( manuallySorted: Boolean = false, automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, - hidden: Boolean = false, + unused: Boolean = false, + minVisibility: Int = 2, + maxVisibility: Int = 0, limit: Int, ): Flow> @@ -52,17 +55,20 @@ interface SearchableDao { "WHERE (`type` IN (:includeTypes)) AND " + "(" + "(:manuallySorted AND pinPosition > 1) OR " + - "(:automaticallySorted AND pinPosition = 1) OR" + - "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0 AND hidden = 0) OR " + - "(:hidden AND hidden = 1)" + - ") ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" + "(:automaticallySorted AND pinPosition = 1) OR " + + "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR" + + "(:unused AND pinPosition = 0 AND launchCount = 0)" + + ") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " + + "ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" ) fun getIncludeTypes( includeTypes: List?, manuallySorted: Boolean = false, automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, - hidden: Boolean = false, + unused: Boolean = false, + minVisibility: Int = 2, + maxVisibility: Int = 0, limit: Int, ): Flow> @@ -71,17 +77,20 @@ interface SearchableDao { "WHERE (`type` NOT IN (:excludeTypes)) AND " + "(" + "(:manuallySorted AND pinPosition > 1) OR " + - "(:automaticallySorted AND pinPosition = 1) OR" + - "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0 AND hidden = 0) OR " + - "(:hidden AND hidden = 1)" + - ") ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" + "(:automaticallySorted AND pinPosition = 1) OR " + + "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " + + "(:unused AND pinPosition = 0 AND launchCount = 0)" + + ") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " + + "ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" ) fun getExcludeTypes( excludeTypes: List?, manuallySorted: Boolean = false, automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, - hidden: Boolean = false, + unused: Boolean = false, + minVisibility: Int = 2, + maxVisibility: Int = 0, limit: Int, ): Flow> @@ -89,16 +98,19 @@ interface SearchableDao { "SELECT `key` FROM Searchable " + "WHERE (" + "(:manuallySorted AND pinPosition > 1) OR " + - "(:automaticallySorted AND pinPosition = 1) OR" + + "(:automaticallySorted AND pinPosition = 1) OR " + "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " + - "(:hidden AND hidden = 1)" + - ") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" + "(:unused AND pinPosition = 0 AND launchCount = 0)" + + ") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " + + "ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" ) fun getKeys( manuallySorted: Boolean = false, automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, - hidden: Boolean = false, + unused: Boolean = false, + minVisibility: Int = 2, + maxVisibility: Int = 0, limit: Int, ): Flow> @@ -109,15 +121,18 @@ interface SearchableDao { "(:manuallySorted AND pinPosition > 1) OR " + "(:automaticallySorted AND pinPosition = 1) OR" + "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " + - "(:hidden AND hidden = 1)" + - ") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" + "(:unused AND pinPosition = 0 AND launchCount = 0)" + + ") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " + + "ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" ) fun getKeysIncludeTypes( includeTypes: List?, manuallySorted: Boolean = false, automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, - hidden: Boolean = false, + unused: Boolean = false, + minVisibility: Int = 2, + maxVisibility: Int = 0, limit: Int, ): Flow> @@ -126,17 +141,20 @@ interface SearchableDao { "WHERE (`type` NOT IN (:excludeTypes)) AND " + "(" + "(:manuallySorted AND pinPosition > 1) OR " + - "(:automaticallySorted AND pinPosition = 1) OR" + + "(:automaticallySorted AND pinPosition = 1) OR " + "(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " + - "(:hidden AND hidden = 1)" + - ") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" + "(:unused AND pinPosition = 0 AND launchCount = 0)" + + ") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " + + "ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit" ) fun getKeysExcludeTypes( excludeTypes: List?, manuallySorted: Boolean = false, automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, - hidden: Boolean = false, + unused: Boolean = false, + minVisibility: Int = 2, + maxVisibility: Int = 0, limit: Int, ): Flow> @@ -184,7 +202,7 @@ interface SearchableDao { fun sortByWeight(keys: List): Flow> @Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1") - fun isHidden(key: String): Flow + fun getVisibility(key: String): Flow @Query("SELECT pinPosition FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinPosition ORDER BY pinPosition DESC LIMIT 1") fun isPinned(key: String): Flow diff --git a/data/database/src/main/java/de/mm20/launcher2/database/entities/SavedSearchableEntity.kt b/data/database/src/main/java/de/mm20/launcher2/database/entities/SavedSearchableEntity.kt index bf6f3b4a..455426b1 100644 --- a/data/database/src/main/java/de/mm20/launcher2/database/entities/SavedSearchableEntity.kt +++ b/data/database/src/main/java/de/mm20/launcher2/database/entities/SavedSearchableEntity.kt @@ -11,7 +11,7 @@ data class SavedSearchableEntity( @ColumnInfo(name = "searchable") val serializedSearchable: String, @ColumnInfo(defaultValue = "0") val launchCount: Int, @ColumnInfo(defaultValue = "0") val pinPosition: Int, - @ColumnInfo(defaultValue = "0") val hidden: Boolean, + @ColumnInfo(name="hidden", defaultValue = "0") val visibility: Int, @ColumnInfo(defaultValue = "0.0") val weight: Double ) diff --git a/data/searchable/src/main/java/de/mm20/launcher2/searchable/PinnedLevel.kt b/data/searchable/src/main/java/de/mm20/launcher2/searchable/PinnedLevel.kt new file mode 100644 index 00000000..f7414d6d --- /dev/null +++ b/data/searchable/src/main/java/de/mm20/launcher2/searchable/PinnedLevel.kt @@ -0,0 +1,8 @@ +package de.mm20.launcher2.searchable + +enum class PinnedLevel { + NotPinned, + FrequentlyUsed, + AutomaticallySorted, + ManuallySorted, +} \ No newline at end of file diff --git a/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt b/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt index a2b426ae..9191d46b 100644 --- a/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt +++ b/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavableSearchableRepository.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONArray @@ -32,7 +31,7 @@ import org.koin.core.error.NoBeanDefFoundException import org.koin.core.qualifier.named import java.io.File -interface SavableSearchableRepository: Backupable { +interface SavableSearchableRepository : Backupable { fun insert( searchable: SavableSearchable, @@ -40,7 +39,7 @@ interface SavableSearchableRepository: Backupable { fun upsert( searchable: SavableSearchable, - hidden: Boolean? = null, + visibility: VisibilityLevel? = null, pinned: Boolean? = null, launchCount: Int? = null, weight: Double? = null, @@ -48,7 +47,7 @@ interface SavableSearchableRepository: Backupable { fun update( searchable: SavableSearchable, - hidden: Boolean? = null, + visibility: VisibilityLevel? = null, pinned: Boolean? = null, launchCount: Int? = null, weight: Double? = null, @@ -61,29 +60,35 @@ interface SavableSearchableRepository: Backupable { searchable: SavableSearchable, ) + /** + * @param minVisibility the minimum visibility of the searchables to return. A visible is + * considered to be "lower" when it makes an item less visible. + * @param maxVisibility the maximum visibility of the searchables to return. A visible is + * considered to be "higher" when it makes an item more visible. + */ fun get( includeTypes: List? = null, excludeTypes: List? = null, - manuallySorted: Boolean = false, - automaticallySorted: Boolean = false, - frequentlyUsed: Boolean = false, - hidden: Boolean = false, + minPinnedLevel: PinnedLevel = PinnedLevel.NotPinned, + maxPinnedLevel: PinnedLevel = PinnedLevel.ManuallySorted, + minVisibility: VisibilityLevel = VisibilityLevel.Hidden, + maxVisibility: VisibilityLevel = VisibilityLevel.Default, limit: Int = 100, ): Flow> fun getKeys( includeTypes: List? = null, excludeTypes: List? = null, - manuallySorted: Boolean = false, - automaticallySorted: Boolean = false, - frequentlyUsed: Boolean = false, - hidden: Boolean = false, + minPinnedLevel: PinnedLevel = PinnedLevel.NotPinned, + maxPinnedLevel: PinnedLevel = PinnedLevel.ManuallySorted, + minVisibility: VisibilityLevel = VisibilityLevel.Hidden, + maxVisibility: VisibilityLevel = VisibilityLevel.Default, limit: Int = 100, ): Flow> fun isPinned(searchable: SavableSearchable): Flow - fun isHidden(searchable: SavableSearchable): Flow + fun getVisibility(searchable: SavableSearchable): Flow fun updateFavorites( manuallySorted: List, automaticallySorted: List, @@ -132,7 +137,7 @@ internal class SavableSearchableRepositoryImpl( key = searchable.key, type = searchable.domain, serializedSearchable = searchable.serialize() ?: return@launch, - hidden = false, + visibility = VisibilityLevel.Default.value, launchCount = 0, weight = 0.0, pinPosition = 0, @@ -144,7 +149,7 @@ internal class SavableSearchableRepositoryImpl( override fun upsert( searchable: SavableSearchable, - hidden: Boolean?, + visibility: VisibilityLevel?, pinned: Boolean?, launchCount: Int?, weight: Double? @@ -156,7 +161,7 @@ internal class SavableSearchableRepositoryImpl( SavedSearchableEntity( key = searchable.key, type = searchable.domain, - hidden = hidden ?: entity?.hidden ?: false, + visibility = visibility?.value ?: entity?.visibility ?: 0, pinPosition = pinned?.let { if (it) 1 else 0 } ?: entity?.pinPosition ?: 0, launchCount = launchCount ?: entity?.launchCount ?: 0, weight = weight ?: entity?.weight ?: 0.0, @@ -168,7 +173,7 @@ internal class SavableSearchableRepositoryImpl( override fun update( searchable: SavableSearchable, - hidden: Boolean?, + visibility: VisibilityLevel?, pinned: Boolean?, launchCount: Int?, weight: Double? @@ -180,7 +185,7 @@ internal class SavableSearchableRepositoryImpl( SavedSearchableEntity( key = searchable.key, type = searchable.domain, - hidden = hidden ?: entity?.hidden ?: false, + visibility = visibility?.value ?: entity?.visibility ?: 0, pinPosition = pinned?.let { if (it) 1 else 0 } ?: entity?.pinPosition ?: 0, launchCount = launchCount ?: entity?.launchCount ?: 0, weight = weight ?: entity?.weight ?: 0.0, @@ -198,7 +203,8 @@ internal class SavableSearchableRepositoryImpl( WeightFactor.High -> WEIGHT_FACTOR_HIGH else -> WEIGHT_FACTOR_MEDIUM } - val item = SavedSearchable(searchable.key, searchable, 0, 0, false, 0.0) + val item = + SavedSearchable(searchable.key, searchable, 0, 0, VisibilityLevel.Default, 0.0) item.toDatabaseEntity()?.let { database.searchableDao() .touch(it, weightFactor) @@ -209,37 +215,43 @@ internal class SavableSearchableRepositoryImpl( override fun get( includeTypes: List?, excludeTypes: List?, - manuallySorted: Boolean, - automaticallySorted: Boolean, - frequentlyUsed: Boolean, - hidden: Boolean, + minPinnedLevel: PinnedLevel, + maxPinnedLevel: PinnedLevel, + minVisibility: VisibilityLevel, + maxVisibility: VisibilityLevel, limit: Int ): Flow> { val dao = database.searchableDao() val entities = when { includeTypes == null && excludeTypes == null -> dao.get( - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, - hidden = hidden, + manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel, + automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel, + frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel, + unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel, + minVisibility = minVisibility.value, + maxVisibility = maxVisibility.value, limit = limit ) includeTypes == null -> dao.getExcludeTypes( excludeTypes = excludeTypes, - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, - hidden = hidden, + manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel, + automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel, + frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel, + unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel, + minVisibility = minVisibility.value, + maxVisibility = maxVisibility.value, limit = limit ) excludeTypes == null -> dao.getIncludeTypes( includeTypes = includeTypes, - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, - hidden = hidden, + manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel, + automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel, + frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel, + unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel, + minVisibility = minVisibility.value, + maxVisibility = maxVisibility.value, limit = limit ) @@ -254,37 +266,43 @@ internal class SavableSearchableRepositoryImpl( override fun getKeys( includeTypes: List?, excludeTypes: List?, - manuallySorted: Boolean, - automaticallySorted: Boolean, - frequentlyUsed: Boolean, - hidden: Boolean, + minPinnedLevel: PinnedLevel, + maxPinnedLevel: PinnedLevel, + minVisibility: VisibilityLevel, + maxVisibility: VisibilityLevel, limit: Int ): Flow> { val dao = database.searchableDao() return when { includeTypes == null && excludeTypes == null -> dao.getKeys( - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, - hidden = hidden, + manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel, + automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel, + frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel, + unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel, + minVisibility = minVisibility.value, + maxVisibility = maxVisibility.value, limit = limit ) includeTypes == null -> dao.getKeysExcludeTypes( excludeTypes = excludeTypes, - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, - hidden = hidden, + manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel, + automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel, + frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel, + unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel, + minVisibility = minVisibility.value, + maxVisibility = maxVisibility.value, limit = limit ) excludeTypes == null -> dao.getKeysIncludeTypes( includeTypes = includeTypes, - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, - hidden = hidden, + manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel, + automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel, + frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel, + unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel, + minVisibility = minVisibility.value, + maxVisibility = maxVisibility.value, limit = limit ) @@ -296,8 +314,10 @@ internal class SavableSearchableRepositoryImpl( return database.searchableDao().isPinned(searchable.key) } - override fun isHidden(searchable: SavableSearchable): Flow { - return database.searchableDao().isHidden(searchable.key) + override fun getVisibility(searchable: SavableSearchable): Flow { + return database.searchableDao().getVisibility(searchable.key).map { + VisibilityLevel.fromInt(it) + } } override fun delete(searchable: SavableSearchable) { @@ -367,7 +387,7 @@ internal class SavableSearchableRepositoryImpl( searchable = searchable, launchCount = entity.launchCount, pinPosition = entity.pinPosition, - hidden = entity.hidden, + visibility = VisibilityLevel.fromInt(entity.visibility), weight = entity.weight ) } @@ -405,7 +425,7 @@ internal class SavableSearchableRepositoryImpl( jsonObjectOf( "key" to fav.key, "type" to fav.type, - "hidden" to fav.hidden, + "visibility" to fav.visibility, "launchCount" to fav.launchCount, "pinPosition" to fav.pinPosition, "searchable" to fav.serializedSearchable, @@ -441,7 +461,7 @@ internal class SavableSearchableRepositoryImpl( type = json.optString("type").takeIf { it.isNotEmpty() } ?: continue, serializedSearchable = json.getString("searchable"), launchCount = json.getInt("launchCount"), - hidden = json.getBoolean("hidden"), + visibility = json.optInt("visibility", 0), pinPosition = json.getInt("pinPosition"), weight = json.optDouble("weight").takeIf { !it.isNaN() } ?: 0.0 ) diff --git a/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavedSearchable.kt b/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavedSearchable.kt index 27ad0069..8207dacc 100644 --- a/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavedSearchable.kt +++ b/data/searchable/src/main/java/de/mm20/launcher2/searchable/SavedSearchable.kt @@ -11,7 +11,7 @@ data class SavedSearchable( val searchable: SavableSearchable?, var launchCount: Int, var pinPosition: Int, - var hidden: Boolean, + var visibility: VisibilityLevel, var weight: Double ) { fun toDatabaseEntity(): SavedSearchableEntity? { @@ -21,7 +21,7 @@ data class SavedSearchable( key = key, type = searchable.domain, serializedSearchable = data, - hidden = hidden, + visibility = visibility.value, pinPosition = pinPosition, launchCount = launchCount, weight = weight diff --git a/data/searchable/src/main/java/de/mm20/launcher2/searchable/VisibilityLevel.kt b/data/searchable/src/main/java/de/mm20/launcher2/searchable/VisibilityLevel.kt new file mode 100644 index 00000000..e76403c5 --- /dev/null +++ b/data/searchable/src/main/java/de/mm20/launcher2/searchable/VisibilityLevel.kt @@ -0,0 +1,32 @@ +package de.mm20.launcher2.searchable + +enum class VisibilityLevel(val value: Int) { + /** + * Default visibility: + * - apps are shown in app drawer + * - calendar events are shown in calendar widget + * - everything else is shown in search results + * - items can appear in frequently used section + */ + Default(0), + + /** + * Search only visibility: + * - items are only shown in search results + * - items can appear in frequently used section + * - items are not shown in app drawer or calendar widget + */ + SearchOnly(1), + + /** + * Hidden visibility: + * - items are not shown in search results + * - items are not shown in app drawer or calendar widget + * - items are not shown in frequently used section + */ + Hidden(2); + + companion object { + internal fun fromInt(value: Int) = entries.firstOrNull() { it.value == value } ?: Default + } +} \ No newline at end of file diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/HiddenItemBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/HiddenItemBadgeProvider.kt index 37dd15b0..a5df83fe 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/HiddenItemBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/HiddenItemBadgeProvider.kt @@ -1,14 +1,13 @@ package de.mm20.launcher2.badges.providers import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.HourglassBottom import androidx.compose.material.icons.rounded.VisibilityOff import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.BadgeIcon -import de.mm20.launcher2.badges.R import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.searchable.SavableSearchableRepository +import de.mm20.launcher2.searchable.VisibilityLevel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -28,7 +27,7 @@ class HiddenItemBadgeProvider( private val scope = CoroutineScope(Job() + Dispatchers.Default) private val hiddenItemKeys = searchableRepository.getKeys( - hidden = true, + maxVisibility = VisibilityLevel.Hidden, limit = 9999, ).shareIn(scope, SharingStarted.WhileSubscribed(), 1) diff --git a/services/favorites/src/main/java/de/mm20/launcher2/services/favorites/FavoritesService.kt b/services/favorites/src/main/java/de/mm20/launcher2/services/favorites/FavoritesService.kt index 5af99db1..82add75f 100644 --- a/services/favorites/src/main/java/de/mm20/launcher2/services/favorites/FavoritesService.kt +++ b/services/favorites/src/main/java/de/mm20/launcher2/services/favorites/FavoritesService.kt @@ -1,7 +1,9 @@ package de.mm20.launcher2.services.favorites import de.mm20.launcher2.search.SavableSearchable +import de.mm20.launcher2.searchable.PinnedLevel import de.mm20.launcher2.searchable.SavableSearchableRepository +import de.mm20.launcher2.searchable.VisibilityLevel import kotlinx.coroutines.flow.Flow class FavoritesService( @@ -10,18 +12,17 @@ class FavoritesService( fun getFavorites( includeTypes: List? = null, excludeTypes: List? = null, - manuallySorted: Boolean = false, - automaticallySorted: Boolean = false, - frequentlyUsed: Boolean = false, + minPinnedLevel: PinnedLevel = PinnedLevel.FrequentlyUsed, + maxPinnedLevel: PinnedLevel = PinnedLevel.ManuallySorted, limit: Int = 100, ): Flow> { return searchableRepository.get( includeTypes = includeTypes, excludeTypes = excludeTypes, - manuallySorted = manuallySorted, - automaticallySorted = automaticallySorted, - frequentlyUsed = frequentlyUsed, + minPinnedLevel = minPinnedLevel, + maxPinnedLevel = maxPinnedLevel, limit = limit, + minVisibility = VisibilityLevel.SearchOnly, ) } @@ -29,8 +30,8 @@ class FavoritesService( return searchableRepository.isPinned(searchable) } - fun isHidden(searchable: SavableSearchable): Flow { - return searchableRepository.isHidden(searchable) + fun getVisibility(searchable: SavableSearchable): Flow { + return searchableRepository.getVisibility(searchable) } fun pinItem(searchable: SavableSearchable) { @@ -44,7 +45,7 @@ class FavoritesService( searchableRepository.update( searchable, pinned = false, - hidden = false, + visibility = VisibilityLevel.Default, weight = 0.0, launchCount = 0, ) @@ -57,17 +58,10 @@ class FavoritesService( ) } - fun hideItem(searchable: SavableSearchable) { + fun setVisibility(searchable: SavableSearchable, visibility: VisibilityLevel) { searchableRepository.upsert( searchable, - hidden = true, - ) - } - - fun unhideItem(searchable: SavableSearchable) { - searchableRepository.upsert( - searchable, - hidden = false, + visibility = visibility, ) } diff --git a/services/tags/src/main/java/de/mm20/launcher2/services/tags/impl/TagsServiceImpl.kt b/services/tags/src/main/java/de/mm20/launcher2/services/tags/impl/TagsServiceImpl.kt index fd0822dd..46b732f2 100644 --- a/services/tags/src/main/java/de/mm20/launcher2/services/tags/impl/TagsServiceImpl.kt +++ b/services/tags/src/main/java/de/mm20/launcher2/services/tags/impl/TagsServiceImpl.kt @@ -4,6 +4,7 @@ import de.mm20.launcher2.data.customattrs.CustomAttributesRepository import de.mm20.launcher2.searchable.SavableSearchableRepository import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.data.Tag +import de.mm20.launcher2.searchable.PinnedLevel import de.mm20.launcher2.services.tags.TagsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -46,8 +47,7 @@ internal class TagsServiceImpl( customAttributesRepository.renameTag(tag, newName).join() val pinnedTags = searchableRepository.get( includeTypes = listOf(Tag.Domain), - manuallySorted = true, - automaticallySorted = true + minPinnedLevel = PinnedLevel.AutomaticallySorted, ).first() val oldTag = Tag(tag) if (pinnedTags.any { it.key == oldTag.key }) {