Skip to content

Commit ffd63a5

Browse files
committed
style(ui): refactor SlideToConfirm enabled state, reset lifecycle, and track color
Add `enabled` parameter with alpha animation, fix loading reset via rememberUpdatedState with 200ms auto-reset, and simplify track color to White20 with white checkmark tint. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 73ee72a commit ffd63a5

1 file changed

Lines changed: 35 additions & 24 deletions

File tree

ui/components/src/main/kotlin/com/getcode/ui/components/SlideToConfirm.kt

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import androidx.compose.runtime.derivedStateOf
4747
import androidx.compose.runtime.getValue
4848
import androidx.compose.runtime.mutableIntStateOf
4949
import androidx.compose.runtime.mutableStateOf
50+
import androidx.compose.runtime.rememberUpdatedState
5051
import androidx.compose.runtime.remember
5152
import androidx.compose.runtime.rememberCoroutineScope
5253
import androidx.compose.runtime.setValue
@@ -70,6 +71,7 @@ import androidx.compose.ui.unit.dp
7071
import androidx.compose.ui.unit.times
7172
import com.getcode.theme.CodeTheme
7273
import com.getcode.theme.DesignSystem
74+
import com.getcode.theme.White20
7375
import com.getcode.theme.White50
7476
import com.getcode.ui.theme.CodeCircularProgressIndicator
7577
import com.getcode.ui.core.addIf
@@ -103,9 +105,6 @@ object SlideToConfirmDefaults {
103105

104106

105107
const val SnapThreshold = 0.7f
106-
val ThemedColor: Color
107-
@Composable get() = Track.ThemedColor
108-
val BlackTrackColor = Track.BlackColor
109108
}
110109

111110
private object Thumb {
@@ -121,9 +120,7 @@ private object Track {
121120
val Shape: Shape
122121
@Composable get() = CodeTheme.shapes.small
123122

124-
val BlackColor = Color(0xFF201D1D)
125-
val ThemedColor: Color
126-
@Composable get() = CodeTheme.colors.trackColor
123+
val Color = White20
127124
}
128125

129126

@@ -161,8 +158,9 @@ fun SlideToConfirm(
161158
onConfirm: () -> Unit,
162159
modifier: Modifier = Modifier,
163160
trackShape: Shape = Track.Shape,
164-
trackColor: Color = Track.ThemedColor,
161+
trackColor: Color = Track.Color,
165162
thumbShape: Shape = Thumb.Shape,
163+
enabled: Boolean = true,
166164
isLoading: Boolean = false,
167165
isSuccess: Boolean = false,
168166
label: String = stringResource(R.string.action_swipeToPay),
@@ -176,10 +174,7 @@ fun SlideToConfirm(
176174
)
177175
},
178176
) {
179-
var loading by remember(isLoading) {
180-
mutableStateOf(isLoading)
181-
}
182-
177+
val currentIsLoading by rememberUpdatedState(isLoading)
183178
val hapticFeedback = LocalHapticFeedback.current
184179
val swipeState = rememberSwipeableState(
185180
initialValue = Anchor.Start,
@@ -192,28 +187,44 @@ fun SlideToConfirm(
192187
derivedStateOf { calculateSwipeFraction(swipeState.progress) }
193188
}
194189

195-
LaunchedEffect(swipeFraction) {
196-
when (swipeFraction) {
197-
0f -> hintState.startTimer()
198-
in 0.1f .. 0.99f -> hintState.cancelTimer()
190+
LaunchedEffect(swipeFraction, enabled) {
191+
when {
192+
!enabled -> hintState.cancelTimer()
193+
swipeFraction == 0f -> hintState.startTimer()
194+
swipeFraction in 0.1f .. 0.99f -> hintState.cancelTimer()
199195
}
200196
}
201197
LaunchedEffect(swipeFraction) {
202198
if (swipeFraction == 1f) {
203199
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
204-
loading = true
205200
onConfirm()
201+
// Give the caller a moment to set isLoading = true.
202+
// If they don't, the confirmation was rejected — reset.
203+
// Launch in composeScope so the animation isn't cancelled when
204+
// swipeFraction changes (which would cancel this LaunchedEffect).
205+
delay(200)
206+
if (!currentIsLoading) {
207+
composeScope.launch { swipeState.animateTo(Anchor.Start) }
208+
}
206209
}
207210
}
208211

209-
LaunchedEffect(loading) {
210-
swipeState.animateTo(if (loading) Anchor.End else Anchor.Start)
212+
// Handle the loading → idle transition (e.g. after a network call completes)
213+
LaunchedEffect(isLoading) {
214+
if (!isLoading && swipeState.currentValue == Anchor.End) {
215+
swipeState.animateTo(Anchor.Start)
216+
}
211217
}
212218

219+
val disabledAlpha by animateFloatAsState(
220+
targetValue = if (enabled || isLoading || isSuccess) 1f else 0.38f,
221+
label = "disabled alpha"
222+
)
223+
213224
Track(
214225
swipeState = swipeState,
215-
enabled = !loading,
216-
modifier = modifier,
226+
enabled = enabled && !isLoading,
227+
modifier = modifier.alpha(disabledAlpha),
217228
shape = trackShape,
218229
color = trackColor,
219230
) {
@@ -225,14 +236,14 @@ fun SlideToConfirm(
225236
Image(
226237
painter = painterResource(id = R.drawable.ic_check),
227238
contentDescription = "",
228-
colorFilter = ColorFilter.tint(CodeTheme.colors.success),
239+
colorFilter = ColorFilter.tint(Color.White),
229240
modifier = Modifier
230241
.size(CodeTheme.dimens.grid.x4)
231242
.align(Alignment.Center),
232243
)
233244
} else {
234245
val loadingColor by animateColorAsState(
235-
targetValue = if (loading) Color.White else Color.Transparent
246+
targetValue = if (isLoading) Color.White else Color.Transparent
236247
)
237248

238249
CodeCircularProgressIndicator(
@@ -245,7 +256,7 @@ fun SlideToConfirm(
245256
}
246257

247258
val thumbAlpha by animateFloatAsState(
248-
targetValue = if (loading || isSuccess) 0f else 1f,
259+
targetValue = if (isLoading || isSuccess) 0f else 1f,
249260
label = "thumb alpha"
250261
)
251262

@@ -290,7 +301,7 @@ private fun Track(
290301
enabled: Boolean,
291302
modifier: Modifier = Modifier,
292303
shape: Shape = Track.Shape,
293-
color: Color = Track.BlackColor,
304+
color: Color = Track.Color,
294305
content: @Composable (BoxScope.() -> Unit),
295306
) {
296307
val density = LocalDensity.current

0 commit comments

Comments
 (0)