Skip to content

Commit 92b7216

Browse files
✨ Add swipe to animate button feature
1 parent cde9f4e commit 92b7216

File tree

5 files changed

+203
-13
lines changed

5 files changed

+203
-13
lines changed

app/src/main/java/com/simform/ssjetpackcomposeprogressbutton/MainActivity.kt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.simform.ssjetpackcomposeprogressbutton
22

33
import android.os.Bundle
4+
import android.util.Log
45
import androidx.activity.ComponentActivity
56
import androidx.activity.compose.setContent
67
import androidx.compose.foundation.gestures.Orientation
@@ -47,6 +48,7 @@ import androidx.compose.ui.tooling.preview.Preview
4748
import androidx.compose.ui.unit.dp
4849
import androidx.core.content.ContextCompat
4950
import com.simform.ssjetpackcomposeprogressbutton.utils.Dimensions
51+
import com.simform.ssjetpackcomposeprogressbutton.utils.Dimensions.EXTRA_SPACING_SMALL
5052
import com.simform.ssjetpackcomposeprogressbutton.utils.Dimensions.SPACING_LARGE
5153
import com.simform.ssjetpackcomposeprogressbutton.utils.Dimensions.SPACING_MEDIUM
5254
import com.simform.ssjetpackcomposeprogressbutton.utils.Dimensions.SPACING_NORMAL
@@ -84,6 +86,8 @@ fun SSLoadingButtonExample() {
8486
var textWithRightButton: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
8587
var textWithIconState: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
8688
var blinkingIcon: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
89+
var automaticSwipeToAnimateButtonState: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
90+
var swipeToAnimateButtonState: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
8791
var customRotationButtonState: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
8892
var customZoomButtonState: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
8993
var customEffectButtonState: SSButtonState by remember { mutableStateOf(SSButtonState.IDLE) }
@@ -113,6 +117,8 @@ fun SSLoadingButtonExample() {
113117
customZoomButtonState = SSButtonState.SUCCESS
114118
customEffectButtonState = SSButtonState.SUCCESS
115119
customGifButtonState = SSButtonState.SUCCESS
120+
automaticSwipeToAnimateButtonState = SSButtonState.SUCCESS
121+
swipeToAnimateButtonState = SSButtonState.SUCCESS
116122
},
117123
modifier = Modifier
118124
.padding(SPACING_NORMAL)
@@ -144,6 +150,8 @@ fun SSLoadingButtonExample() {
144150
customEffectButtonState = SSButtonState.FAILURE
145151
customZoomButtonState = SSButtonState.FAILURE
146152
customGifButtonState = SSButtonState.FAILURE
153+
automaticSwipeToAnimateButtonState = SSButtonState.FAILURE
154+
swipeToAnimateButtonState = SSButtonState.FAILURE
147155
},
148156
modifier = Modifier
149157
.padding(SPACING_NORMAL)
@@ -409,6 +417,64 @@ fun SSLoadingButtonExample() {
409417
successIconPainter = rememberVectorPainter(image = Icons.Default.Done),
410418
failureIconPainter = rememberVectorPainter(image = Icons.Outlined.Info)
411419
)
420+
SSJetPackComposeProgressButton(
421+
assetColor = LIGHT_PINK,
422+
colors = ButtonDefaults.buttonColors(
423+
containerColor = Color.White,
424+
disabledContainerColor = Color.White
425+
),
426+
buttonBorderWidth = Dimensions.COMMON_BORDER_WIDTH,
427+
animatedButtonBorderColor = LIGHT_PINK,
428+
buttonBorderColor = LIGHT_PINK,
429+
type = SSButtonType.CIRCLE,
430+
onClick = { automaticSwipeToAnimateButtonState = SSButtonState.LOADING },
431+
buttonState = automaticSwipeToAnimateButtonState,
432+
width = Dimensions.COMMON_WIDTH,
433+
height = Dimensions.COMMON_HEIGHT,
434+
padding = PaddingValues(SPACING_NORMAL),
435+
cornerRadius = Dimensions.COMMON_CORNER_RADIUS,
436+
leftImagePainter = rememberVectorPainter(image = Icons.Default.Home),
437+
leftImageTintColor = LIGHT_PINK,
438+
successIconPainter = rememberVectorPainter(image = Icons.Default.Done),
439+
failureIconTintColor = LIGHT_PINK,
440+
successIconTintColor = LIGHT_PINK,
441+
failureIconPainter = rememberVectorPainter(image = Icons.Outlined.Info),
442+
swipeAbleImagePainter = painterResource(id = R.drawable.move_forward),
443+
shouldAutomateSwipeToAnimate = true,
444+
swipeAbleButtonPadding = PaddingValues(EXTRA_SPACING_SMALL),
445+
onSwiped = {
446+
automaticSwipeToAnimateButtonState = SSButtonState.LOADING
447+
}
448+
)
449+
SSJetPackComposeProgressButton(
450+
assetColor = LIGHT_PINK,
451+
colors = ButtonDefaults.buttonColors(
452+
containerColor = Color.White,
453+
disabledContainerColor = Color.White
454+
),
455+
buttonBorderWidth = Dimensions.COMMON_BORDER_WIDTH,
456+
animatedButtonBorderColor = LIGHT_PINK,
457+
buttonBorderColor = LIGHT_PINK,
458+
type = SSButtonType.CIRCLE,
459+
onClick = { swipeToAnimateButtonState = SSButtonState.LOADING },
460+
buttonState = swipeToAnimateButtonState,
461+
width = Dimensions.COMMON_WIDTH,
462+
height = Dimensions.COMMON_HEIGHT,
463+
padding = PaddingValues(SPACING_NORMAL),
464+
cornerRadius = Dimensions.SWIPE_BUTTON_CORNER_RADIUS,
465+
leftImagePainter = rememberVectorPainter(image = Icons.Default.Home),
466+
leftImageTintColor = LIGHT_PINK,
467+
swipeAbleImagePainter = painterResource(id = R.drawable.move_forward),
468+
shouldAutomateSwipeToAnimate = false,
469+
swipeAbleButtonPadding = PaddingValues(EXTRA_SPACING_SMALL),
470+
onSwipeAbleButtonDragPercentageUpdate = { percentage ->
471+
// Get the swipe progress over here
472+
Log.d("Swipe progress update >", "$percentage")
473+
},
474+
onSwiped = {
475+
swipeToAnimateButtonState = SSButtonState.LOADING
476+
}
477+
)
412478
Spacer(modifier = Modifier.size(Dimensions.COMMON_HEIGHT))
413479
SSJetPackComposeProgressButton(
414480
type = SSButtonType.CUSTOM,

app/src/main/java/com/simform/ssjetpackcomposeprogressbutton/utils/Dimens.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ object Dimensions{
1010
val TOP_BUTTON_RADIUS = 10.dp
1111
val COMMON_BORDER_WIDTH = 3.dp
1212
val SPACING_SMALL = 10.dp
13+
val EXTRA_SPACING_SMALL = 4.dp
1314
val SPACING_NORMAL = 12.dp
1415
val SPACING_MEDIUM = 24.dp
1516
val SPACING_LARGE = 40.dp
1617
// Constants
1718
const val COMMON_CORNER_RADIUS = 25
19+
const val SWIPE_BUTTON_CORNER_RADIUS = 50
1820
// Fonts
1921
val MEDIUM_FONT_SIZE = 20.sp
2022
}
4.12 KB
Loading

ssjetpackcomposeprogressbutton/src/main/java/com/simform/ssjetpackcomposeprogressbuttonlibrary/SSJetPackComposeProgressButton.kt

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@ package com.simform.ssjetpackcomposeprogressbuttonlibrary
22

33
import androidx.compose.animation.animateColorAsState
44
import androidx.compose.animation.core.LinearOutSlowInEasing
5+
import androidx.compose.animation.core.animateIntOffsetAsState
56
import androidx.compose.animation.core.tween
67
import androidx.compose.foundation.BorderStroke
8+
import androidx.compose.foundation.ExperimentalFoundationApi
79
import 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
815
import androidx.compose.foundation.layout.Box
916
import androidx.compose.foundation.layout.PaddingValues
1017
import androidx.compose.foundation.layout.Row
18+
import androidx.compose.foundation.layout.offset
1119
import androidx.compose.foundation.layout.padding
1220
import androidx.compose.foundation.layout.size
21+
import androidx.compose.foundation.layout.width
1322
import androidx.compose.foundation.shape.RoundedCornerShape
1423
import androidx.compose.material3.Button
1524
import androidx.compose.material3.ButtonColors
@@ -26,25 +35,34 @@ import androidx.compose.runtime.remember
2635
import androidx.compose.runtime.setValue
2736
import androidx.compose.ui.Alignment
2837
import androidx.compose.ui.Modifier
38+
import androidx.compose.ui.draw.alpha
2939
import androidx.compose.ui.graphics.Color
3040
import androidx.compose.ui.graphics.ColorFilter
3141
import androidx.compose.ui.graphics.graphicsLayer
3242
import androidx.compose.ui.graphics.painter.Painter
43+
import androidx.compose.ui.layout.onGloballyPositioned
44+
import androidx.compose.ui.platform.LocalDensity
3345
import androidx.compose.ui.text.TextStyle
3446
import androidx.compose.ui.text.font.FontFamily
3547
import androidx.compose.ui.text.font.FontStyle
3648
import androidx.compose.ui.text.font.FontWeight
3749
import androidx.compose.ui.unit.Dp
50+
import androidx.compose.ui.unit.IntOffset
3851
import androidx.compose.ui.unit.TextUnit
3952
import androidx.compose.ui.unit.dp
4053
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.DEFAULT_ANIMATION_SPEED
54+
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.DELAY_AFTER_SWIPE_COMPLETE
4155
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.DISABLE_VIEW_ALPHA
4256
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.Dimens.COMMON_CORNER_RADIUS
4357
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.Dimens.SPACING_LARGE
4458
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.Dimens.SPACING_SMALL
4559
import com.simform.ssjetpackcomposeprogressbuttonlibrary.utils.ENABLE_VIEW_ALPHA
4660
import 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
4763
import 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
99125
fun 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

ssjetpackcomposeprogressbutton/src/main/java/com/simform/ssjetpackcomposeprogressbuttonlibrary/utils/Constant.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.simform.ssjetpackcomposeprogressbuttonlibrary.utils
22

33
import androidx.compose.ui.unit.dp
44

5-
object Dimens {
5+
internal object Dimens {
66
val SPACING_SMALL = 10.dp
77
val SPACING_MEDIUM = 12.dp
88
val SPACING_LARGE = 20.dp
@@ -14,12 +14,15 @@ object Dimens {
1414
const val COMMON_CORNER_RADIUS = 50
1515
}
1616
// Other Constant's
17-
const val ANIMATION_INITIAL_ZERO = 0f
18-
const val ANIMATION_TARGET_ONE = 1f
19-
const val ENABLE_VIEW_ALPHA = 1f
20-
const val DISABLE_VIEW_ALPHA = 0f
21-
const val ROTATE_THREE_SIXTY_DEGREE = 360f
22-
const val LAUNCH_EFFECT_KEY = "ANIMATION"
23-
const val DEFAULT_ANIMATION_SPEED = 1000
24-
const val MINUTE_DURATION = 1080
25-
const val HOUR_DURATION = 12960
17+
internal const val ANIMATION_INITIAL_ZERO = 0f
18+
internal const val ANIMATION_TARGET_ONE = 1f
19+
internal const val ENABLE_VIEW_ALPHA = 1f
20+
internal const val DISABLE_VIEW_ALPHA = 0f
21+
internal const val ROTATE_THREE_SIXTY_DEGREE = 360f
22+
internal const val LAUNCH_EFFECT_KEY = "ANIMATION"
23+
internal const val DEFAULT_ANIMATION_SPEED = 1000
24+
internal const val MINUTE_DURATION = 1080
25+
internal const val HOUR_DURATION = 12960
26+
internal const val SWIPE_ABLE_THRESHOLD_FIFTY_PERCENT = 0.5f
27+
internal const val SWIPE_ABLE_THRESHOLD_HUNDRED_PERCENT = 1f
28+
internal const val DELAY_AFTER_SWIPE_COMPLETE = 2000L

0 commit comments

Comments
 (0)