Skip to content

Commit cdcf40a

Browse files
refactor: migrate settings pages to M3E spliced column group style
- Add SplicedColumnGroup component with animated corner radius morphing - Convert main settings hub to grouped card layout (SplicedColumnGroup) - Convert all 8 sub-settings content pages to SplicedColumnGroup style - Add bare parameter to ThemeModeSelector/ThemeColorPicker for inline use - Fix double horizontal padding in sub-settings screens for consistent width
1 parent 5239991 commit cdcf40a

20 files changed

Lines changed: 2782 additions & 2424 deletions
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package me.bmax.apatch.ui.component
2+
3+
import android.os.Build
4+
import androidx.compose.animation.AnimatedVisibility
5+
import androidx.compose.animation.core.Spring
6+
import androidx.compose.animation.core.animateDpAsState
7+
import androidx.compose.animation.core.spring
8+
import androidx.compose.animation.expandVertically
9+
import androidx.compose.animation.fadeIn
10+
import androidx.compose.animation.fadeOut
11+
import androidx.compose.animation.shrinkVertically
12+
import androidx.compose.foundation.background
13+
import androidx.compose.foundation.layout.Arrangement
14+
import androidx.compose.foundation.layout.Column
15+
import androidx.compose.foundation.layout.padding
16+
import androidx.compose.foundation.shape.RoundedCornerShape
17+
import androidx.compose.material3.MaterialTheme
18+
import androidx.compose.material3.Text
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.key
21+
import androidx.compose.ui.Alignment
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.graphics.graphicsLayer
24+
import androidx.compose.ui.unit.dp
25+
import androidx.compose.ui.zIndex
26+
27+
private val CornerRadius = 16.dp
28+
private val ConnectionRadius = 5.dp
29+
30+
data class SplicedItemData(
31+
val key: Any?,
32+
val visible: Boolean,
33+
val content: @Composable () -> Unit,
34+
)
35+
36+
class SplicedGroupScope {
37+
val items = mutableListOf<SplicedItemData>()
38+
39+
fun item(key: Any? = null, visible: Boolean = true, content: @Composable () -> Unit) {
40+
items.add(SplicedItemData(key ?: items.size, visible, content))
41+
}
42+
}
43+
44+
@Composable
45+
fun SplicedColumnGroup(
46+
modifier: Modifier = Modifier,
47+
title: String = "",
48+
flat: Boolean = false,
49+
content: SplicedGroupScope.() -> Unit,
50+
) {
51+
val scope = SplicedGroupScope().apply(content)
52+
val allItems = scope.items
53+
54+
if (allItems.isEmpty()) return
55+
56+
Column(modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
57+
if (title.isNotEmpty()) {
58+
Text(
59+
text = title,
60+
style = MaterialTheme.typography.titleSmall,
61+
color = MaterialTheme.colorScheme.secondary,
62+
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold,
63+
modifier = Modifier.padding(start = 8.dp, bottom = 8.dp),
64+
)
65+
}
66+
67+
Column(verticalArrangement = Arrangement.Top) {
68+
val firstVisibleIndex = allItems.indexOfFirst { it.visible }
69+
val lastVisibleIndex = allItems.indexOfLast { it.visible }
70+
val sharedStiffness = Spring.StiffnessMediumLow
71+
val isAtLeastTiramisu = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
72+
73+
allItems.forEachIndexed { index, itemData ->
74+
key(itemData.key) {
75+
val zIndex = if (itemData.visible) 0f else 1f
76+
77+
AnimatedVisibility(
78+
visible = itemData.visible,
79+
modifier = Modifier.zIndex(zIndex),
80+
enter = expandVertically(
81+
animationSpec = spring(stiffness = sharedStiffness),
82+
expandFrom = Alignment.Top,
83+
) + fadeIn(animationSpec = spring(stiffness = sharedStiffness)),
84+
exit = shrinkVertically(
85+
animationSpec = spring(stiffness = sharedStiffness),
86+
shrinkTowards = Alignment.Top,
87+
) + fadeOut(animationSpec = spring(stiffness = sharedStiffness)),
88+
) {
89+
val isFirst = index == firstVisibleIndex
90+
val isLast = index == lastVisibleIndex
91+
92+
val targetTopRadius = if (isFirst) CornerRadius else ConnectionRadius
93+
val targetBottomRadius = if (isLast) CornerRadius else ConnectionRadius
94+
95+
val currentTopRadius = if (isAtLeastTiramisu) {
96+
animateDpAsState(
97+
targetValue = targetTopRadius,
98+
animationSpec = spring(stiffness = sharedStiffness),
99+
label = "TopCornerRadius",
100+
).value
101+
} else {
102+
targetTopRadius
103+
}
104+
105+
val currentBottomRadius = if (isAtLeastTiramisu) {
106+
animateDpAsState(
107+
targetValue = targetBottomRadius,
108+
animationSpec = spring(stiffness = sharedStiffness),
109+
label = "BottomCornerRadius",
110+
).value
111+
} else {
112+
targetBottomRadius
113+
}
114+
115+
val shape = RoundedCornerShape(
116+
topStart = currentTopRadius,
117+
topEnd = currentTopRadius,
118+
bottomStart = currentBottomRadius,
119+
bottomEnd = currentBottomRadius,
120+
)
121+
122+
val targetTopPadding = if (isFirst) 0.dp else 2.dp
123+
val currentTopPadding = if (isAtLeastTiramisu) {
124+
animateDpAsState(
125+
targetValue = targetTopPadding,
126+
animationSpec = spring(stiffness = sharedStiffness),
127+
label = "TopPadding",
128+
).value
129+
} else {
130+
targetTopPadding
131+
}
132+
133+
val containerColor = if (flat) {
134+
MaterialTheme.colorScheme.surfaceContainer
135+
} else {
136+
MaterialTheme.colorScheme.surfaceContainer
137+
}
138+
139+
Column(
140+
modifier = Modifier
141+
.padding(top = currentTopPadding)
142+
.graphicsLayer {
143+
this.shape = shape
144+
this.clip = true
145+
}
146+
.background(containerColor),
147+
) {
148+
itemData.content()
149+
}
150+
}
151+
}
152+
}
153+
}
154+
}
155+
}

