@@ -2,14 +2,23 @@ package com.simform.ssjetpackcomposeprogressbuttonlibrary
22
33import androidx.compose.animation.animateColorAsState
44import androidx.compose.animation.core.LinearOutSlowInEasing
5+ import androidx.compose.animation.core.animateIntOffsetAsState
56import androidx.compose.animation.core.tween
67import androidx.compose.foundation.BorderStroke
8+ import androidx.compose.foundation.ExperimentalFoundationApi
79import androidx.compose.foundation.Image
10+ import androidx.compose.foundation.background
11+ import androidx.compose.foundation.gestures.Orientation
12+ import androidx.compose.foundation.gestures.draggable
13+ import androidx.compose.foundation.gestures.rememberDraggableState
14+ import androidx.compose.foundation.layout.Arrangement
815import androidx.compose.foundation.layout.Box
916import androidx.compose.foundation.layout.PaddingValues
1017import androidx.compose.foundation.layout.Row
18+ import androidx.compose.foundation.layout.offset
1119import androidx.compose.foundation.layout.padding
1220import androidx.compose.foundation.layout.size
21+ import androidx.compose.foundation.layout.width
1322import androidx.compose.foundation.shape.RoundedCornerShape
1423import androidx.compose.material3.Button
1524import androidx.compose.material3.ButtonColors
@@ -26,25 +35,34 @@ import androidx.compose.runtime.remember
2635import androidx.compose.runtime.setValue
2736import androidx.compose.ui.Alignment
2837import androidx.compose.ui.Modifier
38+ import androidx.compose.ui.draw.alpha
2939import androidx.compose.ui.graphics.Color
3040import androidx.compose.ui.graphics.ColorFilter
3141import androidx.compose.ui.graphics.graphicsLayer
3242import androidx.compose.ui.graphics.painter.Painter
43+ import androidx.compose.ui.layout.onGloballyPositioned
44+ import androidx.compose.ui.platform.LocalDensity
3345import androidx.compose.ui.text.TextStyle
3446import androidx.compose.ui.text.font.FontFamily
3547import androidx.compose.ui.text.font.FontStyle
3648import androidx.compose.ui.text.font.FontWeight
3749import androidx.compose.ui.unit.Dp
50+ import androidx.compose.ui.unit.IntOffset
3851import androidx.compose.ui.unit.TextUnit
3952import androidx.compose.ui.unit.dp
4053import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.DEFAULT_ANIMATION_SPEED
54+ import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.DELAY_AFTER_SWIPE_COMPLETE
4155import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.DISABLE_VIEW_ALPHA
4256import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.Dimens.COMMON_CORNER_RADIUS
4357import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.Dimens.SPACING_LARGE
4458import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.Dimens.SPACING_SMALL
4559import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.ENABLE_VIEW_ALPHA
4660import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.LAUNCH_EFFECT_KEY
61+ import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.SWIPE_ABLE_THRESHOLD_FIFTY_PERCENT
62+ import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.SWIPE_ABLE_THRESHOLD_HUNDRED_PERCENT
4763import kotlinx.coroutines.delay
64+ import kotlin.math.max
65+ import kotlin.math.roundToInt
4866
4967/* *
5068 * @param type of animation from SSButtonType in loading state.
@@ -93,8 +111,16 @@ import kotlinx.coroutines.delay
93111 * @param customLoadingEffect custom loading animation type.
94112 * @param customLoadingPadding spacing between button border and loading icon.
95113 * @param shouldAutoMoveToIdleState In case of success/failure state after defined time it move back to idle state
114+ * @param swipeAbleButtonPadding Spacing for swipeAble icon button.
115+ * @param swipeAbleButtonThreshold Threshold when swipeAble button should move to swipeComplete state
116+ * @param shouldAutomateSwipeToAnimate Boolean value which identifier if button should start animate
117+ * as soon as `swipeAbleButtonThreshold` value is reached.
118+ * @param onSwipeAbleButtonDragPercentageUpdate Callback which provides progress of swipeAble button
119+ * value lies between 0 to 1.
120+ * @param onSwiped Callback when swipe is completed
96121 */
97122
123+ @OptIn(ExperimentalFoundationApi ::class )
98124@Composable
99125fun SSJetPackComposeProgressButton (
100126 type : SSButtonType ,
@@ -115,6 +141,7 @@ fun SSJetPackComposeProgressButton(
115141 colors : ButtonColors = ButtonDefaults .buttonColors(),
116142 padding : PaddingValues = PaddingValues (0.dp),
117143 alphaValue : Float = 1f,
144+ swipeAbleImagePainter : Painter ? = null,
118145 leftImagePainter : Painter ? = null,
119146 leftImageTintColor : Color ? = null,
120147 rightImagePainter : Painter ? = null,
@@ -137,8 +164,14 @@ fun SSJetPackComposeProgressButton(
137164 fadeInOut = false
138165 ),
139166 shouldAutoMoveToIdleState : Boolean = true,
140- customLoadingPadding : Int = 0
167+ customLoadingPadding : Int = 0,
168+ swipeAbleButtonPadding : PaddingValues = PaddingValues (0.dp),
169+ swipeAbleButtonThreshold : Float = SWIPE_ABLE_THRESHOLD_FIFTY_PERCENT ,
170+ shouldAutomateSwipeToAnimate : Boolean = false,
171+ onSwipeAbleButtonDragPercentageUpdate : (Float ) -> Unit = {},
172+ onSwiped : () -> Unit = {}
141173) {
174+
142175 var buttonWidth by remember { mutableStateOf(width) }
143176 var buttonHeight by remember { mutableStateOf(height) }
144177 var iconAlphaValue by remember { mutableFloatStateOf(ENABLE_VIEW_ALPHA ) }
@@ -164,6 +197,40 @@ fun SSJetPackComposeProgressButton(
164197 } else {
165198 height
166199 }
200+ // Swipe button related variables
201+ var isSwipeCompleted by remember { mutableStateOf(false ) }
202+ var buttonBoxWidth by remember { mutableIntStateOf(0 ) }
203+ val buttonBoxWidthInDp = with (LocalDensity .current) {
204+ buttonBoxWidth.toDp()
205+ }
206+ var swipeAbleImagePainterWidth by remember { mutableIntStateOf(0 ) }
207+ var swipeAbleButtonDragOffsetX by remember { mutableFloatStateOf(0f ) }
208+ val swipeAbleButtonDragPercentage = if (buttonBoxWidth > 0 ) {
209+ (swipeAbleButtonDragOffsetX / (buttonBoxWidth - swipeAbleImagePainterWidth))
210+ .coerceIn(0f , SWIPE_ABLE_THRESHOLD_HUNDRED_PERCENT )
211+ } else {
212+ 0f
213+ }
214+ val swipeAbleButtonMaxSwipeDistance = max(
215+ 0f ,
216+ buttonBoxWidth.toFloat() - swipeAbleImagePainterWidth.toFloat()
217+ )
218+
219+ LaunchedEffect (isSwipeCompleted) {
220+ if (isSwipeCompleted) {
221+ onSwiped()
222+ delay(DELAY_AFTER_SWIPE_COMPLETE ) // We need to add delay before resetting to original state
223+ isSwipeCompleted = false
224+ }
225+ }
226+
227+ LaunchedEffect (swipeAbleButtonDragPercentage) {
228+ onSwipeAbleButtonDragPercentageUpdate(swipeAbleButtonDragPercentage)
229+ if (shouldAutomateSwipeToAnimate && swipeAbleButtonDragPercentage > swipeAbleButtonThreshold) {
230+ isSwipeCompleted = true
231+ }
232+ }
233+
167234 when (buttonState) {
168235 SSButtonState .IDLE -> {
169236 if (height > width) {
@@ -201,6 +268,7 @@ fun SSJetPackComposeProgressButton(
201268 successIconAlphaValue = ENABLE_VIEW_ALPHA
202269 progressAlphaValue = DISABLE_VIEW_ALPHA
203270 cornerRadiusValue = COMMON_CORNER_RADIUS
271+ swipeAbleButtonDragOffsetX = 0f
204272 if (shouldAutoMoveToIdleState) {
205273 // Delay to show success icon and then IDLE state
206274 successIconPainter?.let {
@@ -251,7 +319,8 @@ fun SSJetPackComposeProgressButton(
251319 }
252320 Box (
253321 contentAlignment = Alignment .Center ,
254- modifier = Modifier .graphicsLayer { alpha = alphaValue }) {
322+ modifier = Modifier .graphicsLayer { alpha = alphaValue }
323+ ) {
255324 Button (
256325 onClick = onClick,
257326 modifier = Modifier
@@ -264,7 +333,10 @@ fun SSJetPackComposeProgressButton(
264333 targetValue = buttonHeight,
265334 durationMillis = speedMillis
266335 )
267- ),
336+ )
337+ .onGloballyPositioned {
338+ buttonBoxWidth = it.size.width
339+ },
268340 enabled = enabled && buttonState != SSButtonState .LOADING ,
269341 elevation = elevation,
270342 shape = RoundedCornerShape (ssAnimateIntAsState(cornerRadiusValue, speedMillis)),
@@ -274,6 +346,7 @@ fun SSJetPackComposeProgressButton(
274346 ),
275347 colors = colors
276348 ) {}
349+
277350 // IDLE State icon
278351 Row (verticalAlignment = Alignment .CenterVertically ) {
279352 leftImagePainter?.let {
@@ -404,6 +477,52 @@ fun SSJetPackComposeProgressButton(
404477 customLoadingEffect = customLoadingEffect,
405478 customLoadingPadding = customLoadingPadding
406479 )
480+ Row (
481+ horizontalArrangement = Arrangement .Start ,
482+ verticalAlignment = Alignment .CenterVertically ,
483+ modifier = Modifier .width(buttonBoxWidthInDp)
484+ ) {
485+ swipeAbleImagePainter?.let {
486+ // This is used to animate offset
487+ val offset = animateIntOffsetAsState(
488+ targetValue = IntOffset (swipeAbleButtonDragOffsetX.roundToInt(), 0 ),
489+ label = " offset"
490+ )
491+ Image (
492+ painter = swipeAbleImagePainter,
493+ contentDescription = null ,
494+ modifier = Modifier
495+ .offset { offset.value }
496+ .alpha(
497+ if (buttonState == SSButtonState .LOADING ) 0f
498+ else ssAnimateFloatAsState(
499+ targetValue = iconAlphaValue,
500+ durationMillis = speedMillis
501+ )
502+ )
503+ .size(minHeightWidth)
504+ .padding(swipeAbleButtonPadding)
505+ .draggable(
506+ orientation = Orientation .Horizontal ,
507+ state = rememberDraggableState { delta ->
508+ swipeAbleButtonDragOffsetX = (swipeAbleButtonDragOffsetX + delta)
509+ .coerceIn(0f , swipeAbleButtonMaxSwipeDistance)
510+ },
511+ onDragStopped = {
512+ if (swipeAbleButtonDragPercentage < swipeAbleButtonThreshold)
513+ swipeAbleButtonDragOffsetX = 0f
514+ else {
515+ swipeAbleButtonDragOffsetX = 0f
516+ isSwipeCompleted = true
517+ }
518+ }
519+ )
520+ .onGloballyPositioned {
521+ swipeAbleImagePainterWidth = it.size.width
522+ }
523+ )
524+ }
525+ }
407526 }
408527}
409528
0 commit comments