|
package com.oleg.green.swipe_button |
|
|
|
import android.content.res.Configuration |
|
import androidx.compose.animation.AnimatedVisibility |
|
import androidx.compose.animation.Crossfade |
|
import androidx.compose.animation.core.tween |
|
import androidx.compose.foundation.ExperimentalFoundationApi |
|
import androidx.compose.foundation.background |
|
import androidx.compose.foundation.gestures.AnchoredDraggableState |
|
import androidx.compose.foundation.gestures.DraggableAnchors |
|
import androidx.compose.foundation.gestures.Orientation |
|
import androidx.compose.foundation.gestures.anchoredDraggable |
|
import androidx.compose.foundation.layout.Box |
|
import androidx.compose.foundation.layout.fillMaxWidth |
|
import androidx.compose.foundation.layout.heightIn |
|
import androidx.compose.foundation.layout.offset |
|
import androidx.compose.foundation.layout.padding |
|
import androidx.compose.foundation.layout.size |
|
import androidx.compose.foundation.layout.wrapContentHeight |
|
import androidx.compose.material.Icon |
|
import androidx.compose.material.MaterialTheme |
|
import androidx.compose.material.Surface |
|
import androidx.compose.material.Text |
|
import androidx.compose.material.icons.Icons |
|
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight |
|
import androidx.compose.material.icons.outlined.Done |
|
import androidx.compose.runtime.Composable |
|
import androidx.compose.runtime.LaunchedEffect |
|
import androidx.compose.runtime.getValue |
|
import androidx.compose.runtime.mutableStateOf |
|
import androidx.compose.runtime.remember |
|
import androidx.compose.runtime.setValue |
|
import androidx.compose.ui.Alignment |
|
import androidx.compose.ui.Modifier |
|
import androidx.compose.ui.graphics.Color |
|
import androidx.compose.ui.layout.onGloballyPositioned |
|
import androidx.compose.ui.platform.LocalContext |
|
import androidx.compose.ui.platform.LocalDensity |
|
import androidx.compose.ui.text.font.FontWeight |
|
import androidx.compose.ui.tooling.preview.Preview |
|
import androidx.compose.ui.unit.IntOffset |
|
import androidx.compose.ui.unit.dp |
|
import androidx.compose.ui.unit.sp |
|
import kotlinx.coroutines.delay |
|
import kotlin.math.roundToInt |
|
|
|
private val swipingBoxSize = 41.dp |
|
|
|
@OptIn(ExperimentalFoundationApi::class) |
|
@Composable |
|
fun SwipeButton( |
|
modifier: Modifier = Modifier, |
|
onSwiped: () -> Unit |
|
) { |
|
Surface( |
|
modifier = modifier |
|
.fillMaxWidth() |
|
.wrapContentHeight() |
|
.heightIn(min = 60.dp), |
|
color = MaterialTheme.colors.primary, |
|
contentColor = Color.White, |
|
shape = MaterialTheme.shapes.small |
|
) { |
|
var swipeCompleted by remember { mutableStateOf(false) } |
|
var containerWidth by remember { mutableStateOf(Int.MAX_VALUE) } |
|
|
|
Box( |
|
modifier = Modifier |
|
.padding(10.dp) |
|
.onGloballyPositioned { |
|
containerWidth = it.size.width |
|
}, |
|
contentAlignment = Alignment.Center |
|
) { |
|
val icon = if (swipeCompleted) { |
|
Icons.Outlined.Done |
|
} else { |
|
Icons.AutoMirrored.Outlined.KeyboardArrowRight |
|
} |
|
|
|
val swipingBoxPx = with(LocalDensity.current) { swipingBoxSize.toPx() } |
|
val maxSwipeDistance = containerWidth - swipingBoxPx |
|
|
|
val context = LocalContext.current |
|
LaunchedEffect(swipeCompleted) { |
|
if (swipeCompleted) { |
|
//maybe add vibration here |
|
delay(1000) |
|
onSwiped() |
|
} |
|
} |
|
|
|
val state = remember { |
|
AnchoredDraggableState( |
|
initialValue = DragAnchors.Start, |
|
positionalThreshold = { distance: Float -> distance * 0.99f }, |
|
velocityThreshold = { Float.MAX_VALUE }, |
|
animationSpec = tween(), |
|
).apply { |
|
updateAnchors( |
|
DraggableAnchors { |
|
DragAnchors.Start at 0f |
|
DragAnchors.End at maxSwipeDistance |
|
} |
|
) |
|
} |
|
} |
|
|
|
LaunchedEffect(maxSwipeDistance) { |
|
state.updateAnchors( |
|
DraggableAnchors { |
|
DragAnchors.Start at 0f |
|
DragAnchors.End at maxSwipeDistance |
|
} |
|
) |
|
} |
|
|
|
val dragAnchorOffset = IntOffset( |
|
x = state.requireOffset().roundToInt(), |
|
y = 0 |
|
) |
|
|
|
if (state.requireOffset() >= maxSwipeDistance) { |
|
swipeCompleted = true |
|
} |
|
|
|
Box( |
|
modifier = Modifier |
|
.align(Alignment.CenterStart) |
|
.offset { dragAnchorOffset } |
|
.anchoredDraggable( |
|
state = state, |
|
enabled = !swipeCompleted, |
|
orientation = Orientation.Horizontal |
|
) |
|
.size(swipingBoxSize) |
|
.background(color = Color.Gray), |
|
) { |
|
|
|
Crossfade( |
|
modifier = Modifier.align(Alignment.Center), |
|
label = "animate icon transition", |
|
targetState = icon |
|
) { targetState -> |
|
Icon( |
|
imageVector = targetState, |
|
tint = Color.White, |
|
contentDescription = null |
|
) |
|
} |
|
} |
|
|
|
Text( |
|
modifier = Modifier.offset { dragAnchorOffset }, |
|
text = "Finish", |
|
fontSize = 18.sp, |
|
fontWeight = FontWeight.W400, |
|
) |
|
|
|
AnimatedVisibility(swipeCompleted) { |
|
Text( |
|
text = "Success", |
|
fontSize = 18.sp, |
|
fontWeight = FontWeight.W400, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
|
|
enum class DragAnchors { |
|
Start, |
|
End, |
|
} |
|
|
|
@Preview( |
|
showBackground = true, heightDp = 650, showSystemUi = false, |
|
uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Normal" |
|
) |
|
@Composable |
|
private fun SwipeButtonPreview() { |
|
SwipeButton( |
|
onSwiped = {}, |
|
) |
|
} |