Skip to content

Commit f6efc4d

Browse files
committed
refactor: improve UI/UX with Material You components
1 parent a61c0a7 commit f6efc4d

18 files changed

Lines changed: 272 additions & 180 deletions

File tree

app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
1010
import androidx.compose.foundation.layout.fillMaxWidth
1111
import androidx.compose.foundation.layout.padding
1212
import androidx.compose.foundation.layout.size
13+
import androidx.compose.foundation.shape.RoundedCornerShape
1314
import androidx.compose.material3.Card
1415
import androidx.compose.material3.CardDefaults
1516
import androidx.compose.material3.Icon
@@ -53,6 +54,7 @@ internal fun ErrorContainer(message: String, onAnimationEnd: () -> Unit) {
5354

5455
Card(
5556
modifier = Modifier.fillMaxWidth(),
57+
shape = RoundedCornerShape(24.dp),
5658
colors = CardDefaults.cardColors(
5759
containerColor = MaterialTheme.colorScheme.errorContainer,
5860
contentColor = MaterialTheme.colorScheme.onErrorContainer
@@ -75,8 +77,8 @@ internal fun ErrorContainer(message: String, onAnimationEnd: () -> Unit) {
7577
) {
7678
Icon(
7779
modifier = Modifier.size(40.dp),
78-
painter = painterResource(R.drawable.ic_close),
79-
contentDescription = null,
80+
painter = painterResource(R.drawable.ic_warning),
81+
contentDescription = stringResource(R.string.error),
8082
tint = MaterialTheme.colorScheme.onErrorContainer
8183
)
8284
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class MainActivity : ComponentActivity() {
2929
registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> }
3030

3131
override fun onCreate(savedInstanceState: Bundle?) {
32-
super.onCreate(savedInstanceState)
3332
updateEdgeToEdgeConfiguration(prefs.forceDark)
33+
super.onCreate(savedInstanceState)
3434

3535
setContent {
3636
val themeState by mainViewModel.themeState.collectAsStateWithLifecycle()

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,18 @@ import androidx.compose.animation.slideInVertically
1313
import androidx.compose.animation.slideOutHorizontally
1414
import androidx.compose.animation.slideOutVertically
1515
import androidx.compose.foundation.layout.Row
16+
import androidx.compose.foundation.layout.WindowInsets
1617
import androidx.compose.foundation.layout.padding
1718
import androidx.compose.material3.ExperimentalMaterial3Api
1819
import androidx.compose.material3.Scaffold
1920
import androidx.compose.material3.SnackbarDuration
2021
import androidx.compose.material3.SnackbarHost
2122
import androidx.compose.material3.SnackbarHostState
22-
import androidx.compose.material3.TopAppBarDefaults
23-
import androidx.compose.material3.rememberTopAppBarState
2423
import androidx.compose.runtime.Composable
2524
import androidx.compose.runtime.LaunchedEffect
2625
import androidx.compose.runtime.getValue
2726
import androidx.compose.runtime.remember
2827
import androidx.compose.ui.Modifier
29-
import androidx.compose.ui.input.nestedscroll.nestedScroll
3028
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
3129
import androidx.compose.ui.tooling.preview.PreviewLightDark
3230
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -73,14 +71,9 @@ private fun MainScreenContent(
7371
) {
7472
val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
7573
val isLandscape = isLandscape()
76-
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
7774

7875
Scaffold(
79-
modifier = if (state.useTopBarScrollBehavior) {
80-
Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
81-
} else {
82-
Modifier
83-
},
76+
contentWindowInsets = WindowInsets(0, 0, 0, 0),
8477
topBar = {
8578
AnimatedVisibility(
8679
visible = state.showTopBar,

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.androidvip.sysctlgui.ui.main
22

33
import androidx.compose.material3.SnackbarResult
4+
import androidx.compose.runtime.Immutable
45
import com.androidvip.sysctlgui.data.repository.CONTRAST_LEVEL_NORMAL
56

7+
@Immutable
68
data class MainViewState(
79
val topBarTitle: String = "Sysctl GUI",
810
val showTopBar: Boolean = true,
911
val showNavBar: Boolean = true,
10-
val useTopBarScrollBehavior: Boolean = true,
1112
val showBackButton: Boolean = false,
1213
val showSearchAction: Boolean = true
1314
)

app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.androidvip.sysctlgui.ui.params.edit
22

33
import androidx.compose.animation.AnimatedContent
44
import androidx.compose.animation.animateColorAsState
5+
import androidx.compose.animation.core.AnimationSpec
56
import androidx.compose.animation.core.Spring
67
import androidx.compose.animation.core.animateDpAsState
78
import androidx.compose.animation.core.spring
@@ -11,10 +12,15 @@ import androidx.compose.animation.fadeOut
1112
import androidx.compose.animation.scaleIn
1213
import androidx.compose.animation.scaleOut
1314
import androidx.compose.animation.togetherWith
15+
import androidx.compose.foundation.interaction.MutableInteractionSource
16+
import androidx.compose.foundation.interaction.collectIsPressedAsState
1417
import androidx.compose.foundation.layout.Arrangement
18+
import androidx.compose.foundation.layout.Box
1519
import androidx.compose.foundation.layout.Row
20+
import androidx.compose.foundation.layout.fillMaxWidth
1621
import androidx.compose.foundation.layout.padding
17-
import androidx.compose.foundation.shape.CircleShape
22+
import androidx.compose.foundation.layout.width
23+
import androidx.compose.foundation.shape.RoundedCornerShape
1824
import androidx.compose.material3.FloatingActionButton
1925
import androidx.compose.material3.FloatingActionButtonDefaults
2026
import androidx.compose.material3.Icon
@@ -24,49 +30,81 @@ import androidx.compose.runtime.Composable
2430
import androidx.compose.runtime.getValue
2531
import androidx.compose.runtime.mutableStateOf
2632
import androidx.compose.runtime.remember
33+
import androidx.compose.runtime.rememberCoroutineScope
2734
import androidx.compose.runtime.setValue
35+
import androidx.compose.ui.Alignment
2836
import androidx.compose.ui.Modifier
2937
import androidx.compose.ui.graphics.painter.Painter
3038
import androidx.compose.ui.res.painterResource
3139
import androidx.compose.ui.res.stringResource
3240
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
3341
import androidx.compose.ui.tooling.preview.PreviewLightDark
42+
import androidx.compose.ui.unit.Dp
3443
import androidx.compose.ui.unit.dp
3544
import com.androidvip.sysctlgui.R
3645
import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme
46+
import kotlinx.coroutines.delay
47+
import kotlinx.coroutines.launch
3748

3849
@Composable
3950
internal fun ActionToggleButton(
4051
modifier: Modifier = Modifier,
52+
size: Dp = 56.dp,
4153
isActive: Boolean,
4254
iconOnActive: Painter,
4355
iconOnInactive: Painter,
4456
contentDescription: String? = null,
4557
onToggle: (Boolean) -> Unit,
4658
) {
59+
var internalPressed by remember { mutableStateOf(false) }
60+
val interactionSource = remember { MutableInteractionSource() }
61+
val isPressed by interactionSource.collectIsPressedAsState()
62+
val radius = if (isPressed || isActive) 16.dp else size / 2f
63+
val cornerRadius = animateDpAsState(targetValue = radius)
64+
val coroutineScope = rememberCoroutineScope()
4765
val containerColor by animateColorAsState(
48-
targetValue = if (isActive) {
49-
MaterialTheme.colorScheme.secondary
50-
} else {
51-
MaterialTheme.colorScheme.background
66+
targetValue = when {
67+
isPressed && isActive -> MaterialTheme.colorScheme.secondary
68+
isPressed -> MaterialTheme.colorScheme.secondaryContainer
69+
isActive -> MaterialTheme.colorScheme.secondary
70+
else -> MaterialTheme.colorScheme.background
5271
},
5372
label = "FabContainerColor"
5473
)
5574

75+
val sizeAnimationSpec: AnimationSpec<Dp> = remember {
76+
spring(dampingRatio = Spring.DampingRatioMediumBouncy)
77+
}
78+
5679
val defaultElevation by animateDpAsState(
5780
targetValue = if (isActive) 4.dp else 2.dp,
58-
label = "FabElevation"
81+
label = "FabElevation",
82+
animationSpec = sizeAnimationSpec
83+
)
84+
85+
val width by animateDpAsState(
86+
targetValue = if (isPressed || internalPressed) size + 4.dp else size,
87+
label = "FabWidth",
88+
animationSpec = sizeAnimationSpec
5989
)
6090

6191
FloatingActionButton(
62-
modifier = modifier,
63-
onClick = { onToggle(!isActive) },
92+
modifier = modifier.width(width),
93+
onClick = {
94+
onToggle(!isActive)
95+
internalPressed = !internalPressed
96+
coroutineScope.launch {
97+
delay(150L)
98+
internalPressed = !internalPressed
99+
}
100+
},
64101
containerColor = containerColor,
102+
interactionSource = interactionSource,
103+
shape = RoundedCornerShape(cornerRadius.value),
65104
elevation = FloatingActionButtonDefaults.elevation(
66105
defaultElevation = defaultElevation,
67106
pressedElevation = defaultElevation * 2
68-
),
69-
shape = CircleShape,
107+
)
70108
) {
71109
AnimatedContent(
72110
targetState = isActive,
@@ -94,14 +132,19 @@ internal fun ActionToggleButton(
94132
MaterialTheme.colorScheme.onSurfaceVariant
95133
}
96134

97-
Icon(
98-
painter = if (isCurrentlyActive) iconOnActive else iconOnInactive,
99-
contentDescription = stringResource(
100-
R.string.toggle_format,
101-
contentDescription.orEmpty()
102-
),
103-
tint = iconTint
104-
)
135+
Box(
136+
modifier = Modifier.fillMaxWidth(),
137+
contentAlignment = Alignment.Center
138+
) {
139+
Icon(
140+
painter = if (isCurrentlyActive) iconOnActive else iconOnInactive,
141+
contentDescription = stringResource(
142+
R.string.toggle_format,
143+
contentDescription.orEmpty()
144+
),
145+
tint = iconTint
146+
)
147+
}
105148
}
106149
}
107150
}

app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamLandscapeContent.kt

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import androidx.compose.animation.AnimatedVisibility
66
import androidx.compose.foundation.background
77
import androidx.compose.foundation.combinedClickable
88
import androidx.compose.foundation.interaction.MutableInteractionSource
9+
import androidx.compose.foundation.layout.Arrangement
910
import androidx.compose.foundation.layout.Box
1011
import androidx.compose.foundation.layout.Column
1112
import androidx.compose.foundation.layout.Row
1213
import androidx.compose.foundation.layout.Spacer
14+
import androidx.compose.foundation.layout.fillMaxSize
1315
import androidx.compose.foundation.layout.padding
1416
import androidx.compose.foundation.layout.width
1517
import androidx.compose.foundation.rememberScrollState
18+
import androidx.compose.foundation.shape.RoundedCornerShape
1619
import androidx.compose.foundation.verticalScroll
1720
import androidx.compose.material3.AssistChip
1821
import androidx.compose.material3.Icon
@@ -26,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
2629
import androidx.compose.runtime.setValue
2730
import androidx.compose.ui.Alignment
2831
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.draw.clip
2933
import androidx.compose.ui.draw.scale
3034
import androidx.compose.ui.platform.ClipEntry
3135
import androidx.compose.ui.platform.LocalClipboard
@@ -78,12 +82,21 @@ internal fun EditParamLandscapeContent(
7882
Toast.makeText(context, toastCopiedText, Toast.LENGTH_SHORT).show()
7983
}
8084

81-
Row {
85+
Row(
86+
modifier = Modifier
87+
.fillMaxSize()
88+
.background(MaterialTheme.colorScheme.background)
89+
.padding(16.dp),
90+
horizontalArrangement = Arrangement.spacedBy(16.dp)
91+
) {
8292
Column(
8393
modifier = Modifier
8494
.weight(1f)
85-
.background(MaterialTheme.colorScheme.background)
95+
.clip(RoundedCornerShape(24.dp))
96+
.background(MaterialTheme.colorScheme.surfaceContainer)
8697
.verticalScroll(rememberScrollState())
98+
.padding(16.dp),
99+
verticalArrangement = Arrangement.spacedBy(16.dp)
87100
) {
88101
val toastCopyMessage = stringResource(R.string.long_press_to_copy)
89102
Text(
@@ -102,18 +115,13 @@ internal fun EditParamLandscapeContent(
102115
).show()
103116
},
104117
onLongClick = copyParamContentToClipboard
105-
)
106-
.padding(start = 16.dp, end = 16.dp, top = 24.dp),
118+
),
107119
maxLines = 3,
108120
color = MaterialTheme.colorScheme.onBackground,
109121
overflow = TextOverflow.Ellipsis
110122
)
111123

112-
Row(
113-
modifier = Modifier.padding(
114-
horizontal = 16.dp, vertical = if (param.isTaskerParam) 0.dp else 24.dp
115-
), verticalAlignment = Alignment.CenterVertically
116-
) {
124+
Row(verticalAlignment = Alignment.CenterVertically) {
117125
Column(modifier = Modifier.weight(1f)) {
118126
Text(
119127
text = param.name,
@@ -154,7 +162,6 @@ internal fun EditParamLandscapeContent(
154162
val listName = taskerListNameResolver(param.taskerList)
155163
AssistChip(
156164
onClick = { onTaskerClicked(true) },
157-
modifier = Modifier.padding(16.dp),
158165
label = { Text(text = stringResource(R.string.tasker_list_format, listName)) },
159166
leadingIcon = {
160167
Icon(
@@ -169,25 +176,25 @@ internal fun EditParamLandscapeContent(
169176
Column(
170177
modifier = Modifier
171178
.weight(1f)
179+
.clip(RoundedCornerShape(24.dp))
172180
.background(MaterialTheme.colorScheme.surfaceContainer)
173181
.verticalScroll(rememberScrollState())
182+
.padding(16.dp),
183+
verticalArrangement = Arrangement.spacedBy(16.dp)
174184
) {
175185
ParamValueContent(
176-
modifier = Modifier.padding(16.dp),
177186
param = param,
178187
keyboardType = state.keyboardType,
179188
onValueApply = onValueApply
180189
)
181190

182191
AnimatedVisibility(
183192
visible = showError && errorMessage.isNotEmpty(),
184-
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
185193
) {
186194
ErrorContainer(message = errorMessage, onAnimationEnd = onErrorAnimationEnd)
187195
}
188196

189197
ParamDocs(
190-
modifier = Modifier.padding(16.dp),
191198
documentation = state.documentation,
192199
onReadMorePressed = onDocsReadMorePressed
193200
)

0 commit comments

Comments
 (0)