add setting to exclude apps from shortcuts search

This commit is contained in:
MM20
2026-08-23 17:54:25 +02:00
parent 61c43650ba
commit 17f8906f26
9 changed files with 267 additions and 46 deletions

View File

@@ -52,6 +52,8 @@ import de.mm20.launcher2.ui.settings.appearance.ImportThemeSettingsRoute
import de.mm20.launcher2.ui.settings.appearance.ImportThemeSettingsScreen
import de.mm20.launcher2.ui.settings.apps.AppSearchSettingsRoute
import de.mm20.launcher2.ui.settings.apps.AppSearchSettingsScreen
import de.mm20.launcher2.ui.settings.appshortcuts.AppShortcutsSettingsRoute
import de.mm20.launcher2.ui.settings.appshortcuts.AppShortcutsSettingsScreen
import de.mm20.launcher2.ui.settings.backup.BackupSettingsRoute
import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen
import de.mm20.launcher2.ui.settings.breezyweather.BreezyWeatherSettingsRoute
@@ -261,6 +263,9 @@ class SettingsActivity : BaseActivity() {
entry<FavoritesSettingsRoute> {
FavoritesSettingsScreen()
}
entry<AppShortcutsSettingsRoute> {
AppShortcutsSettingsScreen()
}
entry<ContactsSettingsRoute> {
ContactsSettingsScreen()
}

View File

@@ -0,0 +1,66 @@
package de.mm20.launcher2.ui.settings.appshortcuts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.BadgeService
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.search.ShortcutSearchSettings
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class AppShortcutSettingsScreenVM : ViewModel(), KoinComponent {
private val appRepository by inject<AppRepository>()
private val settings by inject<ShortcutSearchSettings>()
private val permissionsManager by inject<PermissionsManager>()
private val iconService by inject<IconService>()
private val badgeService by inject<BadgeService>()
val blocklist =
settings.blocklist.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptySet())
val apps = appRepository.findMany().map { it.sorted() }.flowOn(Dispatchers.Default)
val isShortcutSearchEnabled = settings.enabled
val hasAppShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setShortcutSearchEnabled(enabled: Boolean) {
settings.setEnabled(enabled)
}
fun setAppBlocklisted(appId: String, blocked: Boolean) {
val blocklist = blocklist.value.toMutableSet()
if (blocked) {
blocklist.add(appId)
} else {
blocklist.remove(appId)
}
settings.setBlocklist(blocklist)
}
fun requestAppShortcutsPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
}
fun getIcon(app: SavableSearchable, size: Int): Flow<LauncherIcon?> {
return iconService.getIcon(app, size)
}
fun getBadge(app: SavableSearchable): Flow<Badge?> {
return badgeService.getBadge(app)
}
}

View File

@@ -0,0 +1,121 @@
package de.mm20.launcher2.ui.settings.appshortcuts
import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.NavKey
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.ktx.toPixels
data object AppShortcutsSettingsRoute : NavKey
@Composable
fun AppShortcutsSettingsScreen() {
val viewModel: AppShortcutSettingsScreenVM = viewModel()
val searchEnabled by viewModel.isShortcutSearchEnabled.collectAsStateWithLifecycle(null)
val hasPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(
null
)
val apps by viewModel.apps.collectAsStateWithLifecycle(emptyList())
val blocklist by viewModel.blocklist.collectAsStateWithLifecycle()
val activity = LocalActivity.current
val context = LocalContext.current
val xsShape = MaterialTheme.shapes.extraSmall
val mdShape = MaterialTheme.shapes.medium
PreferenceScreen(
title = { Text(stringResource(R.string.preference_search_appshortcuts)) },
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
item {
Box(modifier = Modifier.padding(bottom = 10.dp)) {
PreferenceCategory {
GuardedPreference(
locked = hasPermission == false,
onUnlock = {
viewModel.requestAppShortcutsPermission(activity as AppCompatActivity)
},
description = stringResource(
R.string.missing_permission_appshortcuts_search_settings,
stringResource(R.string.app_name),
),
) {
SwitchPreference(
title = stringResource(R.string.preference_search_appshortcuts),
summary = stringResource(R.string.preference_search_appshortcuts_summary),
icon = R.drawable.mobile_arrow_up_right_24px,
value = searchEnabled == true && hasPermission == true,
onValueChanged = {
viewModel.setShortcutSearchEnabled(it)
},
enabled = hasPermission == true,
)
}
}
}
}
itemsIndexed(apps) { index, app ->
val key = "${app.componentName.packageName}:${app.user.getSerialNumber(context)}"
Box(
modifier = Modifier.clip(
when {
apps.size == 1 -> mdShape
index == 0 -> mdShape.copy(
bottomEnd = xsShape.bottomEnd,
bottomStart = xsShape.bottomStart
)
index == apps.size - 1 -> mdShape.copy(
topEnd = xsShape.topEnd,
topStart = xsShape.topStart
)
else -> xsShape
}
)
) {
CheckboxPreference(
icon = {
val size = 32.dp
val icon by viewModel.getIcon(app, size.toPixels().toInt())
.collectAsStateWithLifecycle(null)
val badge by viewModel.getBadge(app)
.collectAsStateWithLifecycle(null)
ShapedLauncherIcon(size = size, icon = { icon }, badge = { badge })
},
title = app.label,
value = !blocklist.contains(key),
onValueChanged = {
viewModel.setAppBlocklisted(key, !it)
},
)
}
}
}
}

View File

@@ -34,6 +34,7 @@ import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
import de.mm20.launcher2.ui.locals.LocalBackStack
import de.mm20.launcher2.ui.settings.apps.AppSearchSettingsRoute
import de.mm20.launcher2.ui.settings.appshortcuts.AppShortcutsSettingsRoute
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarProviderSettingsRoute
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarSearchSettingsRoute
import de.mm20.launcher2.ui.settings.contacts.ContactsSettingsRoute
@@ -208,15 +209,18 @@ fun SearchSettingsScreen() {
stringResource(R.string.app_name),
),
) {
SwitchPreference(
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_appshortcuts),
summary = stringResource(R.string.preference_search_appshortcuts_summary),
icon = R.drawable.mobile_arrow_up_right_24px,
value = appShortcuts == true && hasAppShortcutsPermission == true,
onValueChanged = {
switchValue = appShortcuts == true && hasAppShortcutsPermission == true,
onSwitchChanged = {
viewModel.setAppShortcuts(it)
},
enabled = hasAppShortcutsPermission == true
enabled = hasAppShortcutsPermission == true,
onClick = {
backStack += AppShortcutsSettingsRoute
}
)
}