Skip to content

Instantly share code, notes, and snippets.

@anitaa1990
Last active April 8, 2025 09:35
Show Gist options
  • Save anitaa1990/bd3d257337317cb71fdfd8ca73188bfb to your computer and use it in GitHub Desktop.
Save anitaa1990/bd3d257337317cb71fdfd8ca73188bfb to your computer and use it in GitHub Desktop.
@Composable
fun SlideToBookButton(
btnText: String,
btnTextStyle: TextStyle,
outerBtnBackgroundColor: Color,
sliderBtnBackgroundColor: Color,
@DrawableRes sliderBtnIcon: Int,
onBtnSwipe: () -> Unit
) {
// Slider button width
val sliderButtonWidthDp = 70.dp
/**
* ----------------------------------------
* ✨ Step 1:
* • Convert slider button width into pixels so we can use it in math
* • Define a variable to compute the current horizontal position of the slider button (in pixels)
* • Define a variable to capture the total width of the outer button in pixels
* ----------------------------------------
*/
val density = LocalDensity.current
val sliderButtonWidthPx = with(density) { sliderButtonWidthDp.toPx() }
var sliderPositionPx by remember { mutableFloatStateOf(0f) }
var boxWidthPx by remember { mutableIntStateOf(0) }
/**
* ----------------------------------------
* ✨ Step 5: Calculate drag progress percentage (0f to 1f)
* ----------------------------------------
*/
val dragProgress = remember(sliderPositionPx, boxWidthPx) {
if (boxWidthPx > 0) {
(sliderPositionPx / (boxWidthPx - sliderButtonWidthPx)).coerceIn(0f, 1f)
} else {
0f
}
}
/**
* ----------------------------------------
* ✨ Step 6: Alpha value for the button label — 1 when untouched, fades to 0 as drag progresses
* ----------------------------------------
*/
val textAlpha = 1f - dragProgress
/**
* ----------------------------------------
* ✨ Step 8:
* • Add a flag to indicate the slide is complete.
* • Animate the shrinking of the outer button i.e. scale the outer button.
* • Animate the fading of the slider button i.e. make it disappear.
* ----------------------------------------
*/
var sliderComplete by remember { mutableStateOf(false) }
val trackScale by animateFloatAsState(
targetValue = if (sliderComplete) 0f else 1f,
animationSpec = tween(durationMillis = 300), label = "trackScale"
)
val sliderAlpha by animateFloatAsState(
targetValue = if (sliderComplete) 0f else 1f,
animationSpec = tween(durationMillis = 300), label = "sliderAlpha"
)
/**
* ----------------------------------------
* ✨ Step 9: Mark slide as complete once drag passes 80%.
* This calls the onBtnSwipe method so users can perform whatever action is needed for their app.
* ----------------------------------------
*/
LaunchedEffect(dragProgress) {
if (dragProgress >= 0.8f && !sliderComplete) {
sliderComplete = true
onBtnSwipe()
}
}
// The root layout for the button — stretches full width and has fixed height
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
/**
* ----------------------------------------
* ✨ Step 2: Capture the full width of the button once it's laid out
* ----------------------------------------
*/
.onSizeChanged { size ->
boxWidthPx = size.width
}
) {
// Outer track — acts as the base of the button
Box(
modifier = Modifier
.matchParentSize()
/**
* ----------------------------------------
* ✨ Step 10: Animate scaling/shrinking of the button
* ----------------------------------------
*/
.graphicsLayer(scaleX = trackScale, scaleY = 1f)
.background(
color = outerBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
)
) {
// The center-aligned button label
Text(
text = btnText,
style = btnTextStyle,
modifier = Modifier.align(Alignment.Center)
/**
* ----------------------------------------
* ✨ Step 7: Apply the dynamic transparency to the label
* ----------------------------------------
*/
.alpha(textAlpha)
)
}
// Slider thumb container, positioned at the left edge of the button
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(1.dp)
/**
* ----------------------------------------
* ✨ Step 3: Shift the slider button based on drag position (px to dp conversion)
* ----------------------------------------
*/
.offset(x = with(density) { sliderPositionPx.toDp() })
/**
* ----------------------------------------
* ✨ Step 4: Handle horizontal drag gestures
* ----------------------------------------
*/
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
// Calculate new potential position
val newPosition = sliderPositionPx + delta
// Clamp it within 0 to (totalWidth - slider button width)
val maxPosition = boxWidthPx - sliderButtonWidthPx
sliderPositionPx = newPosition.coerceIn(0f, maxPosition)
},
onDragStarted = { /* Optional: add feedback or animation here */ },
onDragStopped = { }
),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
/**
* ----------------------------------------
* ✨ Step 11: Animate fade out of the slider button
* ----------------------------------------
*/
.alpha(sliderAlpha)
.graphicsLayer { alpha = sliderAlpha }
) {
// The draggable thumb itself
SliderButton(
sliderBtnWidth = sliderButtonWidthDp,
sliderBtnBackgroundColor = sliderBtnBackgroundColor,
sliderBtnIcon = sliderBtnIcon
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment