Redesign gesture settings

This commit is contained in:
MM20
2026-04-06 12:07:29 +02:00
parent b07773a8cc
commit b9c227be23
18 changed files with 826 additions and 466 deletions

View File

@@ -1,6 +1,9 @@
package de.mm20.launcher2.ui.common
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
@@ -8,10 +11,11 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.DockedSearchBar
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -34,128 +38,153 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.DismissableBottomSheet
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.ktx.animateShapeAsState
import de.mm20.launcher2.ui.ktx.toPixels
@Composable
fun SearchablePicker(
expanded: Boolean,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
value: SavableSearchable?,
onValueChanged: (SavableSearchable?) -> Unit,
onDismissRequest: () -> Unit,
) {
DismissableBottomSheet(expanded = expanded, onDismissRequest = onDismissRequest) {
val viewModel: SearchablePickerVM = viewModel()
val viewModel: SearchablePickerVM = viewModel()
val colorSurface = MaterialTheme.colorScheme.surfaceContainerLow
val colorSurface = MaterialTheme.colorScheme.surfaceContainerLow
LazyColumn(
modifier = Modifier
.fillMaxSize(),
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues()
.calculateBottomPadding(),
top = 16.dp,
)
) {
stickyHeader {
DockedSearchBar(
modifier = Modifier
.fillMaxWidth()
.drawBehind {
drawRect(
brush = Brush.verticalGradient(
0.5f to colorSurface,
0.5f to colorSurface.copy(alpha = 0f),
)
LazyColumn(
modifier = modifier,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
stickyHeader {
DockedSearchBar(
modifier = Modifier
.fillMaxWidth()
.drawBehind {
drawRect(
brush = Brush.verticalGradient(
0.5f to colorSurface,
0.5f to colorSurface.copy(alpha = 0f),
)
}
.padding(bottom = 16.dp),
expanded = false,
onExpandedChange = {},
inputField = {
SearchBarDefaults.InputField(
leadingIcon = {
Icon(
painterResource(R.drawable.search_24px),
contentDescription = null
)
},
trailingIcon = {
if (viewModel.searchQuery.isNotEmpty()) {
IconButton(
onClick = {
viewModel.onSearchQueryChanged("")
}) {
Icon(painterResource(R.drawable.close_24px), null)
}
}
},
onSearch = {},
expanded = false,
onExpandedChange = {},
query = viewModel.searchQuery,
onQueryChange = {
viewModel.onSearchQueryChanged(it)
},
placeholder = {
Text(stringResource(R.string.search_bar_placeholder))
},
)
}
) {
}
}
items(viewModel.items) {
val iconSize = 32.dp.toPixels()
val icon by remember(it.key) {
viewModel.getIcon(
it,
iconSize.toInt()
)
}.collectAsStateWithLifecycle(null)
val selected = it.key == value?.key
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
shape = MaterialTheme.shapes.small,
border = BorderStroke(
1.dp,
if (selected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.outline
),
color = if (selected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface,
onClick = { onValueChanged(it) }) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
ShapedLauncherIcon(
icon = { icon },
size = 32.dp,
)
Text(
text = it.label,
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp),
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (selected) {
.padding(bottom = 16.dp),
expanded = false,
onExpandedChange = {},
inputField = {
SearchBarDefaults.InputField(
leadingIcon = {
Icon(
painterResource(R.drawable.check_circle_24px_filled),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
painterResource(R.drawable.search_24px),
contentDescription = null
)
}
},
trailingIcon = {
if (viewModel.searchQuery.isNotEmpty()) {
IconButton(
modifier = Modifier.offset(16.dp),
onClick = {
viewModel.onSearchQueryChanged("")
}) {
Icon(painterResource(R.drawable.close_24px), null)
}
}
},
onSearch = {},
expanded = false,
onExpandedChange = {},
query = viewModel.searchQuery,
onQueryChange = {
viewModel.onSearchQueryChanged(it)
},
placeholder = {
Text(stringResource(R.string.search_bar_placeholder))
},
)
}
) {
}
}
itemsIndexed(viewModel.items) { i, it ->
val iconSize = 32.dp.toPixels()
val icon by remember(it.key) {
viewModel.getIcon(
it,
iconSize.toInt()
)
}.collectAsStateWithLifecycle(null)
val badge by remember(it.key) {
viewModel.getBadge(it)
}.collectAsStateWithLifecycle(null)
val selected = it.key == value?.key
val transition = updateTransition(selected)
val background by transition.animateColor {
if (it) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surfaceBright
}
val foreground by transition.animateColor {
if (it) MaterialTheme.colorScheme.onSecondaryContainer
else MaterialTheme.colorScheme.onSurface
}
val md = MaterialTheme.shapes.medium
val xs = MaterialTheme.shapes.extraSmall
val shape by animateShapeAsState(
if (selected || viewModel.items.size == 1) md
else when (i) {
0 -> md.copy(bottomStart = xs.bottomStart, bottomEnd = xs.bottomEnd)
viewModel.items.lastIndex -> md.copy(topStart = xs.topStart, topEnd = xs.topEnd)
else -> xs
}
)
Surface(
modifier = Modifier
.fillMaxWidth(),
shape = shape,
color = background,
contentColor = foreground,
onClick = { onValueChanged(it) }) {
Row(
modifier = Modifier
.padding(
start = 8.dp,
end = if (selected) 8.dp else 16.dp,
),
verticalAlignment = Alignment.CenterVertically
) {
ShapedLauncherIcon(
modifier = Modifier.padding(horizontal = 10.dp),
icon = { icon },
badge = { badge },
size = 36.dp,
)
Text(
text = it.label,
modifier = Modifier
.weight(1f)
.padding(start = 8.dp, top = 16.dp, bottom = 16.dp),
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (selected) {
Icon(
painterResource(R.drawable.check_24px),
contentDescription = null,
modifier = Modifier.padding(16.dp),
tint = foreground,
)
}
}
}
}
}
}
}

View File

@@ -5,6 +5,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.search.SavableSearchable
@@ -24,6 +26,7 @@ class SearchablePickerVM : ViewModel(), KoinComponent {
private val searchService: SearchService by inject()
private val iconService: IconService by inject()
private val badgeService: BadgeService by inject()
var searchQuery by mutableStateOf("")
@@ -54,4 +57,10 @@ class SearchablePickerVM : ViewModel(), KoinComponent {
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon?> {
return iconService.getIcon(searchable, size)
}
fun getBadge(searchable: SavableSearchable): Flow<Badge?> {
return badgeService.getBadge(searchable)
}
}

View File

@@ -41,6 +41,7 @@ import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.preferences.SearchBarColors
import de.mm20.launcher2.preferences.SearchBarStyle
import de.mm20.launcher2.preferences.SystemBarColors
import de.mm20.launcher2.preferences.WidgetScreenTarget
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.base.BaseActivity
import de.mm20.launcher2.ui.base.ProvideCompositionLocals
@@ -289,7 +290,7 @@ abstract class SharedLauncherActivity(
)
is GestureAction.Widgets ->
if (widgetsOnHomeScreen == true) {
if (widgetsOnHomeScreen == true && action.target == WidgetScreenTarget.Default) {
null
} else {
ScaffoldGesture(

View File

@@ -125,7 +125,7 @@ internal object ClockAndWidgetsHomeComponent : ScaffoldComponent() {
scope.launch { state.lock(hideSearchBar = true) }
editMode = it
},
parentId = WidgetScreenTarget.Default.scopeId.toString(),
parentId = WidgetScreenTarget.Default.id.toString(),
)
}
if (editMode) {

View File

@@ -1,16 +1,13 @@
package de.mm20.launcher2.ui.launcher.scaffold
import android.annotation.SuppressLint
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
@@ -38,8 +35,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
@@ -119,7 +114,7 @@ internal class WidgetsComponent(
scope.launch { state.lock(hideSearchBar = true) }
editMode = it
},
parentId = target.scopeId.toString(),
parentId = target.id.toString(),
)
}
if (editMode) {

View File

@@ -0,0 +1,428 @@
package de.mm20.launcher2.ui.settings.gestures
import android.content.res.Resources
import android.icu.text.ListFormatter
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.preferences.WidgetScreenTarget
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.SearchablePicker
import de.mm20.launcher2.ui.component.DismissableBottomSheet
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.ktx.animateShapeAsState
import de.mm20.launcher2.ui.ktx.toPixels
import kotlinx.coroutines.flow.combine
import org.koin.compose.koinInject
import kotlin.reflect.KClass
@Composable
internal fun GesturePreference(
title: String,
@DrawableRes icon: Int,
value: GestureAction?,
onValueChanged: (GestureAction, SavableSearchable?) -> Unit,
options: Set<KClass<out GestureAction>>,
shortcutOptions: List<SavableSearchable>,
widgetOptions: List<WidgetPageOption>,
) {
var showSheet by remember { mutableStateOf(false) }
val value = if (value is GestureAction.Widgets && widgetOptions.none { it.id == value.target }) {
GestureAction.NoAction
} else {
value
}
val iconService: IconService = koinInject()
val iconSize = 24.dp.toPixels().toInt()
val shortcut = remember(shortcutOptions, value) {
if (value !is GestureAction.Launch) {
null
} else {
shortcutOptions.find { value.key == it.key }
}
}
val icons by remember(shortcutOptions) {
combine(shortcutOptions.map {
iconService.getIcon(it, iconSize)
}) { it.toList() }
}.collectAsStateWithLifecycle(emptyList())
Preference(
title = title,
summary = getActionLabel(LocalResources.current, value, shortcutOptions),
icon = icon,
onClick = {
showSheet = true
},
)
DismissableBottomSheet(
expanded = showSheet,
onDismissRequest = { showSheet = false }
) {
val contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
)
var showShortcutPicker by remember { mutableStateOf(false) }
AnimatedContent(
showShortcutPicker
) { shortcutPicker ->
if (shortcutPicker) {
SearchablePicker(
value = shortcut,
onValueChanged = {
onValueChanged(GestureAction.Launch(it?.key), it)
showSheet = false
},
modifier = Modifier.fillMaxSize(),
contentPadding = contentPadding,
)
} else {
LazyColumn(
modifier = Modifier.fillMaxWidth(),
contentPadding = contentPadding,
) {
item {
PreferenceCategory {
GestureItem(
title = stringResource(R.string.gesture_action_none),
icon = R.drawable.close_24px,
selected = value is GestureAction.NoAction,
onClick = {
onValueChanged(GestureAction.NoAction, null)
showSheet = false
}
)
}
}
item {
PreferenceCategory(
title = stringResource(R.string.gesture_action_category_launcher)
) {
if (options.contains(GestureAction.Search::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_open_search),
icon = R.drawable.search_24px,
selected = value is GestureAction.Search,
onClick = {
onValueChanged(GestureAction.Search, null)
showSheet = false
}
)
}
if (options.contains(GestureAction.Feed::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_feed),
icon = R.drawable.news_24px,
selected = value is GestureAction.Feed,
onClick = {
onValueChanged(GestureAction.Feed, null)
showSheet = false
}
)
}
if (options.contains(GestureAction.Widgets::class)) {
for (widget in widgetOptions) {
GestureItem(
title = if (widget.isNewPage) {
stringResource(R.string.gesture_action_widgets_new)
} else {
getActionLabel(
LocalResources.current,
GestureAction.Widgets(widget.id),
shortcutOptions
)
},
icon = if (widget.isNewPage) R.drawable.widgets_add_24px else R.drawable.widgets_24px,
selected = value is GestureAction.Widgets && value.target == widget.id,
onClick = {
onValueChanged(GestureAction.Widgets(widget.id), null)
showSheet = false
},
summary = if (widget.isNewPage) {
null
} else if (widget.widgets.isEmpty()) {
stringResource(R.string.gesture_action_widgets_empty)
} else {
ListFormatter.getInstance().format(
widget.widgets.map { it.getLabel(LocalContext.current) }
)
}
)
}
}
}
}
item {
PreferenceCategory(
title = stringResource(R.string.gesture_action_category_system)
) {
if (options.contains(GestureAction.Notifications::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_notifications),
icon = R.drawable.notifications_24px,
selected = value is GestureAction.Notifications,
onClick = {
onValueChanged(GestureAction.Notifications, null)
showSheet = false
}
)
}
if (options.contains(GestureAction.QuickSettings::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_quick_settings),
icon = R.drawable.settings_24px,
selected = value is GestureAction.QuickSettings,
onClick = {
onValueChanged(GestureAction.QuickSettings, null)
showSheet = false
}
)
}
if (options.contains(GestureAction.ScreenLock::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_lock_screen),
icon = R.drawable.lock_24px,
selected = value is GestureAction.ScreenLock,
onClick = {
onValueChanged(GestureAction.ScreenLock, null)
showSheet = false
}
)
}
if (options.contains(GestureAction.PowerMenu::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_power_menu),
icon = R.drawable.power_settings_new_24px,
selected = value is GestureAction.PowerMenu,
onClick = {
onValueChanged(GestureAction.PowerMenu, null)
showSheet = false
}
)
}
if (options.contains(GestureAction.Recents::class)) {
GestureItem(
title = stringResource(R.string.gesture_action_recents),
icon = R.drawable.amp_stories_24px,
selected = value is GestureAction.Recents,
onClick = {
onValueChanged(GestureAction.Recents, null)
showSheet = false
}
)
}
}
}
item {
PreferenceCategory(
title = stringResource(R.string.gesture_action_category_apps)
) {
for ((i, shortcut) in shortcutOptions.withIndex()) {
GestureItem(
title = shortcut.labelOverride ?: shortcut.label,
icon = R.drawable.android_24px,
shortcutIcon = icons.getOrNull(i),
selected = value is GestureAction.Launch && value.key == shortcut.key,
onClick = {
onValueChanged(GestureAction.Launch(shortcut.key), shortcut)
showSheet = false
}
)
}
GestureItem(
title = stringResource(R.string.gesture_action_launch_select_app),
icon = R.drawable.action_key_24px,
selected = false,
onClick = {
showShortcutPicker = true
}
)
}
}
}
}
}
}
}
@Composable
private fun GestureItem(
title: String,
summary: String? = null,
@DrawableRes icon: Int,
shortcutIcon: LauncherIcon? = null,
selected: Boolean,
onClick: () -> Unit,
) {
val transition = updateTransition(selected)
val backgroundColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surfaceBright
}
val textColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.onSecondaryContainer
else MaterialTheme.colorScheme.onSurface
}
val iconColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.onSecondaryContainer
else MaterialTheme.colorScheme.primary
}
val shape by animateShapeAsState(
if (selected) MaterialTheme.shapes.medium else MaterialTheme.shapes.extraSmall
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clip(shape)
.clickable(onClick = onClick)
.background(backgroundColor)
.padding(
start = 8.dp,
end = if (selected) 8.dp else 16.dp,
)
) {
Box(
modifier = Modifier
.width(56.dp)
.padding(end = 8.dp),
contentAlignment = Alignment.Center
) {
if (shortcutIcon != null) {
ShapedLauncherIcon(
size = 36.dp,
icon = { shortcutIcon },
)
} else {
Icon(
painterResource(icon),
contentDescription = null,
tint = iconColor
)
}
}
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 16.dp),
) {
Text(
text = title,
color = textColor,
style = MaterialTheme.typography.titleMedium,
)
if (summary != null) {
Text(
text = summary,
color = textColor,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
if (selected) {
Box(
modifier = Modifier
.width(56.dp)
.padding(end = 8.dp),
contentAlignment = Alignment.Center
) {
Icon(
painterResource(R.drawable.check_24px),
contentDescription = null,
tint = iconColor
)
}
}
}
}
private fun getActionLabel(
resources: Resources,
action: GestureAction?,
shortcutOptions: List<SavableSearchable>
): String {
return when (action) {
GestureAction.Feed -> resources.getString(R.string.gesture_action_feed)
is GestureAction.Launch -> {
shortcutOptions.find { it.key == action.key }
?.let { it.labelOverride ?: it.label }
?: resources.getString(R.string.gesture_action_launch_app)
}
GestureAction.Notifications -> resources.getString(R.string.gesture_action_notifications)
GestureAction.PowerMenu -> resources.getString(R.string.gesture_action_power_menu)
GestureAction.QuickSettings -> resources.getString(R.string.gesture_action_quick_settings)
GestureAction.Recents -> resources.getString(R.string.gesture_action_recents)
GestureAction.ScreenLock -> resources.getString(R.string.gesture_action_lock_screen)
GestureAction.Search -> resources.getString(R.string.gesture_action_open_search)
is GestureAction.Widgets -> {
when (action.target) {
WidgetScreenTarget.Widgets1 -> resources.getString(R.string.gesture_action_widgets)
WidgetScreenTarget.Widgets2 -> resources.getString(
R.string.gesture_action_widgets_indexed,
2
)
WidgetScreenTarget.Widgets3 -> resources.getString(
R.string.gesture_action_widgets_indexed,
3
)
WidgetScreenTarget.Widgets4 -> resources.getString(
R.string.gesture_action_widgets_indexed,
4
)
}
}
else -> resources.getString(R.string.gesture_action_none)
}
}

View File

@@ -9,7 +9,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -20,6 +23,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -49,51 +53,33 @@ fun GestureSettingsScreen() {
val viewModel: GestureSettingsScreenVM = viewModel()
val hasPermission by viewModel.hasPermission.collectAsStateWithLifecycle(null)
val allowWidgetGesture by viewModel.allowWidgetGesture.collectAsStateWithLifecycle(null)
val widgetScreenCount by viewModel.widgetScreenCount.collectAsStateWithLifecycle(1)
val options = buildList {
add(stringResource(R.string.gesture_action_none) to GestureAction.NoAction)
add(stringResource(R.string.gesture_action_notifications) to GestureAction.Notifications)
add(stringResource(R.string.gesture_action_quick_settings) to GestureAction.QuickSettings)
if (isAtLeastApiLevel(28)) add(stringResource(R.string.gesture_action_lock_screen) to GestureAction.ScreenLock)
add(stringResource(R.string.gesture_action_recents) to GestureAction.Recents)
add(stringResource(R.string.gesture_action_power_menu) to GestureAction.PowerMenu)
add(stringResource(R.string.gesture_action_open_search) to GestureAction.Search)
if (allowWidgetGesture == true) {
// Dynamically add widget screen targets based on user configuration
WidgetScreenTarget.getAvailableTargets(widgetScreenCount)
.forEachIndexed { index, target ->
val label =
if (widgetScreenCount == 1) {
stringResource(R.string.gesture_action_widgets)
} else {
stringResource(R.string.gesture_action_widgets_indexed, index + 1)
}
add(label to GestureAction.Widgets(target))
}
}
add(stringResource(R.string.gesture_action_launch_app) to GestureAction.Launch(null))
val options = buildSet {
add(GestureAction.NoAction::class)
add(GestureAction.Notifications::class)
add(GestureAction.QuickSettings::class)
if (isAtLeastApiLevel(28)) add(GestureAction.ScreenLock::class)
add(GestureAction.Recents::class)
add(GestureAction.PowerMenu::class)
add(GestureAction.Search::class)
add(GestureAction.Widgets::class)
add(GestureAction.Launch::class)
}
val shortcutOptions by viewModel.shortcutOptions.collectAsStateWithLifecycle(emptyList())
val widgetOptions by viewModel.widgetOptions.collectAsStateWithLifecycle(emptyList())
val optionsWithFeed =
if (FeatureFlags.feed) {
options + (stringResource(R.string.gesture_action_feed) to GestureAction.Feed)
options + GestureAction.Feed::class
} else options
val context = LocalContext.current
PreferenceScreen(title = stringResource(R.string.preference_screen_gestures)) {
item {
val appIconSize = 32.dp.toPixels()
PreferenceCategory {
val swipeDown by viewModel.swipeDown.collectAsStateWithLifecycle(null)
val swipeDownApp by viewModel.swipeDownApp.collectAsState(null)
val swipeDownAppIcon by remember(swipeDownApp?.key) {
viewModel.getIcon(swipeDownApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeDown),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -103,19 +89,14 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_swipe_down),
icon = R.drawable.swipe_down_alt_24px,
value = swipeDown,
onValueChanged = { viewModel.setSwipeDown(it) },
onValueChanged = viewModel::setSwipeDown,
options = options,
app = swipeDownApp,
appIcon = swipeDownAppIcon,
onAppChanged = { viewModel.setSwipeDownApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
val swipeLeft by viewModel.swipeLeft.collectAsStateWithLifecycle(null)
val swipeLeftApp by viewModel.swipeLeftApp.collectAsState(null)
val swipeLeftAppIcon by remember(swipeLeftApp?.key) {
viewModel.getIcon(swipeLeftApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeLeft),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -125,19 +106,14 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_swipe_left),
icon = R.drawable.swipe_left_alt_24px,
value = swipeLeft,
onValueChanged = { viewModel.setSwipeLeft(it) },
onValueChanged = viewModel::setSwipeLeft,
options = options,
app = swipeLeftApp,
appIcon = swipeLeftAppIcon,
onAppChanged = { viewModel.setSwipeLeftApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
val swipeRight by viewModel.swipeRight.collectAsStateWithLifecycle(null)
val swipeRightApp by viewModel.swipeRightApp.collectAsState(null)
val swipeRightAppIcon by remember(swipeRightApp?.key) {
viewModel.getIcon(swipeRightApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeRight),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -147,19 +123,14 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_swipe_right),
icon = R.drawable.swipe_right_alt_24px,
value = swipeRight,
onValueChanged = { viewModel.setSwipeRight(it) },
onValueChanged = viewModel::setSwipeRight,
options = optionsWithFeed,
app = swipeRightApp,
appIcon = swipeRightAppIcon,
onAppChanged = { viewModel.setSwipeRightApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
val swipeUp by viewModel.swipeUp.collectAsStateWithLifecycle(null)
val swipeUpApp by viewModel.swipeUpApp.collectAsState(null)
val swipeUpAppIcon by remember(swipeUpApp?.key) {
viewModel.getIcon(swipeUpApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeUp),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -169,19 +140,14 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_swipe_up),
icon = R.drawable.swipe_up_alt_24px,
value = swipeUp,
onValueChanged = { viewModel.setSwipeUp(it) },
onValueChanged = viewModel::setSwipeUp,
options = options,
app = swipeUpApp,
appIcon = swipeUpAppIcon,
onAppChanged = { viewModel.setSwipeUpApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
val doubleTap by viewModel.doubleTap.collectAsStateWithLifecycle(null)
val doubleTapApp by viewModel.doubleTapApp.collectAsState(null)
val doubleTapAppIcon by remember(doubleTapApp?.key) {
viewModel.getIcon(doubleTapApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(doubleTap),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -191,19 +157,14 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_double_tap),
icon = R.drawable.adjust_24px,
value = doubleTap,
onValueChanged = { viewModel.setDoubleTap(it) },
onValueChanged = viewModel::setDoubleTap,
options = options,
app = doubleTapApp,
appIcon = doubleTapAppIcon,
onAppChanged = { viewModel.setDoubleTapApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
val longPress by viewModel.longPress.collectAsStateWithLifecycle(null)
val longPressApp by viewModel.longPressApp.collectAsState(null)
val longPressAppIcon by remember(longPressApp?.key) {
viewModel.getIcon(longPressApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(longPress),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -213,18 +174,13 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_long_press),
icon = R.drawable.circle_24px,
value = longPress,
onValueChanged = { viewModel.setLongPress(it) },
onValueChanged = viewModel::setLongPress,
options = options,
app = longPressApp,
appIcon = longPressAppIcon,
onAppChanged = { viewModel.setLongPressApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
val homeButton by viewModel.homeButton.collectAsStateWithLifecycle(null)
val homeButtonApp by viewModel.homeButtonApp.collectAsState(null)
val homeButtonAppIcon by remember(homeButtonApp?.key) {
viewModel.getIcon(homeButtonApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(homeButton),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
@@ -234,11 +190,10 @@ fun GestureSettingsScreen() {
title = stringResource(R.string.preference_gesture_home_button),
icon = R.drawable.home_24px,
value = homeButton,
onValueChanged = { viewModel.setHomeButton(it) },
onValueChanged = viewModel::setHomeButton,
options = options,
app = homeButtonApp,
appIcon = homeButtonAppIcon,
onAppChanged = { viewModel.setHomeButtonApp(it) },
shortcutOptions = shortcutOptions,
widgetOptions = widgetOptions,
)
}
}
@@ -257,75 +212,88 @@ fun requiresAccessibilityService(action: GestureAction?): Boolean {
}
}
@Composable
fun GesturePreference(
title: String,
@DrawableRes icon: Int,
value: GestureAction?,
onValueChanged: (GestureAction) -> Unit,
options: List<Pair<String, GestureAction>>,
app: SavableSearchable?,
appIcon: LauncherIcon?,
onAppChanged: (SavableSearchable?) -> Unit,
) {
var showAppPicker by remember { mutableStateOf(false) }
Row(
verticalAlignment = (Alignment.CenterVertically),
) {
Box(
modifier = Modifier.weight(1f),
) {
ListPreference(
title = title,
icon = icon,
items = options,
value = value,
summary = options.find { option ->
when {
value is GestureAction.Widgets && option.second is GestureAction.Widgets -> {
val valueTarget = value.target
val optionTarget = (option.second as GestureAction.Widgets).target
valueTarget == optionTarget
}
else -> value?.javaClass == option.second.javaClass
}
}?.first ?: stringResource(R.string.gesture_action_none),
onValueChanged = { if (it != null) onValueChanged(it) },
)
}
if (value is GestureAction.Launch) {
Box(
modifier = Modifier
.height(36.dp)
.width(1.dp)
.alpha(0.38f)
.background(LocalContentColor.current),
)
Box(
modifier = Modifier
.clickable { showAppPicker = true }
.padding(12.dp),
) {
ShapedLauncherIcon(size = 32.dp, icon = { appIcon })
}
}
}
if (value is GestureAction.Launch) {
SearchablePicker(
expanded = (showAppPicker || app == null),
onDismissRequest = {
showAppPicker = false
if (app == null) onValueChanged(GestureAction.NoAction)
},
value = app,
onValueChanged = {
showAppPicker = false
onAppChanged(it)
},
)
}
}
//@Composable
//fun GesturePreference(
// title: String,
// @DrawableRes icon: Int,
// value: GestureAction?,
// onValueChanged: (GestureAction) -> Unit,
// options: List<Pair<String, GestureAction>>,
// app: SavableSearchable?,
// appIcon: LauncherIcon?,
// onAppChanged: (SavableSearchable?) -> Unit,
//) {
// var showAppPicker by remember { mutableStateOf(false) }
// Row(
// verticalAlignment = (Alignment.CenterVertically),
// ) {
// Box(
// modifier = Modifier.weight(1f),
// ) {
// ListPreference(
// title = title,
// icon = icon,
// items = options,
// value = value,
// summary = options.find { option ->
// when {
// value is GestureAction.Widgets && option.second is GestureAction.Widgets -> {
// val valueTarget = value.target
// val optionTarget = (option.second as GestureAction.Widgets).target
// valueTarget == optionTarget
// }
//
// else -> value?.javaClass == option.second.javaClass
// }
// }?.first ?: stringResource(R.string.gesture_action_none),
// onValueChanged = { if (it != null) onValueChanged(it) },
// )
// }
//
// if (value is GestureAction.Launch) {
// Box(
// modifier = Modifier
// .height(36.dp)
// .width(1.dp)
// .background(MaterialTheme.colorScheme.outlineVariant),
// )
// Box(
// modifier = Modifier
// .clickable { showAppPicker = true }
// .padding(12.dp),
// ) {
// ShapedLauncherIcon(size = 32.dp, icon = { appIcon })
// }
// }
// if (value is GestureAction.Widgets) {
// Box(
// modifier = Modifier
// .height(36.dp)
// .width(1.dp)
// .background(MaterialTheme.colorScheme.outlineVariant),
// )
// IconButton(
// modifier = Modifier.padding(4.dp),
// onClick = {}
// ) {
// Icon(painterResource(R.drawable.tune_24px), "")
// }
// }
// }
//
// if (value is GestureAction.Launch) {
// SearchablePicker(
// expanded = (showAppPicker || app == null),
// onDismissRequest = {
// showAppPicker = false
// if (app == null) onValueChanged(GestureAction.NoAction)
// },
// value = app,
// onValueChanged = {
// showAppPicker = false
// onAppChanged(it)
// },
// )
// }
//
//}

View File

@@ -7,39 +7,36 @@ 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.BaseLayout
import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.preferences.WidgetScreenTarget
import de.mm20.launcher2.preferences.ui.GestureSettings
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.searchable.SavableSearchableRepository
import de.mm20.launcher2.widgets.Widget
import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
internal class GestureSettingsScreenVM : ViewModel(), KoinComponent {
private val gestureSettings: GestureSettings by inject()
private val uiSettings: UiSettings by inject()
private val permissionsManager: PermissionsManager by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private val iconService: IconService by inject()
private val widgetRepository: WidgetRepository by inject()
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val allowWidgetGesture = uiSettings.homeScreenWidgets.map { it == false }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val widgetScreenCount = uiSettings.widgetScreenCount
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
val swipeDown = gestureSettings.swipeDown
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val swipeLeft = gestureSettings.swipeLeft
@@ -55,131 +52,101 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
val homeButton = gestureSettings.homeButton
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setSwipeDown(action: GestureAction) {
fun setSwipeDown(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setSwipeDown(action)
}
fun setSwipeLeft(action: GestureAction) {
fun setSwipeLeft(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setSwipeLeft(action)
}
fun setSwipeRight(action: GestureAction) {
fun setSwipeRight(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setSwipeRight(action)
}
fun setSwipeUp(action: GestureAction) {
fun setSwipeUp(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setSwipeUp(action)
}
fun setDoubleTap(action: GestureAction) {
fun setDoubleTap(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setDoubleTap(action)
}
fun setLongPress(action: GestureAction) {
fun setLongPress(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setLongPress(action)
}
fun setHomeButton(action: GestureAction) {
fun setHomeButton(action: GestureAction, searchable: SavableSearchable?) {
saveShortcut(searchable)
gestureSettings.setHomeButton(action)
}
val swipeLeftApp: Flow<SavableSearchable?> = swipeLeft
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeLeftApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setSwipeLeft(GestureAction.Launch(searchable.key))
private fun saveShortcut(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) }
}
val swipeRightApp: Flow<SavableSearchable?> = swipeRight
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeRightApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setSwipeRight(GestureAction.Launch(searchable.key))
val shortcutOptions: Flow<List<SavableSearchable>> = gestureSettings.flatMapLatest {
val keys = listOfNotNull(
(it.swipeUp as? GestureAction.Launch)?.key,
(it.swipeLeft as? GestureAction.Launch)?.key,
(it.swipeRight as? GestureAction.Launch)?.key,
(it.swipeDown as? GestureAction.Launch)?.key,
(it.doubleTap as? GestureAction.Launch)?.key,
(it.homeButton as? GestureAction.Launch)?.key,
(it.longPress as? GestureAction.Launch)?.key,
)
searchableRepository.getByKeys(keys)
}
val swipeDownApp: Flow<SavableSearchable?> = swipeDown
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
val widgetOptions: Flow<List<WidgetPageOption>> =
combine(uiSettings.homeScreenWidgets, gestureSettings) { home, gestures ->
val options = mutableListOf<WidgetPageOption>()
val usedTargets = setOfNotNull(
(gestures.swipeUp as? GestureAction.Widgets)?.target,
(gestures.swipeRight as? GestureAction.Widgets)?.target,
(gestures.swipeLeft as? GestureAction.Widgets)?.target,
(gestures.swipeDown as? GestureAction.Widgets)?.target,
(gestures.doubleTap as? GestureAction.Widgets)?.target,
(gestures.longPress as? GestureAction.Widgets)?.target,
(gestures.homeButton as? GestureAction.Widgets)?.target,
)
// We want to provide at most one "new screen" option
var newScreenOptionAdded = false
for (target in WidgetScreenTarget.entries) {
// Never offer the default screen as an option when widgets on home is enabled
if (home && target == WidgetScreenTarget.Default) continue
val widgets = widgetRepository.get(
parent = target.id,
).first()
if (widgets.isNotEmpty() || usedTargets.contains(target)) {
options += WidgetPageOption(
widgets = widgets,
id = target,
isNewPage = false,
)
} else if (!newScreenOptionAdded) {
options += WidgetPageOption(
id = target,
widgets = emptyList(),
isNewPage = true,
)
newScreenOptionAdded = true
}
}
options.sortedWith { a, b ->
if (a.isNewPage) return@sortedWith 1
if (b.isNewPage) return@sortedWith -1
a.id.compareTo(b.id)
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeDownApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setSwipeDown(GestureAction.Launch(searchable.key))
}
val swipeUpApp: Flow<SavableSearchable?> = swipeUp
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setSwipeUpApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setSwipeUp(GestureAction.Launch(searchable.key))
}
val longPressApp: Flow<SavableSearchable?> = longPress
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setLongPressApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setLongPress(GestureAction.Launch(searchable.key))
}
val doubleTapApp: Flow<SavableSearchable?> = doubleTap
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setDoubleTapApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setDoubleTap(GestureAction.Launch(searchable.key))
}
val homeButtonApp: Flow<SavableSearchable?> = homeButton
.flatMapLatest {
if (it !is GestureAction.Launch || it.key == null) flowOf(null)
else searchableRepository.getByKeys(listOf(it.key!!)).map {
it.firstOrNull()
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
fun setHomeButtonApp(searchable: SavableSearchable?) {
searchable?.let { searchableRepository.insert(it) } ?: return
setHomeButton(GestureAction.Launch(searchable.key))
}
fun requestPermission(context: AppCompatActivity) {
@@ -191,3 +158,9 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
return iconService.getIcon(searchable, size)
}
}
internal data class WidgetPageOption(
val id: WidgetScreenTarget,
val widgets: List<Widget>,
val isNewPage: Boolean,
)

View File

@@ -135,28 +135,6 @@ fun HomescreenSettingsScreen() {
onValueChanged = {
viewModel.setWidgetsOnHomeScreen(it)
})
AnimatedVisibility(widgetsOnHomeScreen == false) {
val widgetScreenCount by viewModel.widgetScreenCount.collectAsStateWithLifecycle(1)
Column {
SliderPreference(
title = stringResource(R.string.preference_widget_screen_count),
value = widgetScreenCount,
min = 1,
max = 4,
onValueChanged = {
viewModel.setWidgetScreenCount(it)
}
)
Text(
text = stringResource(R.string.preference_widget_screen_count_info),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
SwitchPreference(
title = stringResource(id = R.string.preference_edit_button),
summary = stringResource(id = R.string.preference_widgets_edit_button_summary),

View File

@@ -166,13 +166,6 @@ class HomescreenSettingsScreenVM(
uiSettings.setHomeScreenWidgets(widgetsOnHomeScreen)
}
val widgetScreenCount = uiSettings.widgetScreenCount
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
fun setWidgetScreenCount(count: Int) {
uiSettings.setWidgetScreenCount(count)
}
companion object : KoinComponent {
val Factory = viewModelFactory {
initializer {