app/src/main/java/me/bmax/apatch/ui/component/ThemeColorPicker.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ fun ThemeColorPicker(
8484
isDynamicColorSupported: Boolean = false,
8585
isDynamicColorEnabled: Boolean = false,
8686
onDynamicColorSelected: () -> Unit = {},
87+
bare: Boolean = false,
8788
) {
88-
ExpressiveCard(modifier = modifier, flat = flat) {
89+
val content: @Composable () -> Unit = {
8990
Column(
9091
modifier = Modifier.padding(16.dp),
9192
) {
@@ -128,6 +129,14 @@ fun ThemeColorPicker(
128129
}
129130
}
130131
}
132+
133+
if (bare) {
134+
content()
135+
} else {
136+
ExpressiveCard(modifier = modifier, flat = flat) {
137+
content()
138+
}
139+
}
131140
}
132141

133142
@OptIn(ExperimentalMaterial3ExpressiveApi::class)

app/src/main/java/me/bmax/apatch/ui/component/ThemeModeSelector.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ fun ThemeModeSelector(
4040
onModeSelected: (ThemeMode) -> Unit,
4141
modifier: Modifier = Modifier,
4242
flat: Boolean = false,
43+
bare: Boolean = false,
4344
) {
44-
ExpressiveCard(modifier = modifier, flat = flat) {
45+
val content: @Composable () -> Unit = {
4546
Row(
4647
modifier = Modifier
4748
.fillMaxWidth()
@@ -77,6 +78,14 @@ fun ThemeModeSelector(
7778
)
7879
}
7980
}
81+
82+
if (bare) {
83+
content()
84+
} else {
85+
ExpressiveCard(modifier = modifier, flat = flat) {
86+
content()
87+
}
88+
}
8089
}
8190

8291
@Composable

0 commit comments

Comments
 (0)