Add search-only visibility level to items
This commit is contained in:
parent
1251924553
commit
c7ea840fc5
@ -8,6 +8,7 @@ import de.mm20.launcher2.preferences.search.FavoritesSettings
|
|||||||
import de.mm20.launcher2.preferences.search.FavoritesSettingsData
|
import de.mm20.launcher2.preferences.search.FavoritesSettingsData
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.Tag
|
import de.mm20.launcher2.search.data.Tag
|
||||||
|
import de.mm20.launcher2.searchable.PinnedLevel
|
||||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import de.mm20.launcher2.widgets.CalendarWidget
|
import de.mm20.launcher2.widgets.CalendarWidget
|
||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
@ -29,8 +30,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val pinnedTags = favoritesService.getFavorites(
|
val pinnedTags = favoritesService.getFavorites(
|
||||||
includeTypes = listOf("tag"),
|
includeTypes = listOf("tag"),
|
||||||
manuallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
automaticallySorted = true,
|
|
||||||
).map {
|
).map {
|
||||||
it.filterIsInstance<Tag>()
|
it.filterIsInstance<Tag>()
|
||||||
}
|
}
|
||||||
@ -52,15 +52,15 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val pinned = favoritesService.getFavorites(
|
val pinned = favoritesService.getFavorites(
|
||||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||||
manuallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
automaticallySorted = true,
|
|
||||||
limit = 10 * columns,
|
limit = 10 * columns,
|
||||||
)
|
)
|
||||||
if (includeFrequentlyUsed) {
|
if (includeFrequentlyUsed) {
|
||||||
emitAll(pinned.flatMapLatest { pinned ->
|
emitAll(pinned.flatMapLatest { pinned ->
|
||||||
favoritesService.getFavorites(
|
favoritesService.getFavorites(
|
||||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||||
frequentlyUsed = true,
|
maxPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
|
minPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
||||||
).map {
|
).map {
|
||||||
pinned + it
|
pinned + it
|
||||||
|
|||||||
@ -4,11 +4,14 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredHeight
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@ -28,7 +31,6 @@ import androidx.compose.material3.LocalTextStyle
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -58,9 +60,11 @@ fun OutlinedTagsInputField(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
tags: List<String>,
|
tags: List<String>,
|
||||||
onTagsChange: (tags: List<String>) -> Unit,
|
onTagsChange: (tags: List<String>) -> Unit,
|
||||||
|
label: @Composable (() -> Unit)? = null,
|
||||||
placeholder: @Composable (() -> Unit)? = null,
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
textStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
textStyle: TextStyle = LocalTextStyle.current,
|
||||||
textColor: Color = LocalContentColor.current,
|
textColor: Color = LocalContentColor.current,
|
||||||
onAutocomplete: (suspend (query: String) -> List<String>)? = null
|
onAutocomplete: (suspend (query: String) -> List<String>)? = null
|
||||||
) {
|
) {
|
||||||
@ -127,14 +131,23 @@ fun OutlinedTagsInputField(
|
|||||||
}),
|
}),
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
OutlinedTextFieldDefaults.DecorationBox(
|
OutlinedTextFieldDefaults.DecorationBox(
|
||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(
|
||||||
value = value,
|
start = 16.dp,
|
||||||
|
end = 0.dp,
|
||||||
|
top = 12.dp,
|
||||||
|
bottom = 12.dp
|
||||||
|
),
|
||||||
|
value = tags.joinToString() + value,
|
||||||
|
label = label,
|
||||||
|
leadingIcon = leadingIcon,
|
||||||
innerTextField = {
|
innerTextField = {
|
||||||
Box {
|
Box(
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.horizontalScroll(rememberScrollState())
|
.requiredHeight(32.dp)
|
||||||
.padding(horizontal = 16.dp),
|
.horizontalScroll(rememberScrollState()),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
for ((i, tag) in tags.withIndex()) {
|
for ((i, tag) in tags.withIndex()) {
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import de.mm20.launcher2.search.Website
|
|||||||
import de.mm20.launcher2.search.data.Calculator
|
import de.mm20.launcher2.search.data.Calculator
|
||||||
import de.mm20.launcher2.search.data.UnitConverter
|
import de.mm20.launcher2.search.data.UnitConverter
|
||||||
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
||||||
|
import de.mm20.launcher2.searchable.VisibilityLevel
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
@ -102,13 +103,6 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val separateWorkProfile = searchUiSettings.separateWorkProfile
|
val separateWorkProfile = searchUiSettings.separateWorkProfile
|
||||||
|
|
||||||
private val hiddenItemKeys = searchableRepository
|
|
||||||
.getKeys(
|
|
||||||
hidden = true,
|
|
||||||
limit = 9999,
|
|
||||||
)
|
|
||||||
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
|
||||||
|
|
||||||
val bestMatch = mutableStateOf<Searchable?>(null)
|
val bestMatch = mutableStateOf<Searchable?>(null)
|
||||||
|
|
||||||
init {
|
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 ->
|
hiddenItemKeys.collectLatest { hiddenKeys ->
|
||||||
val hidden = mutableListOf<SavableSearchable>()
|
val hidden = mutableListOf<SavableSearchable>()
|
||||||
val apps = mutableListOf<Application>()
|
val apps = mutableListOf<Application>()
|
||||||
|
|||||||
@ -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(
|
Toolbar(
|
||||||
leftActions = listOf(
|
leftActions = listOf(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
|
|||||||
@ -221,39 +221,6 @@ fun CalendarItem(
|
|||||||
toolbarActions.add(favAction)
|
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(
|
toolbarActions.add(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
label = stringResource(R.string.menu_calendar_open_externally),
|
label = stringResource(R.string.menu_calendar_open_externally),
|
||||||
@ -272,8 +239,6 @@ fun CalendarItem(
|
|||||||
action = { sheetManager.showCustomizeSearchableModal(calendar) }
|
action = { sheetManager.showCustomizeSearchableModal(calendar) }
|
||||||
))
|
))
|
||||||
|
|
||||||
toolbarActions.add(hideAction)
|
|
||||||
|
|
||||||
Toolbar(
|
Toolbar(
|
||||||
leftActions = listOf(
|
leftActions = listOf(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
|
|||||||
@ -78,18 +78,6 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
|
|||||||
searchable.value?.let { favoritesService.unpinItem(it) }
|
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 {
|
val badge = searchable.flatMapLatest {
|
||||||
if (it == null) emptyFlow() else badgeService.getBadge(it)
|
if (it == null) emptyFlow() else badgeService.getBadge(it)
|
||||||
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||||
|
|||||||
@ -205,39 +205,6 @@ fun ContactItem(
|
|||||||
toolbarActions.add(favAction)
|
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(
|
toolbarActions.add(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
label = stringResource(R.string.menu_contacts_open_externally),
|
label = stringResource(R.string.menu_contacts_open_externally),
|
||||||
@ -255,7 +222,6 @@ fun ContactItem(
|
|||||||
action = { sheetManager.showCustomizeSearchableModal(contact) }
|
action = { sheetManager.showCustomizeSearchableModal(contact) }
|
||||||
))
|
))
|
||||||
|
|
||||||
toolbarActions.add(hideAction)
|
|
||||||
|
|
||||||
Toolbar(
|
Toolbar(
|
||||||
leftActions = listOf(
|
leftActions = listOf(
|
||||||
|
|||||||
@ -257,41 +257,6 @@ fun FileItem(
|
|||||||
action = { sheetManager.showCustomizeSearchableModal(file) }
|
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(
|
Toolbar(
|
||||||
leftActions = listOf(
|
leftActions = listOf(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
|
|||||||
@ -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(
|
Toolbar(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leftActions = listOf(DefaultToolbarAction(
|
leftActions = listOf(DefaultToolbarAction(
|
||||||
|
|||||||
@ -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(
|
Toolbar(
|
||||||
leftActions = listOf(
|
leftActions = listOf(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.sheets
|
package de.mm20.launcher2.ui.launcher.sheets
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.InsetDrawable
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
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.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.material.icons.Icons
|
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.ArrowDropDown
|
||||||
import androidx.compose.material.icons.rounded.Edit
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
import androidx.compose.material.icons.rounded.FilterAlt
|
import androidx.compose.material.icons.rounded.FilterAlt
|
||||||
import androidx.compose.material.icons.rounded.Search
|
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.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MenuAnchorType
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -44,7 +50,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.badges.BadgeIcon
|
||||||
import de.mm20.launcher2.icons.CustomIconWithPreview
|
import de.mm20.launcher2.icons.CustomIconWithPreview
|
||||||
import de.mm20.launcher2.icons.IconPack
|
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.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.searchable.VisibilityLevel
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.component.OutlinedTagsInputField
|
import de.mm20.launcher2.ui.component.OutlinedTagsInputField
|
||||||
@ -84,9 +92,11 @@ fun CustomizeSearchableSheet(
|
|||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = {
|
title = if (pickIcon) {
|
||||||
Text(stringResource(if (pickIcon) R.string.icon_picker_title else R.string.menu_customize))
|
{
|
||||||
},
|
Text(stringResource(R.string.icon_picker_title))
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
dismissible = { !pickIcon },
|
dismissible = { !pickIcon },
|
||||||
confirmButton = if (pickIcon) {
|
confirmButton = if (pickIcon) {
|
||||||
{
|
{
|
||||||
@ -126,44 +136,178 @@ fun CustomizeSearchableSheet(
|
|||||||
mutableStateOf(searchable.labelOverride ?: "")
|
mutableStateOf(searchable.labelOverride ?: "")
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier =
|
modifier = Modifier
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 24.dp),
|
.padding(top = 24.dp, bottom = 16.dp),
|
||||||
value = customLabelValue,
|
value = customLabelValue,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
customLabelValue = it
|
customLabelValue = it
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
label = {
|
||||||
|
Text(stringResource(R.string.customize_item_label))
|
||||||
|
},
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(searchable.label)
|
Text(searchable.label)
|
||||||
},
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.AutoMirrored.Rounded.Label, null)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var tags by remember { mutableStateOf(emptyList<String>()) }
|
var tags by remember { mutableStateOf(emptyList<String>()) }
|
||||||
|
var visibility by remember { mutableStateOf(VisibilityLevel.Default) }
|
||||||
|
|
||||||
LaunchedEffect(searchable.key) {
|
LaunchedEffect(searchable.key) {
|
||||||
|
visibility = viewModel.getVisibility().first()
|
||||||
tags = viewModel.getTags().first()
|
tags = viewModel.getTags().first()
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedTagsInputField(
|
OutlinedTagsInputField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.padding(top = 8.dp)
|
||||||
.padding(vertical = 16.dp),
|
.fillMaxWidth(),
|
||||||
tags = tags, onTagsChange = { tags = it.distinct() },
|
tags = tags, onTagsChange = { tags = it.distinct() },
|
||||||
placeholder = {
|
label = {
|
||||||
Text(stringResource(R.string.customize_tags_placeholder))
|
Text(stringResource(R.string.customize_item_tags))
|
||||||
},
|
},
|
||||||
textStyle = MaterialTheme.typography.bodyMedium,
|
|
||||||
onAutocomplete = {
|
onAutocomplete = {
|
||||||
viewModel.autocompleteTags(it).minus(tags.toSet())
|
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) {
|
DisposableEffect(searchable.key) {
|
||||||
onDispose {
|
onDispose {
|
||||||
viewModel.setCustomLabel(customLabelValue)
|
viewModel.setCustomLabel(customLabelValue)
|
||||||
viewModel.setTags(tags)
|
viewModel.setTags(tags)
|
||||||
|
viewModel.setVisibility(visibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import de.mm20.launcher2.icons.IconPack
|
|||||||
import de.mm20.launcher2.icons.IconService
|
import de.mm20.launcher2.icons.IconService
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
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.Job
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -25,6 +27,7 @@ class CustomizeSearchableSheetVM(
|
|||||||
) : KoinComponent {
|
) : KoinComponent {
|
||||||
private val iconService: IconService by inject()
|
private val iconService: IconService by inject()
|
||||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||||
|
private val favoritesService: FavoritesService by inject()
|
||||||
|
|
||||||
val isIconPickerOpen = mutableStateOf(false)
|
val isIconPickerOpen = mutableStateOf(false)
|
||||||
|
|
||||||
@ -89,10 +92,18 @@ class CustomizeSearchableSheetVM(
|
|||||||
customAttributesRepository.setTags(searchable, tags)
|
customAttributesRepository.setTags(searchable, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setVisibility(visibility: VisibilityLevel) {
|
||||||
|
favoritesService.setVisibility(searchable, visibility)
|
||||||
|
}
|
||||||
|
|
||||||
fun getTags(): Flow<List<String>> {
|
fun getTags(): Flow<List<String>> {
|
||||||
return customAttributesRepository.getTags(searchable)
|
return customAttributesRepository.getTags(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getVisibility(): Flow<VisibilityLevel> {
|
||||||
|
return favoritesService.getVisibility(searchable)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun autocompleteTags(query: String): List<String> {
|
suspend fun autocompleteTags(query: String): List<String> {
|
||||||
return customAttributesRepository.getAllTags(startsWith = query).first()
|
return customAttributesRepository.getAllTags(startsWith = query).first()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import de.mm20.launcher2.appshortcuts.AppShortcut
|
|||||||
import de.mm20.launcher2.preferences.search.FavoritesSettings
|
import de.mm20.launcher2.preferences.search.FavoritesSettings
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import de.mm20.launcher2.search.data.Tag
|
import de.mm20.launcher2.search.data.Tag
|
||||||
|
import de.mm20.launcher2.searchable.PinnedLevel
|
||||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@ -60,21 +61,22 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
loading.value = showLoadingIndicator
|
loading.value = showLoadingIndicator
|
||||||
manuallySorted = mutableListOf()
|
manuallySorted = mutableListOf()
|
||||||
manuallySorted = favoritesService.getFavorites(
|
manuallySorted = favoritesService.getFavorites(
|
||||||
manuallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
automaticallySorted = favoritesService.getFavorites(
|
automaticallySorted = favoritesService.getFavorites(
|
||||||
automaticallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
|
maxPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
frequentlyUsed = favoritesService.getFavorites(
|
frequentlyUsed = favoritesService.getFavorites(
|
||||||
frequentlyUsed = true,
|
minPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
|
maxPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
val pinnedTags = favoritesService.getFavorites(
|
val pinnedTags = favoritesService.getFavorites(
|
||||||
includeTypes = listOf("tag"),
|
includeTypes = listOf("tag"),
|
||||||
manuallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
automaticallySorted = true,
|
|
||||||
).first().filterIsInstance<Tag>().toMutableList()
|
).first().filterIsInstance<Tag>().toMutableList()
|
||||||
availableTags.value =
|
availableTags.value =
|
||||||
customAttributesRepository
|
customAttributesRepository
|
||||||
@ -296,7 +298,8 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
favoritesService.unpinItem(item.item)
|
favoritesService.unpinItem(item.item)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
frequentlyUsed = favoritesService.getFavorites(
|
frequentlyUsed = favoritesService.getFavorites(
|
||||||
frequentlyUsed = true,
|
minPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
|
maxPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
buildItemList()
|
buildItemList()
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import de.mm20.launcher2.ktx.tryStartActivity
|
|||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.search.CalendarEvent
|
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.services.favorites.FavoritesService
|
||||||
import de.mm20.launcher2.widgets.CalendarWidget
|
import de.mm20.launcher2.widgets.CalendarWidget
|
||||||
import de.mm20.launcher2.widgets.CalendarWidgetConfig
|
import de.mm20.launcher2.widgets.CalendarWidgetConfig
|
||||||
@ -42,8 +44,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
val pinnedCalendarEvents =
|
val pinnedCalendarEvents =
|
||||||
favoritesService.getFavorites(
|
favoritesService.getFavorites(
|
||||||
includeTypes = listOf("calendar"),
|
includeTypes = listOf("calendar"),
|
||||||
automaticallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
manuallySorted = true,
|
|
||||||
).stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
).stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
val nextEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
|
val nextEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
|
||||||
var availableDates = listOf(LocalDate.now())
|
var availableDates = listOf(LocalDate.now())
|
||||||
@ -172,7 +173,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
).collectLatest { events ->
|
).collectLatest { events ->
|
||||||
searchableRepository.getKeys(
|
searchableRepository.getKeys(
|
||||||
includeTypes = listOf("calendar"),
|
includeTypes = listOf("calendar"),
|
||||||
hidden = true,
|
maxVisibility = VisibilityLevel.SearchOnly,
|
||||||
limit = 9999,
|
limit = 9999,
|
||||||
).collectLatest { hidden ->
|
).collectLatest { hidden ->
|
||||||
upcomingEvents = events.filter { !hidden.contains(it.key) }
|
upcomingEvents = events.filter { !hidden.contains(it.key) }
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import de.mm20.launcher2.preferences.ui.UiSettings
|
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||||
|
import de.mm20.launcher2.searchable.PinnedLevel
|
||||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||||
import de.mm20.launcher2.widgets.CalendarWidget
|
import de.mm20.launcher2.widgets.CalendarWidget
|
||||||
@ -44,9 +45,7 @@ class FavoritesPartProvider : PartProvider, KoinComponent {
|
|||||||
val favorites by remember(columns, excludeCalendar) {
|
val favorites by remember(columns, excludeCalendar) {
|
||||||
favoritesService.getFavorites(
|
favoritesService.getFavorites(
|
||||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||||
manuallySorted = true,
|
minPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
automaticallySorted = true,
|
|
||||||
frequentlyUsed = true,
|
|
||||||
limit = columns
|
limit = columns
|
||||||
)
|
)
|
||||||
}.collectAsState(emptyList())
|
}.collectAsState(emptyList())
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package de.mm20.launcher2.ui.settings.hiddenitems
|
package de.mm20.launcher2.ui.settings.hiddenitems
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
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.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
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.Visibility
|
||||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
@ -25,13 +27,15 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
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.R
|
||||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
@ -42,7 +46,6 @@ import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
|||||||
fun HiddenItemsSettingsScreen() {
|
fun HiddenItemsSettingsScreen() {
|
||||||
val viewModel: HiddenItemsSettingsScreenVM = viewModel()
|
val viewModel: HiddenItemsSettingsScreenVM = viewModel()
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val apps by viewModel.allApps.collectAsState()
|
val apps by viewModel.allApps.collectAsState()
|
||||||
@ -66,63 +69,41 @@ fun HiddenItemsSettingsScreen() {
|
|||||||
viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() })
|
viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() })
|
||||||
}.collectAsState(null)
|
}.collectAsState(null)
|
||||||
|
|
||||||
val isHidden by remember(searchable.key) {
|
val visibility by remember(searchable.key) {
|
||||||
viewModel.isHidden(searchable)
|
viewModel.getVisibility(searchable)
|
||||||
}.collectAsState(false)
|
}.collectAsState(null)
|
||||||
|
|
||||||
var showPopup by remember(searchable.key) {
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
HiddenItem(
|
HiddenItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(
|
.clickable {
|
||||||
onClick = {
|
if (searchable is Application || searchable is CalendarEvent) {
|
||||||
viewModel.setHidden(searchable, !isHidden)
|
showDropdown = true
|
||||||
},
|
} else {
|
||||||
onLongClick = {
|
if (visibility == null) return@clickable
|
||||||
showPopup = true
|
viewModel.setVisibility(
|
||||||
|
searchable,
|
||||||
|
if (visibility == VisibilityLevel.Default) VisibilityLevel.Hidden else VisibilityLevel.Default
|
||||||
|
)
|
||||||
}
|
}
|
||||||
),
|
},
|
||||||
icon = icon,
|
icon = icon,
|
||||||
label = searchable.label,
|
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() })
|
viewModel.getIcon(searchable, with(density) { 32.dp.roundToPx() })
|
||||||
}.collectAsState(null)
|
}.collectAsState(null)
|
||||||
|
|
||||||
val isHidden by remember(searchable.key) {
|
val visibility by remember(searchable.key) {
|
||||||
viewModel.isHidden(searchable)
|
viewModel.getVisibility(searchable)
|
||||||
}.collectAsState(false)
|
}.collectAsState(null)
|
||||||
|
|
||||||
var showPopup by remember(searchable.key) {
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
HiddenItem(
|
HiddenItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(
|
.clickable {
|
||||||
onClick = {
|
if (searchable is Application || searchable is CalendarEvent) {
|
||||||
viewModel.setHidden(searchable, !isHidden)
|
showDropdown = true
|
||||||
},
|
} else {
|
||||||
onLongClick = {
|
if (visibility == null) return@clickable
|
||||||
showPopup = true
|
viewModel.setVisibility(
|
||||||
|
searchable,
|
||||||
|
if (visibility == VisibilityLevel.Default) VisibilityLevel.Hidden else VisibilityLevel.Default
|
||||||
|
)
|
||||||
}
|
}
|
||||||
),
|
},
|
||||||
icon = icon,
|
icon = icon,
|
||||||
label = searchable.label,
|
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
|
@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,
|
modifier: Modifier,
|
||||||
icon: LauncherIcon?,
|
icon: LauncherIcon?,
|
||||||
label: String,
|
label: String,
|
||||||
isHidden: Boolean,
|
visibility: VisibilityLevel?,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -219,11 +274,18 @@ fun HiddenItem(
|
|||||||
modifier = Modifier.weight(1f, fill = true),
|
modifier = Modifier.weight(1f, fill = true),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
Icon(
|
if (visibility != null) {
|
||||||
modifier = Modifier.alpha(if (isHidden) 0.3f else 1f),
|
Icon(
|
||||||
imageVector = if (isHidden) Icons.Rounded.VisibilityOff else Icons.Rounded.Visibility,
|
modifier = Modifier.alpha(if (visibility == VisibilityLevel.Hidden) 0.3f else 1f),
|
||||||
tint = if (isHidden) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.primary,
|
imageVector = when (visibility) {
|
||||||
contentDescription = null
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,6 +12,7 @@ import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
|||||||
import de.mm20.launcher2.preferences.ui.SearchUiSettings
|
import de.mm20.launcher2.preferences.ui.SearchUiSettings
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.Application
|
import de.mm20.launcher2.search.Application
|
||||||
|
import de.mm20.launcher2.searchable.VisibilityLevel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@ -20,7 +21,6 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -36,20 +36,18 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
val hiddenItems: StateFlow<List<SavableSearchable>> = flow {
|
val hiddenItems: StateFlow<List<SavableSearchable>> = flow {
|
||||||
val hidden =
|
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)
|
emit(hidden)
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
|
|
||||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
fun getVisibility(searchable: SavableSearchable): Flow<VisibilityLevel> {
|
||||||
return searchableRepository.isHidden(searchable)
|
return searchableRepository.getVisibility(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setHidden(searchable: SavableSearchable, hidden: Boolean) {
|
fun setVisibility(searchable: SavableSearchable, visibilityLevel: VisibilityLevel) {
|
||||||
if (hidden) {
|
searchableRepository.upsert(searchable, visibilityLevel)
|
||||||
searchableRepository.upsert(searchable, hidden = true, pinned = false)
|
|
||||||
} else {
|
|
||||||
searchableRepository.update(searchable, hidden = false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon?> {
|
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon?> {
|
||||||
|
|||||||
@ -121,9 +121,6 @@ class IconsSettingsScreenVM(
|
|||||||
favoritesService.getFavorites(
|
favoritesService.getFavorites(
|
||||||
includeTypes = listOf("app"),
|
includeTypes = listOf("app"),
|
||||||
limit = grid.columnCount,
|
limit = grid.columnCount,
|
||||||
manuallySorted = true,
|
|
||||||
automaticallySorted = true,
|
|
||||||
frequentlyUsed = true,
|
|
||||||
)
|
)
|
||||||
}.shareIn(viewModelScope, started = SharingStarted.WhileSubscribed(), 1)
|
}.shareIn(viewModelScope, started = SharingStarted.WhileSubscribed(), 1)
|
||||||
|
|
||||||
|
|||||||
@ -688,7 +688,7 @@
|
|||||||
<string name="create_app_shortcut">Create shortcut</string>
|
<string name="create_app_shortcut">Create shortcut</string>
|
||||||
<string name="frequently_used_show_in_favorites">Show in favorites</string>
|
<string name="frequently_used_show_in_favorites">Show in favorites</string>
|
||||||
<string name="frequently_used_rows">Number of rows</string>
|
<string name="frequently_used_rows">Number of rows</string>
|
||||||
<string name="customize_tags_placeholder">Tags…</string>
|
<string name="customize_tags_placeholder">Tags</string>
|
||||||
<string name="preference_screen_search_actions">Quick actions</string>
|
<string name="preference_screen_search_actions">Quick actions</string>
|
||||||
<string name="preference_search_search_actions_summary">Configure quick actions and search shortcuts</string>
|
<string name="preference_search_search_actions_summary">Configure quick actions and search shortcuts</string>
|
||||||
<string name="search_action_call">Call</string>
|
<string name="search_action_call">Call</string>
|
||||||
@ -942,4 +942,11 @@
|
|||||||
<string name="poi_category_courthouse">Courthouse</string>
|
<string name="poi_category_courthouse">Courthouse</string>
|
||||||
<string name="poi_category_townhall">Townhall</string>
|
<string name="poi_category_townhall">Townhall</string>
|
||||||
<string name="preference_search_locations_radius_large_radius_warning">Large search radii can significantly slow down the search.</string>
|
<string name="preference_search_locations_radius_large_radius_warning">Large search radii can significantly slow down the search.</string>
|
||||||
|
<string name="customize_item_label">Label</string>
|
||||||
|
<string name="customize_item_tags">Tags</string>
|
||||||
|
<string name="customize_item_visibility">Show in</string>
|
||||||
|
<string name="item_visibility_app_default">App grid & search results</string>
|
||||||
|
<string name="item_visibility_calendar_default">Calendar widget & search results</string>
|
||||||
|
<string name="item_visibility_search_only">Search results</string>
|
||||||
|
<string name="item_visibility_hidden">Never</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -34,16 +34,19 @@ interface SearchableDao {
|
|||||||
"SELECT * FROM Searchable " +
|
"SELECT * FROM Searchable " +
|
||||||
"WHERE (" +
|
"WHERE (" +
|
||||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
"(:automaticallySorted AND pinPosition = 1) OR " +
|
||||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0 AND hidden = 0) OR " +
|
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||||
"(:hidden AND hidden = 1)" +
|
"(:unused AND pinPosition = 0 AND launchCount = 0)" +
|
||||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " +
|
||||||
|
"ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
)
|
)
|
||||||
fun get(
|
fun get(
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
hidden: Boolean = false,
|
unused: Boolean = false,
|
||||||
|
minVisibility: Int = 2,
|
||||||
|
maxVisibility: Int = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<SavedSearchableEntity>>
|
): Flow<List<SavedSearchableEntity>>
|
||||||
|
|
||||||
@ -52,17 +55,20 @@ interface SearchableDao {
|
|||||||
"WHERE (`type` IN (:includeTypes)) AND " +
|
"WHERE (`type` IN (:includeTypes)) AND " +
|
||||||
"(" +
|
"(" +
|
||||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
"(:automaticallySorted AND pinPosition = 1) OR " +
|
||||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0 AND hidden = 0) OR " +
|
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR" +
|
||||||
"(:hidden AND hidden = 1)" +
|
"(:unused AND pinPosition = 0 AND launchCount = 0)" +
|
||||||
") ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " +
|
||||||
|
"ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
)
|
)
|
||||||
fun getIncludeTypes(
|
fun getIncludeTypes(
|
||||||
includeTypes: List<String>?,
|
includeTypes: List<String>?,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
hidden: Boolean = false,
|
unused: Boolean = false,
|
||||||
|
minVisibility: Int = 2,
|
||||||
|
maxVisibility: Int = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<SavedSearchableEntity>>
|
): Flow<List<SavedSearchableEntity>>
|
||||||
|
|
||||||
@ -71,17 +77,20 @@ interface SearchableDao {
|
|||||||
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
|
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
|
||||||
"(" +
|
"(" +
|
||||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
"(:automaticallySorted AND pinPosition = 1) OR " +
|
||||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0 AND hidden = 0) OR " +
|
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||||
"(:hidden AND hidden = 1)" +
|
"(:unused AND pinPosition = 0 AND launchCount = 0)" +
|
||||||
") ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " +
|
||||||
|
"ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
)
|
)
|
||||||
fun getExcludeTypes(
|
fun getExcludeTypes(
|
||||||
excludeTypes: List<String>?,
|
excludeTypes: List<String>?,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
hidden: Boolean = false,
|
unused: Boolean = false,
|
||||||
|
minVisibility: Int = 2,
|
||||||
|
maxVisibility: Int = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<SavedSearchableEntity>>
|
): Flow<List<SavedSearchableEntity>>
|
||||||
|
|
||||||
@ -89,16 +98,19 @@ interface SearchableDao {
|
|||||||
"SELECT `key` FROM Searchable " +
|
"SELECT `key` FROM Searchable " +
|
||||||
"WHERE (" +
|
"WHERE (" +
|
||||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
"(:automaticallySorted AND pinPosition = 1) OR " +
|
||||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||||
"(:hidden AND hidden = 1)" +
|
"(:unused AND pinPosition = 0 AND launchCount = 0)" +
|
||||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " +
|
||||||
|
"ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
)
|
)
|
||||||
fun getKeys(
|
fun getKeys(
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
hidden: Boolean = false,
|
unused: Boolean = false,
|
||||||
|
minVisibility: Int = 2,
|
||||||
|
maxVisibility: Int = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<String>>
|
): Flow<List<String>>
|
||||||
|
|
||||||
@ -109,15 +121,18 @@ interface SearchableDao {
|
|||||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||||
"(:hidden AND hidden = 1)" +
|
"(:unused AND pinPosition = 0 AND launchCount = 0)" +
|
||||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " +
|
||||||
|
"ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
)
|
)
|
||||||
fun getKeysIncludeTypes(
|
fun getKeysIncludeTypes(
|
||||||
includeTypes: List<String>?,
|
includeTypes: List<String>?,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
hidden: Boolean = false,
|
unused: Boolean = false,
|
||||||
|
minVisibility: Int = 2,
|
||||||
|
maxVisibility: Int = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<String>>
|
): Flow<List<String>>
|
||||||
|
|
||||||
@ -126,17 +141,20 @@ interface SearchableDao {
|
|||||||
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
|
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
|
||||||
"(" +
|
"(" +
|
||||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
"(:automaticallySorted AND pinPosition = 1) OR " +
|
||||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||||
"(:hidden AND hidden = 1)" +
|
"(:unused AND pinPosition = 0 AND launchCount = 0)" +
|
||||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
") AND (hidden <= :minVisibility AND hidden >= :maxVisibility) " +
|
||||||
|
"ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
)
|
)
|
||||||
fun getKeysExcludeTypes(
|
fun getKeysExcludeTypes(
|
||||||
excludeTypes: List<String>?,
|
excludeTypes: List<String>?,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
hidden: Boolean = false,
|
unused: Boolean = false,
|
||||||
|
minVisibility: Int = 2,
|
||||||
|
maxVisibility: Int = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<String>>
|
): Flow<List<String>>
|
||||||
|
|
||||||
@ -184,7 +202,7 @@ interface SearchableDao {
|
|||||||
fun sortByWeight(keys: List<String>): Flow<List<String>>
|
fun sortByWeight(keys: List<String>): Flow<List<String>>
|
||||||
|
|
||||||
@Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1")
|
@Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1")
|
||||||
fun isHidden(key: String): Flow<Boolean>
|
fun getVisibility(key: String): Flow<Int>
|
||||||
|
|
||||||
@Query("SELECT pinPosition FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinPosition ORDER BY pinPosition DESC LIMIT 1")
|
@Query("SELECT pinPosition FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinPosition ORDER BY pinPosition DESC LIMIT 1")
|
||||||
fun isPinned(key: String): Flow<Boolean>
|
fun isPinned(key: String): Flow<Boolean>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ data class SavedSearchableEntity(
|
|||||||
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
||||||
@ColumnInfo(defaultValue = "0") val launchCount: Int,
|
@ColumnInfo(defaultValue = "0") val launchCount: Int,
|
||||||
@ColumnInfo(defaultValue = "0") val pinPosition: 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
|
@ColumnInfo(defaultValue = "0.0") val weight: Double
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
package de.mm20.launcher2.searchable
|
||||||
|
|
||||||
|
enum class PinnedLevel {
|
||||||
|
NotPinned,
|
||||||
|
FrequentlyUsed,
|
||||||
|
AutomaticallySorted,
|
||||||
|
ManuallySorted,
|
||||||
|
}
|
||||||
@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
@ -32,7 +31,7 @@ import org.koin.core.error.NoBeanDefFoundException
|
|||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface SavableSearchableRepository: Backupable {
|
interface SavableSearchableRepository : Backupable {
|
||||||
|
|
||||||
fun insert(
|
fun insert(
|
||||||
searchable: SavableSearchable,
|
searchable: SavableSearchable,
|
||||||
@ -40,7 +39,7 @@ interface SavableSearchableRepository: Backupable {
|
|||||||
|
|
||||||
fun upsert(
|
fun upsert(
|
||||||
searchable: SavableSearchable,
|
searchable: SavableSearchable,
|
||||||
hidden: Boolean? = null,
|
visibility: VisibilityLevel? = null,
|
||||||
pinned: Boolean? = null,
|
pinned: Boolean? = null,
|
||||||
launchCount: Int? = null,
|
launchCount: Int? = null,
|
||||||
weight: Double? = null,
|
weight: Double? = null,
|
||||||
@ -48,7 +47,7 @@ interface SavableSearchableRepository: Backupable {
|
|||||||
|
|
||||||
fun update(
|
fun update(
|
||||||
searchable: SavableSearchable,
|
searchable: SavableSearchable,
|
||||||
hidden: Boolean? = null,
|
visibility: VisibilityLevel? = null,
|
||||||
pinned: Boolean? = null,
|
pinned: Boolean? = null,
|
||||||
launchCount: Int? = null,
|
launchCount: Int? = null,
|
||||||
weight: Double? = null,
|
weight: Double? = null,
|
||||||
@ -61,29 +60,35 @@ interface SavableSearchableRepository: Backupable {
|
|||||||
searchable: SavableSearchable,
|
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(
|
fun get(
|
||||||
includeTypes: List<String>? = null,
|
includeTypes: List<String>? = null,
|
||||||
excludeTypes: List<String>? = null,
|
excludeTypes: List<String>? = null,
|
||||||
manuallySorted: Boolean = false,
|
minPinnedLevel: PinnedLevel = PinnedLevel.NotPinned,
|
||||||
automaticallySorted: Boolean = false,
|
maxPinnedLevel: PinnedLevel = PinnedLevel.ManuallySorted,
|
||||||
frequentlyUsed: Boolean = false,
|
minVisibility: VisibilityLevel = VisibilityLevel.Hidden,
|
||||||
hidden: Boolean = false,
|
maxVisibility: VisibilityLevel = VisibilityLevel.Default,
|
||||||
limit: Int = 100,
|
limit: Int = 100,
|
||||||
): Flow<List<SavableSearchable>>
|
): Flow<List<SavableSearchable>>
|
||||||
|
|
||||||
fun getKeys(
|
fun getKeys(
|
||||||
includeTypes: List<String>? = null,
|
includeTypes: List<String>? = null,
|
||||||
excludeTypes: List<String>? = null,
|
excludeTypes: List<String>? = null,
|
||||||
manuallySorted: Boolean = false,
|
minPinnedLevel: PinnedLevel = PinnedLevel.NotPinned,
|
||||||
automaticallySorted: Boolean = false,
|
maxPinnedLevel: PinnedLevel = PinnedLevel.ManuallySorted,
|
||||||
frequentlyUsed: Boolean = false,
|
minVisibility: VisibilityLevel = VisibilityLevel.Hidden,
|
||||||
hidden: Boolean = false,
|
maxVisibility: VisibilityLevel = VisibilityLevel.Default,
|
||||||
limit: Int = 100,
|
limit: Int = 100,
|
||||||
): Flow<List<String>>
|
): Flow<List<String>>
|
||||||
|
|
||||||
|
|
||||||
fun isPinned(searchable: SavableSearchable): Flow<Boolean>
|
fun isPinned(searchable: SavableSearchable): Flow<Boolean>
|
||||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean>
|
fun getVisibility(searchable: SavableSearchable): Flow<VisibilityLevel>
|
||||||
fun updateFavorites(
|
fun updateFavorites(
|
||||||
manuallySorted: List<SavableSearchable>,
|
manuallySorted: List<SavableSearchable>,
|
||||||
automaticallySorted: List<SavableSearchable>,
|
automaticallySorted: List<SavableSearchable>,
|
||||||
@ -132,7 +137,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
key = searchable.key,
|
key = searchable.key,
|
||||||
type = searchable.domain,
|
type = searchable.domain,
|
||||||
serializedSearchable = searchable.serialize() ?: return@launch,
|
serializedSearchable = searchable.serialize() ?: return@launch,
|
||||||
hidden = false,
|
visibility = VisibilityLevel.Default.value,
|
||||||
launchCount = 0,
|
launchCount = 0,
|
||||||
weight = 0.0,
|
weight = 0.0,
|
||||||
pinPosition = 0,
|
pinPosition = 0,
|
||||||
@ -144,7 +149,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
|
|
||||||
override fun upsert(
|
override fun upsert(
|
||||||
searchable: SavableSearchable,
|
searchable: SavableSearchable,
|
||||||
hidden: Boolean?,
|
visibility: VisibilityLevel?,
|
||||||
pinned: Boolean?,
|
pinned: Boolean?,
|
||||||
launchCount: Int?,
|
launchCount: Int?,
|
||||||
weight: Double?
|
weight: Double?
|
||||||
@ -156,7 +161,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
SavedSearchableEntity(
|
SavedSearchableEntity(
|
||||||
key = searchable.key,
|
key = searchable.key,
|
||||||
type = searchable.domain,
|
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,
|
pinPosition = pinned?.let { if (it) 1 else 0 } ?: entity?.pinPosition ?: 0,
|
||||||
launchCount = launchCount ?: entity?.launchCount ?: 0,
|
launchCount = launchCount ?: entity?.launchCount ?: 0,
|
||||||
weight = weight ?: entity?.weight ?: 0.0,
|
weight = weight ?: entity?.weight ?: 0.0,
|
||||||
@ -168,7 +173,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
|
|
||||||
override fun update(
|
override fun update(
|
||||||
searchable: SavableSearchable,
|
searchable: SavableSearchable,
|
||||||
hidden: Boolean?,
|
visibility: VisibilityLevel?,
|
||||||
pinned: Boolean?,
|
pinned: Boolean?,
|
||||||
launchCount: Int?,
|
launchCount: Int?,
|
||||||
weight: Double?
|
weight: Double?
|
||||||
@ -180,7 +185,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
SavedSearchableEntity(
|
SavedSearchableEntity(
|
||||||
key = searchable.key,
|
key = searchable.key,
|
||||||
type = searchable.domain,
|
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,
|
pinPosition = pinned?.let { if (it) 1 else 0 } ?: entity?.pinPosition ?: 0,
|
||||||
launchCount = launchCount ?: entity?.launchCount ?: 0,
|
launchCount = launchCount ?: entity?.launchCount ?: 0,
|
||||||
weight = weight ?: entity?.weight ?: 0.0,
|
weight = weight ?: entity?.weight ?: 0.0,
|
||||||
@ -198,7 +203,8 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
WeightFactor.High -> WEIGHT_FACTOR_HIGH
|
WeightFactor.High -> WEIGHT_FACTOR_HIGH
|
||||||
else -> WEIGHT_FACTOR_MEDIUM
|
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 {
|
item.toDatabaseEntity()?.let {
|
||||||
database.searchableDao()
|
database.searchableDao()
|
||||||
.touch(it, weightFactor)
|
.touch(it, weightFactor)
|
||||||
@ -209,37 +215,43 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
override fun get(
|
override fun get(
|
||||||
includeTypes: List<String>?,
|
includeTypes: List<String>?,
|
||||||
excludeTypes: List<String>?,
|
excludeTypes: List<String>?,
|
||||||
manuallySorted: Boolean,
|
minPinnedLevel: PinnedLevel,
|
||||||
automaticallySorted: Boolean,
|
maxPinnedLevel: PinnedLevel,
|
||||||
frequentlyUsed: Boolean,
|
minVisibility: VisibilityLevel,
|
||||||
hidden: Boolean,
|
maxVisibility: VisibilityLevel,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Flow<List<SavableSearchable>> {
|
): Flow<List<SavableSearchable>> {
|
||||||
val dao = database.searchableDao()
|
val dao = database.searchableDao()
|
||||||
val entities = when {
|
val entities = when {
|
||||||
includeTypes == null && excludeTypes == null -> dao.get(
|
includeTypes == null && excludeTypes == null -> dao.get(
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel,
|
||||||
hidden = hidden,
|
unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel,
|
||||||
|
minVisibility = minVisibility.value,
|
||||||
|
maxVisibility = maxVisibility.value,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
includeTypes == null -> dao.getExcludeTypes(
|
includeTypes == null -> dao.getExcludeTypes(
|
||||||
excludeTypes = excludeTypes,
|
excludeTypes = excludeTypes,
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel,
|
||||||
hidden = hidden,
|
unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel,
|
||||||
|
minVisibility = minVisibility.value,
|
||||||
|
maxVisibility = maxVisibility.value,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
excludeTypes == null -> dao.getIncludeTypes(
|
excludeTypes == null -> dao.getIncludeTypes(
|
||||||
includeTypes = includeTypes,
|
includeTypes = includeTypes,
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel,
|
||||||
hidden = hidden,
|
unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel,
|
||||||
|
minVisibility = minVisibility.value,
|
||||||
|
maxVisibility = maxVisibility.value,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -254,37 +266,43 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
override fun getKeys(
|
override fun getKeys(
|
||||||
includeTypes: List<String>?,
|
includeTypes: List<String>?,
|
||||||
excludeTypes: List<String>?,
|
excludeTypes: List<String>?,
|
||||||
manuallySorted: Boolean,
|
minPinnedLevel: PinnedLevel,
|
||||||
automaticallySorted: Boolean,
|
maxPinnedLevel: PinnedLevel,
|
||||||
frequentlyUsed: Boolean,
|
minVisibility: VisibilityLevel,
|
||||||
hidden: Boolean,
|
maxVisibility: VisibilityLevel,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Flow<List<String>> {
|
): Flow<List<String>> {
|
||||||
val dao = database.searchableDao()
|
val dao = database.searchableDao()
|
||||||
return when {
|
return when {
|
||||||
includeTypes == null && excludeTypes == null -> dao.getKeys(
|
includeTypes == null && excludeTypes == null -> dao.getKeys(
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel,
|
||||||
hidden = hidden,
|
unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel,
|
||||||
|
minVisibility = minVisibility.value,
|
||||||
|
maxVisibility = maxVisibility.value,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
includeTypes == null -> dao.getKeysExcludeTypes(
|
includeTypes == null -> dao.getKeysExcludeTypes(
|
||||||
excludeTypes = excludeTypes,
|
excludeTypes = excludeTypes,
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel,
|
||||||
hidden = hidden,
|
unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel,
|
||||||
|
minVisibility = minVisibility.value,
|
||||||
|
maxVisibility = maxVisibility.value,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
excludeTypes == null -> dao.getKeysIncludeTypes(
|
excludeTypes == null -> dao.getKeysIncludeTypes(
|
||||||
includeTypes = includeTypes,
|
includeTypes = includeTypes,
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = PinnedLevel.ManuallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = PinnedLevel.AutomaticallySorted in minPinnedLevel..maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = PinnedLevel.FrequentlyUsed in minPinnedLevel..maxPinnedLevel,
|
||||||
hidden = hidden,
|
unused = PinnedLevel.NotPinned in minPinnedLevel..maxPinnedLevel,
|
||||||
|
minVisibility = minVisibility.value,
|
||||||
|
maxVisibility = maxVisibility.value,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -296,8 +314,10 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
return database.searchableDao().isPinned(searchable.key)
|
return database.searchableDao().isPinned(searchable.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
override fun getVisibility(searchable: SavableSearchable): Flow<VisibilityLevel> {
|
||||||
return database.searchableDao().isHidden(searchable.key)
|
return database.searchableDao().getVisibility(searchable.key).map {
|
||||||
|
VisibilityLevel.fromInt(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(searchable: SavableSearchable) {
|
override fun delete(searchable: SavableSearchable) {
|
||||||
@ -367,7 +387,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
searchable = searchable,
|
searchable = searchable,
|
||||||
launchCount = entity.launchCount,
|
launchCount = entity.launchCount,
|
||||||
pinPosition = entity.pinPosition,
|
pinPosition = entity.pinPosition,
|
||||||
hidden = entity.hidden,
|
visibility = VisibilityLevel.fromInt(entity.visibility),
|
||||||
weight = entity.weight
|
weight = entity.weight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -405,7 +425,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
jsonObjectOf(
|
jsonObjectOf(
|
||||||
"key" to fav.key,
|
"key" to fav.key,
|
||||||
"type" to fav.type,
|
"type" to fav.type,
|
||||||
"hidden" to fav.hidden,
|
"visibility" to fav.visibility,
|
||||||
"launchCount" to fav.launchCount,
|
"launchCount" to fav.launchCount,
|
||||||
"pinPosition" to fav.pinPosition,
|
"pinPosition" to fav.pinPosition,
|
||||||
"searchable" to fav.serializedSearchable,
|
"searchable" to fav.serializedSearchable,
|
||||||
@ -441,7 +461,7 @@ internal class SavableSearchableRepositoryImpl(
|
|||||||
type = json.optString("type").takeIf { it.isNotEmpty() } ?: continue,
|
type = json.optString("type").takeIf { it.isNotEmpty() } ?: continue,
|
||||||
serializedSearchable = json.getString("searchable"),
|
serializedSearchable = json.getString("searchable"),
|
||||||
launchCount = json.getInt("launchCount"),
|
launchCount = json.getInt("launchCount"),
|
||||||
hidden = json.getBoolean("hidden"),
|
visibility = json.optInt("visibility", 0),
|
||||||
pinPosition = json.getInt("pinPosition"),
|
pinPosition = json.getInt("pinPosition"),
|
||||||
weight = json.optDouble("weight").takeIf { !it.isNaN() } ?: 0.0
|
weight = json.optDouble("weight").takeIf { !it.isNaN() } ?: 0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ data class SavedSearchable(
|
|||||||
val searchable: SavableSearchable?,
|
val searchable: SavableSearchable?,
|
||||||
var launchCount: Int,
|
var launchCount: Int,
|
||||||
var pinPosition: Int,
|
var pinPosition: Int,
|
||||||
var hidden: Boolean,
|
var visibility: VisibilityLevel,
|
||||||
var weight: Double
|
var weight: Double
|
||||||
) {
|
) {
|
||||||
fun toDatabaseEntity(): SavedSearchableEntity? {
|
fun toDatabaseEntity(): SavedSearchableEntity? {
|
||||||
@ -21,7 +21,7 @@ data class SavedSearchable(
|
|||||||
key = key,
|
key = key,
|
||||||
type = searchable.domain,
|
type = searchable.domain,
|
||||||
serializedSearchable = data,
|
serializedSearchable = data,
|
||||||
hidden = hidden,
|
visibility = visibility.value,
|
||||||
pinPosition = pinPosition,
|
pinPosition = pinPosition,
|
||||||
launchCount = launchCount,
|
launchCount = launchCount,
|
||||||
weight = weight
|
weight = weight
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
package de.mm20.launcher2.badges.providers
|
package de.mm20.launcher2.badges.providers
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.HourglassBottom
|
|
||||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
import de.mm20.launcher2.badges.BadgeIcon
|
import de.mm20.launcher2.badges.BadgeIcon
|
||||||
import de.mm20.launcher2.badges.R
|
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
||||||
|
import de.mm20.launcher2.searchable.VisibilityLevel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -28,7 +27,7 @@ class HiddenItemBadgeProvider(
|
|||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
private val hiddenItemKeys = searchableRepository.getKeys(
|
private val hiddenItemKeys = searchableRepository.getKeys(
|
||||||
hidden = true,
|
maxVisibility = VisibilityLevel.Hidden,
|
||||||
limit = 9999,
|
limit = 9999,
|
||||||
).shareIn(scope, SharingStarted.WhileSubscribed(), 1)
|
).shareIn(scope, SharingStarted.WhileSubscribed(), 1)
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
package de.mm20.launcher2.services.favorites
|
package de.mm20.launcher2.services.favorites
|
||||||
|
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.searchable.PinnedLevel
|
||||||
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
||||||
|
import de.mm20.launcher2.searchable.VisibilityLevel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class FavoritesService(
|
class FavoritesService(
|
||||||
@ -10,18 +12,17 @@ class FavoritesService(
|
|||||||
fun getFavorites(
|
fun getFavorites(
|
||||||
includeTypes: List<String>? = null,
|
includeTypes: List<String>? = null,
|
||||||
excludeTypes: List<String>? = null,
|
excludeTypes: List<String>? = null,
|
||||||
manuallySorted: Boolean = false,
|
minPinnedLevel: PinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||||
automaticallySorted: Boolean = false,
|
maxPinnedLevel: PinnedLevel = PinnedLevel.ManuallySorted,
|
||||||
frequentlyUsed: Boolean = false,
|
|
||||||
limit: Int = 100,
|
limit: Int = 100,
|
||||||
): Flow<List<SavableSearchable>> {
|
): Flow<List<SavableSearchable>> {
|
||||||
return searchableRepository.get(
|
return searchableRepository.get(
|
||||||
includeTypes = includeTypes,
|
includeTypes = includeTypes,
|
||||||
excludeTypes = excludeTypes,
|
excludeTypes = excludeTypes,
|
||||||
manuallySorted = manuallySorted,
|
minPinnedLevel = minPinnedLevel,
|
||||||
automaticallySorted = automaticallySorted,
|
maxPinnedLevel = maxPinnedLevel,
|
||||||
frequentlyUsed = frequentlyUsed,
|
|
||||||
limit = limit,
|
limit = limit,
|
||||||
|
minVisibility = VisibilityLevel.SearchOnly,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +30,8 @@ class FavoritesService(
|
|||||||
return searchableRepository.isPinned(searchable)
|
return searchableRepository.isPinned(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
fun getVisibility(searchable: SavableSearchable): Flow<VisibilityLevel> {
|
||||||
return searchableRepository.isHidden(searchable)
|
return searchableRepository.getVisibility(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pinItem(searchable: SavableSearchable) {
|
fun pinItem(searchable: SavableSearchable) {
|
||||||
@ -44,7 +45,7 @@ class FavoritesService(
|
|||||||
searchableRepository.update(
|
searchableRepository.update(
|
||||||
searchable,
|
searchable,
|
||||||
pinned = false,
|
pinned = false,
|
||||||
hidden = false,
|
visibility = VisibilityLevel.Default,
|
||||||
weight = 0.0,
|
weight = 0.0,
|
||||||
launchCount = 0,
|
launchCount = 0,
|
||||||
)
|
)
|
||||||
@ -57,17 +58,10 @@ class FavoritesService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideItem(searchable: SavableSearchable) {
|
fun setVisibility(searchable: SavableSearchable, visibility: VisibilityLevel) {
|
||||||
searchableRepository.upsert(
|
searchableRepository.upsert(
|
||||||
searchable,
|
searchable,
|
||||||
hidden = true,
|
visibility = visibility,
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unhideItem(searchable: SavableSearchable) {
|
|
||||||
searchableRepository.upsert(
|
|
||||||
searchable,
|
|
||||||
hidden = false,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
|||||||
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
import de.mm20.launcher2.searchable.SavableSearchableRepository
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.Tag
|
import de.mm20.launcher2.search.data.Tag
|
||||||
|
import de.mm20.launcher2.searchable.PinnedLevel
|
||||||
import de.mm20.launcher2.services.tags.TagsService
|
import de.mm20.launcher2.services.tags.TagsService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -46,8 +47,7 @@ internal class TagsServiceImpl(
|
|||||||
customAttributesRepository.renameTag(tag, newName).join()
|
customAttributesRepository.renameTag(tag, newName).join()
|
||||||
val pinnedTags = searchableRepository.get(
|
val pinnedTags = searchableRepository.get(
|
||||||
includeTypes = listOf(Tag.Domain),
|
includeTypes = listOf(Tag.Domain),
|
||||||
manuallySorted = true,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
automaticallySorted = true
|
|
||||||
).first()
|
).first()
|
||||||
val oldTag = Tag(tag)
|
val oldTag = Tag(tag)
|
||||||
if (pinnedTags.any { it.key == oldTag.key }) {
|
if (pinnedTags.any { it.key == oldTag.key }) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user