diff --git a/app/api/app.api b/app/api/app.api index 8cf0454..a59ec1a 100644 --- a/app/api/app.api +++ b/app/api/app.api @@ -1,9 +1,10 @@ public final class com/revenuecat/slidetounlockdemo/ComposableSingletons$MainActivityKt { public static final field INSTANCE Lcom/revenuecat/slidetounlockdemo/ComposableSingletons$MainActivityKt; public fun ()V + public final fun getLambda$-238258827$app_release ()Lkotlin/jvm/functions/Function9; + public final fun getLambda$-55614235$app_release ()Lkotlin/jvm/functions/Function8; public final fun getLambda$-794938742$app_release ()Lkotlin/jvm/functions/Function2; - public final fun getLambda$136809679$app_release ()Lkotlin/jvm/functions/Function7; - public final fun getLambda$1441203408$app_release ()Lkotlin/jvm/functions/Function7; + public final fun getLambda$1248779494$app_release ()Lkotlin/jvm/functions/Function8; public final fun getLambda$1783585206$app_release ()Lkotlin/jvm/functions/Function2; } @@ -12,3 +13,7 @@ public final class com/revenuecat/slidetounlockdemo/MainActivity : androidx/acti public fun ()V } +public final class com/revenuecat/slidetounlockdemo/MainActivityKt { + public static final fun StackedVerticalText-FNF3uiM (Ljava/lang/String;Landroidx/compose/ui/Modifier;JLandroidx/compose/runtime/Composer;II)V +} + diff --git a/app/src/main/kotlin/com/revenuecat/slidetounlockdemo/MainActivity.kt b/app/src/main/kotlin/com/revenuecat/slidetounlockdemo/MainActivity.kt index 5500ec3..41ef46d 100644 --- a/app/src/main/kotlin/com/revenuecat/slidetounlockdemo/MainActivity.kt +++ b/app/src/main/kotlin/com/revenuecat/slidetounlockdemo/MainActivity.kt @@ -32,8 +32,10 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -56,6 +58,7 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource @@ -65,6 +68,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.revenuecat.purchases.slidetounlock.DefaultSlideToUnlockColors import com.revenuecat.purchases.slidetounlock.HintTexts +import com.revenuecat.purchases.slidetounlock.SlideOrientation import com.revenuecat.purchases.slidetounlock.SlideToUnlock import com.valentinilk.shimmer.shimmer import kotlinx.coroutines.delay @@ -91,6 +95,8 @@ class MainActivity : ComponentActivity() { SlideToUnlockStyle3() SlideToUnlockStyle4() + + SlideToUnlockStyle5() } } } @@ -129,7 +135,8 @@ private fun SlideToUnlockStyle1() { slidedHintColor = Color.White, ), onSlideCompleted = { isSlided = true }, - thumb = { slided, fraction, colors, size -> + + thumb = { slided, fraction, colors, size, orientation -> Box( modifier = Modifier.size(size), ) { @@ -142,16 +149,17 @@ private fun SlideToUnlockStyle1() { ) } }, - hint = { slided, fraction, hintTexts, colors, paddings -> + + hint = { slided, fraction, hintTexts, colors, paddings, orientation -> val layoutDirection = LocalLayoutDirection.current AnimatedContent( modifier = Modifier .fillMaxWidth() .align(Alignment.Center), - targetState = isSlided, - ) { slided -> - if (!slided) { + targetState = slided, + ) { slidedState -> + if (!slidedState) { Text( modifier = Modifier .fillMaxWidth() @@ -214,7 +222,7 @@ private fun SlideToUnlockStyle2() { isSlided = true Toast.makeText(context, "unlocked!", Toast.LENGTH_SHORT).show() }, - thumb = { slided, fraction, colors, size -> + thumb = { slided, fraction, colors, size, orientation -> val colorStops = arrayOf( 0.0f to Color.White, 1f to colors.thumbColor(), @@ -237,7 +245,7 @@ private fun SlideToUnlockStyle2() { ) } }, - hint = { slided, fraction, hintTexts, colors, paddings -> + hint = { slided, fraction, hintTexts, colors, paddings, orientation -> val layoutDirection = LocalLayoutDirection.current Crossfade( modifier = Modifier @@ -295,7 +303,7 @@ private fun SlideToUnlockStyle3() { ), colors = colors, onSlideCompleted = { isSlided = true }, - thumb = { slided, fraction, colors, size -> + thumb = { slided, fraction, colors, size, orientation -> Box( modifier = Modifier .size(size) @@ -329,7 +337,7 @@ private fun SlideToUnlockStyle3() { } } }, - hint = { slided, fraction, hintTexts, colors, paddings -> + hint = { slided, fraction, hintTexts, colors, paddings, orientation -> val layoutDirection = LocalLayoutDirection.current AnimatedContent( @@ -411,7 +419,7 @@ private fun SlideToUnlockStyle4() { ), colors = colors, onSlideCompleted = { isSlided = true }, - thumb = { slided, fraction, colors, size -> + thumb = { slided, fraction, colors, size, orientation -> Box( modifier = Modifier .size(size) @@ -445,7 +453,7 @@ private fun SlideToUnlockStyle4() { } } }, - hint = { slided, fraction, hintTexts, colors, paddings -> + hint = { slided, fraction, hintTexts, colors, paddings, orientation -> val layoutDirection = LocalLayoutDirection.current AnimatedContent( @@ -489,3 +497,128 @@ private fun SlideToUnlockStyle4() { }, ) } + +@Composable +private fun SlideToUnlockStyle5() { + var isSlided by remember { mutableStateOf(false) } + var isCompleted by remember { mutableStateOf(false) } + val animateColor: Float by animateFloatAsState( + if (isCompleted) 1f else 0f, + label = "alpha", + animationSpec = tween(durationMillis = 700), + ) + val colors = if (isCompleted) { + DefaultSlideToUnlockColors( + endTrackColor = lerp(Color(0xFFB4AFB4), Color(0xFFC91224), animateColor), + slidedHintColor = Color.White, + thumbIconColor = Color(0xFFC91224), + ) + } else { + DefaultSlideToUnlockColors( + endTrackColor = Color(0xFFB4AFB4), + slidedHintColor = Color.White, + ) + } + + LaunchedEffect(isSlided) { + if (isSlided) { + delay(1500) + isCompleted = true + } + } + + SlideToUnlock( + isSlided = isSlided, + hintTexts = HintTexts.defaultHintTexts().copy( + defaultText = "Hi", + slidedText = "Hello", + ), + colors = colors, + onSlideCompleted = { isSlided = true }, + orientation = SlideOrientation.Vertical, + thumb = { slided, fraction, colors, size, orientation -> + Box( + modifier = Modifier + .size(size) + .background(color = colors.thumbColor(), shape = CircleShape), + contentAlignment = Alignment.Center, + ) { + if (isCompleted) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(30.dp), + imageVector = Icons.Default.Error, + tint = colors.thumbIconColor(), + contentDescription = "Failed", + ) + } else if (isSlided) { + CircularProgressIndicator( + modifier = Modifier.padding(8.dp), + color = colors.progressColor(), + strokeWidth = 3.dp, + ) + } else { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(30.dp) + .rotate(fraction * -360), + imageVector = Icons.Default.Restore, + tint = colors.thumbIconColor(), + contentDescription = "Slide to unlock", + ) + } + } + }, + hint = { slided, fraction, hintTexts, colors, paddings, orientation -> + + AnimatedContent( + modifier = Modifier + .align(Alignment.Center) + .onGloballyPositioned {}, + targetState = isSlided, + ) { slided -> + if (isCompleted) { + StackedVerticalText( + text = "Failed: Check out your account", + textColor = colors.slidedHintColor(), + ) + } else if (!slided) { + StackedVerticalText( + text = hintTexts.defaultText, + textColor = colors.hintColor(fraction), + ) + } else { + StackedVerticalText( + text = hintTexts.slidedText, + textColor = colors.slidedHintColor(), + ) + } + } + }, + ) +} + +@Composable +fun StackedVerticalText( + text: String, + modifier: Modifier = Modifier, + textColor: Color = Color.Black, +) { + Column( + modifier = modifier + .heightIn(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + text.forEach { char -> + Text( + text = char.toString(), + textAlign = TextAlign.Center, + color = textColor, + style = MaterialTheme.typography.titleMedium, + ) + } + } +} diff --git a/slidetounlock/api/android/slidetounlock.api b/slidetounlock/api/android/slidetounlock.api index c0a91c8..f2c51f6 100644 --- a/slidetounlock/api/android/slidetounlock.api +++ b/slidetounlock/api/android/slidetounlock.api @@ -56,6 +56,14 @@ public final class com/revenuecat/purchases/slidetounlock/HintTexts$Companion { public final fun defaultHintTexts ()Lcom/revenuecat/purchases/slidetounlock/HintTexts; } +public final class com/revenuecat/purchases/slidetounlock/SlideOrientation : java/lang/Enum { + public static final field Horizontal Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; + public static final field Vertical Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; + public static fun values ()[Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; +} + public abstract interface class com/revenuecat/purchases/slidetounlock/SlideToUnlockColors { public abstract fun hintColor-XeAY9LY (FLandroidx/compose/runtime/Composer;I)J public abstract fun progressColor-WaAFU9c (Landroidx/compose/runtime/Composer;I)J @@ -70,14 +78,14 @@ public abstract interface class com/revenuecat/purchases/slidetounlock/SlideToUn public final class com/revenuecat/purchases/slidetounlock/SlideToUnlockDefaults { public static final field $stable I public static final field INSTANCE Lcom/revenuecat/purchases/slidetounlock/SlideToUnlockDefaults; - public final fun Hint (Lcom/revenuecat/purchases/slidetounlock/HintTexts;ZFLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V - public final fun Thumb-8HUqYh0 (ZJLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V + public final fun Hint (Lcom/revenuecat/purchases/slidetounlock/HintTexts;ZFLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Landroidx/compose/foundation/layout/PaddingValues;Lcom/revenuecat/purchases/slidetounlock/SlideOrientation;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V + public final fun Thumb-coD9juw (ZJLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Lcom/revenuecat/purchases/slidetounlock/SlideOrientation;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V public final fun getPaddings-D9Ej5fM ()F public final fun getThumbSize-D9Ej5fM ()F public final fun getVelocityThreshold-D9Ej5fM ()F } public final class com/revenuecat/purchases/slidetounlock/SlideToUnlockKt { - public static final fun SlideToUnlock-lbUUqPM (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Lcom/revenuecat/purchases/slidetounlock/HintTexts;Landroidx/compose/ui/graphics/Shape;JFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function7;Lkotlin/jvm/functions/Function8;Landroidx/compose/runtime/Composer;III)V + public static final fun SlideToUnlock-PVngBv4 (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Lcom/revenuecat/purchases/slidetounlock/HintTexts;Landroidx/compose/ui/graphics/Shape;JFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function1;Lcom/revenuecat/purchases/slidetounlock/SlideOrientation;Lkotlin/jvm/functions/Function8;Lkotlin/jvm/functions/Function9;Landroidx/compose/runtime/Composer;III)V } diff --git a/slidetounlock/api/desktop/slidetounlock.api b/slidetounlock/api/desktop/slidetounlock.api index c0a91c8..f2c51f6 100644 --- a/slidetounlock/api/desktop/slidetounlock.api +++ b/slidetounlock/api/desktop/slidetounlock.api @@ -56,6 +56,14 @@ public final class com/revenuecat/purchases/slidetounlock/HintTexts$Companion { public final fun defaultHintTexts ()Lcom/revenuecat/purchases/slidetounlock/HintTexts; } +public final class com/revenuecat/purchases/slidetounlock/SlideOrientation : java/lang/Enum { + public static final field Horizontal Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; + public static final field Vertical Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; + public static fun values ()[Lcom/revenuecat/purchases/slidetounlock/SlideOrientation; +} + public abstract interface class com/revenuecat/purchases/slidetounlock/SlideToUnlockColors { public abstract fun hintColor-XeAY9LY (FLandroidx/compose/runtime/Composer;I)J public abstract fun progressColor-WaAFU9c (Landroidx/compose/runtime/Composer;I)J @@ -70,14 +78,14 @@ public abstract interface class com/revenuecat/purchases/slidetounlock/SlideToUn public final class com/revenuecat/purchases/slidetounlock/SlideToUnlockDefaults { public static final field $stable I public static final field INSTANCE Lcom/revenuecat/purchases/slidetounlock/SlideToUnlockDefaults; - public final fun Hint (Lcom/revenuecat/purchases/slidetounlock/HintTexts;ZFLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V - public final fun Thumb-8HUqYh0 (ZJLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V + public final fun Hint (Lcom/revenuecat/purchases/slidetounlock/HintTexts;ZFLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Landroidx/compose/foundation/layout/PaddingValues;Lcom/revenuecat/purchases/slidetounlock/SlideOrientation;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V + public final fun Thumb-coD9juw (ZJLcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Lcom/revenuecat/purchases/slidetounlock/SlideOrientation;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V public final fun getPaddings-D9Ej5fM ()F public final fun getThumbSize-D9Ej5fM ()F public final fun getVelocityThreshold-D9Ej5fM ()F } public final class com/revenuecat/purchases/slidetounlock/SlideToUnlockKt { - public static final fun SlideToUnlock-lbUUqPM (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Lcom/revenuecat/purchases/slidetounlock/HintTexts;Landroidx/compose/ui/graphics/Shape;JFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function7;Lkotlin/jvm/functions/Function8;Landroidx/compose/runtime/Composer;III)V + public static final fun SlideToUnlock-PVngBv4 (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lcom/revenuecat/purchases/slidetounlock/SlideToUnlockColors;Lcom/revenuecat/purchases/slidetounlock/HintTexts;Landroidx/compose/ui/graphics/Shape;JFLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function1;Lcom/revenuecat/purchases/slidetounlock/SlideOrientation;Lkotlin/jvm/functions/Function8;Lkotlin/jvm/functions/Function9;Landroidx/compose/runtime/Composer;III)V } diff --git a/slidetounlock/src/commonMain/kotlin/com/revenuecat/purchases/slidetounlock/SlideToUnlock.kt b/slidetounlock/src/commonMain/kotlin/com/revenuecat/purchases/slidetounlock/SlideToUnlock.kt index d328ff4..7f6a3bc 100644 --- a/slidetounlock/src/commonMain/kotlin/com/revenuecat/purchases/slidetounlock/SlideToUnlock.kt +++ b/slidetounlock/src/commonMain/kotlin/com/revenuecat/purchases/slidetounlock/SlideToUnlock.kt @@ -23,9 +23,11 @@ import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding @@ -38,6 +40,7 @@ import androidx.compose.material.SwipeProgress import androidx.compose.material.SwipeableState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowRight +import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.rememberSwipeableState import androidx.compose.material.swipeable import androidx.compose.material3.CircularProgressIndicator @@ -74,22 +77,19 @@ import kotlin.math.roundToInt private enum class SlideToUnlockValue { Start, End } /** - * A composable that provides a "slide to unlock" UI element. + * A fully customizable "slide-to-unlock" UI component for Jetpack Compose. * - * This component displays a track with a draggable thumb. The user can slide the thumb - * from the start to the end to trigger an action. The component supports a loading state - * where the thumb is locked at the end and displays a progress indicator. + * Supports horizontal (default) and vertical orientations via [SlideOrientation]. * - * @param isSlided A boolean that indicates if the component should be in the loading state. - * When true, the thumb is locked at the end and shows a progress indicator. - * @param onSlideCompleted A lambda that is invoked when the user successfully slides the - * thumb to the end. - * @param modifier The [Modifier] to be applied to this component. - * @param colors The [SlideToUnlockColors] used to customize the appearance of the component. - * @param trackShape The [Shape] of the track. - * @param onSlideFractionChanged A callback that will be invoked whenever the swipe fraction is changed. - * @param thumb A composable lambda for the thumb that is dragged. By default, it uses [SlideToUnlockDefaults.Thumb]. - * @param hint A composable lambda for the hint text displayed on the track. By default, it uses [SlideToUnlockDefaults.Hint]. + * @param isSlided Whether the slider has been completed and locked. + * @param onSlideCompleted Invoked when the user successfully completes the slide gesture. + * @param orientation The direction of the sliding gesture. Defaults to [SlideOrientation.Horizontal]. + * @param onSlideFractionChanged Optional callback invoked with the current slide progress fraction (0f–1f). + * @param thumb A composable slot for customizing the draggable thumb. + * @param hint A composable slot for customizing the hint text or visuals inside the track. + * @param colors Provides the color scheme for the track, thumb, and hint. Defaults to [DefaultSlideToUnlockColors]. + * @param hintTexts Provides the hint messages (default and slided states). + * @param modifier Modifier to be applied to the layout. */ @Composable public fun SlideToUnlock( @@ -100,19 +100,21 @@ public fun SlideToUnlock( hintTexts: HintTexts = HintTexts.defaultHintTexts(), trackShape: Shape = RoundedCornerShape(percent = 50), thumbSize: DpSize = DpSize(ThumbSize, ThumbSize), - factionalThreshold: Float = 0.85f, + fractionalThreshold: Float = 0.85f, paddings: PaddingValues = PaddingValues(SlideToUnlockDefaults.Paddings), hintPaddings: PaddingValues = PaddingValues(start = thumbSize.width, end = thumbSize.width), onSlideFractionChanged: (Float) -> Unit = {}, - thumb: @Composable BoxScope.(isSlided: Boolean, slideFraction: Float, colors: SlideToUnlockColors, size: DpSize) -> Unit = { slided, _, _, size -> + orientation: SlideOrientation = SlideOrientation.Horizontal, + thumb: @Composable BoxScope.(isSlided: Boolean, slideFraction: Float, colors: SlideToUnlockColors, size: DpSize, orientation: SlideOrientation) -> Unit = { slided, _, _, size, orient -> SlideToUnlockDefaults.Thumb( modifier = Modifier.size(thumbSize), isSlided = slided, colors = colors, thumbSize = thumbSize, + orientation = orient, ) }, - hint: @Composable BoxScope.(isSlided: Boolean, slideFraction: Float, hintTexts: HintTexts, colors: SlideToUnlockColors, paddings: PaddingValues) -> Unit = { slided, fraction, _, _, paddings -> + hint: @Composable BoxScope.(isSlided: Boolean, slideFraction: Float, hintTexts: HintTexts, colors: SlideToUnlockColors, paddings: PaddingValues, orientation: SlideOrientation) -> Unit = { slided, fraction, _, _, paddings, orient -> SlideToUnlockDefaults.Hint( modifier = Modifier.align(Alignment.Center), slideFraction = fraction, @@ -120,6 +122,7 @@ public fun SlideToUnlock( isSlided = slided, colors = colors, paddingValues = hintPaddings, + orientation = orient, ) }, ) { @@ -156,7 +159,8 @@ public fun SlideToUnlock( trackShape = trackShape, thumbSize = thumbSize, paddingValues = paddings, - factionalThreshold = factionalThreshold, + factionalThreshold = fractionalThreshold, + orientation = orientation, ) { hint( isSlided, @@ -164,14 +168,18 @@ public fun SlideToUnlock( hintTexts, colors, hintPaddings, + orientation, ) Box( modifier = Modifier.offset { - IntOffset(swipeState.offset.value.roundToInt(), 0) + when (orientation) { + SlideOrientation.Horizontal -> IntOffset(swipeState.offset.value.roundToInt(), 0) + SlideOrientation.Vertical -> IntOffset(0, swipeState.offset.value.roundToInt()) + } }, ) { - thumb(isSlided, slideFraction, colors, thumbSize) + thumb(isSlided, slideFraction, colors, thumbSize, orientation) } } } @@ -204,6 +212,7 @@ private fun SlideToUnlockTrack( factionalThreshold: Float, thumbSize: DpSize, paddingValues: PaddingValues, + orientation: SlideOrientation, content: @Composable BoxScope.() -> Unit, ) { val density = LocalDensity.current @@ -213,18 +222,37 @@ private fun SlideToUnlockTrack( val startOfTrackPx = 0f var measuredWidth by remember { mutableIntStateOf(0) } - val endOfTrackPx = remember(measuredWidth) { + var measuredHeight by remember { mutableIntStateOf(0) } + + val endOfTrackPx = remember(measuredWidth, measuredHeight, orientation) { with(density) { - val totalPadding = paddingValues.calculateStartPadding(layoutDirection) + - paddingValues.calculateEndPadding(layoutDirection) - measuredWidth - (totalPadding + thumbSize.width).toPx() + when (orientation) { + SlideOrientation.Horizontal -> { + val totalPadding = paddingValues.calculateStartPadding(layoutDirection) + + paddingValues.calculateEndPadding(layoutDirection) + measuredWidth - (totalPadding + thumbSize.width).toPx() + } + SlideOrientation.Vertical -> { + val totalPadding = paddingValues.calculateTopPadding() + + paddingValues.calculateBottomPadding() + measuredHeight - (totalPadding + thumbSize.height).toPx() + } + } } } Box( modifier = modifier - .fillMaxWidth() - .onSizeChanged { measuredWidth = it.width } + .run { + when (orientation) { + SlideOrientation.Horizontal -> fillMaxWidth() + SlideOrientation.Vertical -> fillMaxHeight() + } + } + .onSizeChanged { + measuredWidth = it.width + measuredHeight = it.height + } .run { if (trackBrush != null) { background( @@ -242,7 +270,10 @@ private fun SlideToUnlockTrack( .swipeable( enabled = enabled, state = swipeState, - orientation = Orientation.Horizontal, + orientation = when (orientation) { + SlideOrientation.Horizontal -> Orientation.Horizontal + SlideOrientation.Vertical -> Orientation.Vertical + }, anchors = mapOf( startOfTrackPx to SlideToUnlockValue.Start, endOfTrackPx to SlideToUnlockValue.End, @@ -267,6 +298,7 @@ public object SlideToUnlockDefaults { * * @param isSlided Whether the component is in the loading state. * @param colors The [SlideToUnlockColors] used to customize the appearance of the component. + * @param orientation The slide orientation. * @param modifier The modifier to be applied to the thumb. */ @Composable @@ -274,12 +306,14 @@ public object SlideToUnlockDefaults { isSlided: Boolean, thumbSize: DpSize, colors: SlideToUnlockColors = DefaultSlideToUnlockColors(), + orientation: SlideOrientation = SlideOrientation.Horizontal, modifier: Modifier = Modifier, ) { Box( modifier = modifier .size(thumbSize) .background(color = colors.thumbColor(), shape = CircleShape), + contentAlignment = Alignment.Center, ) { if (isSlided) { CircularProgressIndicator( @@ -290,9 +324,15 @@ public object SlideToUnlockDefaults { } else { Icon( modifier = Modifier.align(Alignment.Center).size(40.dp), - imageVector = Icons.AutoMirrored.Filled.ArrowRight, + imageVector = when (orientation) { + SlideOrientation.Horizontal -> Icons.AutoMirrored.Filled.ArrowRight + SlideOrientation.Vertical -> Icons.Filled.KeyboardArrowDown + }, tint = colors.thumbIconColor(), - contentDescription = "Slide to unlock", + contentDescription = when (orientation) { + SlideOrientation.Horizontal -> "Slide to unlock" + SlideOrientation.Vertical -> "Slide down to unlock" + }, ) } } @@ -302,6 +342,7 @@ public object SlideToUnlockDefaults { * The default hint composable for the [SlideToUnlock] component. * * @param slideFraction The current progress of the swipe, from 0.0f to 1.0f. + * @param orientation The slide orientation. * @param modifier The modifier to be applied to the hint. */ @Composable @@ -311,32 +352,80 @@ public object SlideToUnlockDefaults { slideFraction: Float, colors: SlideToUnlockColors, paddingValues: PaddingValues, + orientation: SlideOrientation = SlideOrientation.Horizontal, modifier: Modifier = Modifier, ) { val layoutDirection = LocalLayoutDirection.current - AnimatedContent(modifier = modifier.fillMaxWidth(), targetState = isSlided) { slided -> + AnimatedContent( + modifier = modifier.run { + when (orientation) { + SlideOrientation.Horizontal -> fillMaxWidth() + SlideOrientation.Vertical -> fillMaxHeight() + } + }, + targetState = isSlided, + ) { slided -> if (!slided) { - Text( - modifier = Modifier.fillMaxWidth().padding( - start = paddingValues.calculateStartPadding( - layoutDirection, - ), - ), - text = hintTexts.defaultText, - textAlign = TextAlign.Center, - color = colors.hintColor(slideFraction), - style = MaterialTheme.typography.titleMedium, - ) + when (orientation) { + SlideOrientation.Horizontal -> { + Text( + modifier = Modifier.fillMaxWidth().padding( + start = paddingValues.calculateStartPadding(layoutDirection), + ), + text = hintTexts.defaultText, + textAlign = TextAlign.Center, + color = colors.hintColor(slideFraction), + style = MaterialTheme.typography.titleMedium, + ) + } + SlideOrientation.Vertical -> { + Column( + modifier = Modifier.fillMaxHeight().padding( + top = paddingValues.calculateTopPadding(), + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = hintTexts.defaultText, + textAlign = TextAlign.Center, + color = colors.hintColor(slideFraction), + style = MaterialTheme.typography.titleMedium, + ) + } + } + } } else { - Text( - modifier = Modifier.fillMaxWidth(), - text = hintTexts.slidedText, - textAlign = TextAlign.Center, - color = colors.slidedHintColor(), - style = MaterialTheme.typography.titleMedium, - ) + when (orientation) { + SlideOrientation.Horizontal -> { + Text( + modifier = Modifier.fillMaxWidth(), + text = hintTexts.slidedText, + textAlign = TextAlign.Center, + color = colors.slidedHintColor(), + style = MaterialTheme.typography.titleMedium, + ) + } + SlideOrientation.Vertical -> { + Column( + modifier = Modifier.fillMaxHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = hintTexts.slidedText, + textAlign = TextAlign.Center, + color = colors.slidedHintColor(), + style = MaterialTheme.typography.titleMedium, + ) + } + } + } } } } } + +public enum class SlideOrientation { + Horizontal, + Vertical, +}