@@ -47,6 +47,7 @@ import androidx.compose.runtime.derivedStateOf
4747import androidx.compose.runtime.getValue
4848import androidx.compose.runtime.mutableIntStateOf
4949import androidx.compose.runtime.mutableStateOf
50+ import androidx.compose.runtime.rememberUpdatedState
5051import androidx.compose.runtime.remember
5152import androidx.compose.runtime.rememberCoroutineScope
5253import androidx.compose.runtime.setValue
@@ -70,6 +71,7 @@ import androidx.compose.ui.unit.dp
7071import androidx.compose.ui.unit.times
7172import com.getcode.theme.CodeTheme
7273import com.getcode.theme.DesignSystem
74+ import com.getcode.theme.White20
7375import com.getcode.theme.White50
7476import com.getcode.ui.theme.CodeCircularProgressIndicator
7577import 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
111110private 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