Adjust search action settings screen design

This commit is contained in:
MM20
2026-02-01 18:00:51 +01:00
parent 54e35214b4
commit 06437df84d
3 changed files with 112 additions and 71 deletions

View File

@@ -88,21 +88,7 @@ fun PreferenceScreen(
val colorScheme = MaterialTheme.colorScheme
val touchSlop = LocalViewConfiguration.current.touchSlop
var fabVisible by remember { mutableStateOf(true) }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset {
if (consumed.y < -touchSlop) fabVisible = false
else if (consumed.y > touchSlop) fabVisible = true
return super.onPostScroll(consumed, available, source)
}
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()

View File

@@ -11,7 +11,7 @@ import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.RectangleShape
@@ -19,30 +19,6 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
/**
* Animates the corners of a shape between its original value and 0.
* For each parameter set to true, the corresponding corner will animate to the original value.
* Otherwise, it will animate to 0.
*/
@Composable
fun CornerBasedShape.animateCorners(
topStart: Boolean,
topEnd: Boolean,
bottomEnd: Boolean,
bottomStart: Boolean,
): CornerBasedShape {
val value by animateShapeAsState(
copy(
topStart = if (topStart) this.topStart else CornerSize(0f),
topEnd = if (topEnd) this.topEnd else CornerSize(0f),
bottomEnd = if (bottomEnd) this.bottomEnd else CornerSize(0f),
bottomStart = if (bottomStart) this.bottomStart else CornerSize(0f)
)
)
return value
}
/**
* Animate between two shapes.
* Limitations:
@@ -52,12 +28,19 @@ fun CornerBasedShape.animateCorners(
*/
@Composable
fun animateShapeAsState(
shape: CornerBasedShape,
shape: Shape,
animationSpec: AnimationSpec<CornerBasedShape> = remember { spring() },
visibilityThreshold: CornerBasedShape? = null,
label: String = "ValueAnimation",
finishedListener: ((CornerBasedShape) -> Unit)? = null
): State<CornerBasedShape> {
finishedListener: ((Shape) -> Unit)? = null
): State<Shape> {
if (shape !is CornerBasedShape) {
return remember(shape) {
mutableStateOf(shape)
}
}
val density = LocalDensity.current
val converter = remember(shape.javaClass, density) {
if (shape is CutCornerShape) CutCornerShapeConverter(density)

View File

@@ -6,19 +6,22 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenuGroup
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.DropdownMenuPopup
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -26,6 +29,7 @@ import androidx.compose.material3.MenuDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -34,8 +38,11 @@ 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.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -54,6 +61,7 @@ import de.mm20.launcher2.ui.component.dragndrop.rememberLazyDragAndDropListState
import de.mm20.launcher2.ui.component.getSearchActionIconVector
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.ktx.animateShapeAsState
import de.mm20.launcher2.ui.locals.LocalBackStack
import kotlinx.serialization.Serializable
@@ -84,6 +92,8 @@ fun SearchActionsSettingsScreen() {
val searchActions by viewModel.searchActions.collectAsState()
val disabledActions by viewModel.disabledActions.collectAsState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold(
topBar = {
CenterAlignedTopAppBar(
@@ -95,6 +105,10 @@ fun SearchActionsSettingsScreen() {
maxLines = 1
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
),
navigationIcon = {
IconButton(onClick = {
if (backStack.size <= 1) {
@@ -110,12 +124,6 @@ fun SearchActionsSettingsScreen() {
}
},
actions = {
IconButton(onClick = { viewModel.createAction() }) {
Icon(
painterResource(R.drawable.add_24px),
contentDescription = null
)
}
IconButton(onClick = {
CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(
@@ -134,9 +142,12 @@ fun SearchActionsSettingsScreen() {
contentDescription = "Help"
)
}
}
},
scrollBehavior = scrollBehavior,
)
}) {
},
containerColor = MaterialTheme.colorScheme.surfaceContainer
) {
LazyDragAndDropColumn(
state = listState,
@@ -144,6 +155,8 @@ fun SearchActionsSettingsScreen() {
contentPadding = it,
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
item(
key = "disabled-info"
@@ -168,19 +181,28 @@ fun SearchActionsSettingsScreen() {
)
}
}
items(
itemsIndexed(
items = searchActions,
key = { it.key }
) { item ->
key = { _, it -> it.key }
) { index, item ->
DraggableItem(
state = listState,
key = item.key
) {
val shape = getShape(index, searchActions.size)
val draggedShape = MaterialTheme.shapes.medium
val elevation by animateDpAsState(if (it) 4.dp else 0.dp)
Surface(
shadowElevation = elevation,
tonalElevation = elevation,
modifier = Modifier.zIndex(if (it) 1f else 0f)
modifier = Modifier
.padding(horizontal = 12.dp)
.zIndex(if (it) 1f else 0f),
shape = animateShapeAsState(
if (it) draggedShape else shape
).value
) {
if (item is CustomizableSearchActionBuilder) {
Preference(
@@ -263,24 +285,48 @@ fun SearchActionsSettingsScreen() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(0.5.dp)
.background(
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
)
.height(8.dp)
)
}
items(
itemsIndexed(
items = disabledActions,
key = { "disabled-${it.key}" }
) { item ->
SwitchPreference(
icon = getSearchActionIconVector(item.icon),
title = item.label,
value = false,
onValueChanged = {
viewModel.addAction(item)
}
)
key = { _, it -> "disabled-${it.key}" }
) { index, item ->
val shape = getShape(index, disabledActions.size)
Box(
modifier = Modifier
.padding(horizontal = 12.dp)
.clip(shape)
) {
SwitchPreference(
icon = getSearchActionIconVector(item.icon),
title = item.label,
value = false,
onValueChanged = {
viewModel.addAction(item)
}
)
}
}
item(key = "disabled-button") {
FilledTonalButton(
modifier = Modifier
.padding(top = 10.dp, start = 12.dp, end = 12.dp)
.navigationBarsPadding(),
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
onClick = {
viewModel.createAction()
}) {
Icon(
painterResource(R.drawable.add_20px),
null,
modifier = Modifier
.padding(end = ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.create_search_action_title))
}
}
}
}
@@ -308,4 +354,30 @@ fun SearchActionsSettingsScreen() {
viewModel.dismissDialogs()
}
)
}
@Composable
private fun getShape(index: Int, total: Int): Shape {
if (total == 1) {
return MaterialTheme.shapes.medium
}
if (total > 1 && index > 0 && index < total - 1) {
return MaterialTheme.shapes.extraSmall
}
val xs = MaterialTheme.shapes.extraSmall
val md = MaterialTheme.shapes.medium
if (index == 0) {
return xs.copy(
topStart = md.topStart,
topEnd = md.topEnd
)
} else {
return xs.copy(
bottomStart = md.bottomStart,
bottomEnd = md.bottomEnd
)
}
}