AdaptiveSheet: Migrate deprecated swipeable (#9642)

This commit is contained in:
Ivan Iskandar 2023-06-27 09:20:08 +07:00 committed by GitHub
parent 8a5e443ca5
commit 7c90fe0f7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -4,8 +4,13 @@ import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.gestures.animateTo
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
@ -18,9 +23,6 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.SwipeableState
import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -38,6 +40,8 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
@ -48,8 +52,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
private const val SheetAnimationDuration = 350 private val sheetAnimationSpec = tween<Float>(durationMillis = 350)
private val SheetAnimationSpec = tween<Float>(durationMillis = SheetAnimationDuration)
@Composable @Composable
fun AdaptiveSheet( fun AdaptiveSheet(
@ -59,12 +62,13 @@ fun AdaptiveSheet(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
val density = LocalDensity.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
if (isTabletUi) { if (isTabletUi) {
var targetAlpha by remember { mutableFloatStateOf(0f) } var targetAlpha by remember { mutableFloatStateOf(0f) }
val alpha by animateFloatAsState( val alpha by animateFloatAsState(
targetValue = targetAlpha, targetValue = targetAlpha,
animationSpec = SheetAnimationSpec, animationSpec = sheetAnimationSpec,
) )
val internalOnDismissRequest: () -> Unit = { val internalOnDismissRequest: () -> Unit = {
scope.launch { scope.launch {
@ -107,23 +111,36 @@ fun AdaptiveSheet(
} }
} }
} else { } else {
val swipeState = rememberSwipeableState( val anchoredDraggableState = remember {
initialValue = 1, AnchoredDraggableState(
animationSpec = SheetAnimationSpec, initialValue = 1,
) animationSpec = sheetAnimationSpec,
val internalOnDismissRequest: () -> Unit = { if (swipeState.currentValue == 0) scope.launch { swipeState.animateTo(1) } } positionalThreshold = { with(density) { 56.dp.toPx() } },
BoxWithConstraints( velocityThreshold = { with(density) { 125.dp.toPx() } },
)
}
val internalOnDismissRequest = {
if (anchoredDraggableState.currentValue == 0) {
scope.launch { anchoredDraggableState.animateTo(1) }
}
}
Box(
modifier = Modifier modifier = Modifier
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
onClick = internalOnDismissRequest, onClick = internalOnDismissRequest,
) )
.fillMaxSize(), .fillMaxSize()
.onSizeChanged {
val anchors = DraggableAnchors {
0 at 0f
1 at it.height.toFloat()
}
anchoredDraggableState.updateAnchors(anchors)
},
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
) { ) {
val fullHeight = constraints.maxHeight.toFloat()
val anchors = mapOf(0f to 0, fullHeight to 1)
Surface( Surface(
modifier = Modifier modifier = Modifier
.widthIn(max = 460.dp) .widthIn(max = 460.dp)
@ -132,26 +149,27 @@ fun AdaptiveSheet(
indication = null, indication = null,
onClick = {}, onClick = {},
) )
.nestedScroll( .then(
remember(enableSwipeDismiss, anchors) { if (enableSwipeDismiss) {
swipeState.preUpPostDownNestedScrollConnection( Modifier.nestedScroll(
enabled = enableSwipeDismiss, remember(anchoredDraggableState) {
anchor = anchors, anchoredDraggableState.preUpPostDownNestedScrollConnection()
},
) )
} else {
Modifier
}, },
) )
.offset { .offset {
IntOffset( IntOffset(
0, 0,
swipeState.offset.value.roundToInt(), anchoredDraggableState.offset.takeIf { it.isFinite() }?.roundToInt() ?: 0,
) )
} }
.swipeable( .anchoredDraggable(
enabled = enableSwipeDismiss, state = anchoredDraggableState,
state = swipeState,
anchors = anchors,
orientation = Orientation.Vertical, orientation = Orientation.Vertical,
resistance = null, enabled = enableSwipeDismiss,
) )
.windowInsetsPadding( .windowInsetsPadding(
WindowInsets.systemBars WindowInsets.systemBars
@ -160,14 +178,14 @@ fun AdaptiveSheet(
shape = MaterialTheme.shapes.extraLarge, shape = MaterialTheme.shapes.extraLarge,
tonalElevation = tonalElevation, tonalElevation = tonalElevation,
content = { content = {
BackHandler(enabled = swipeState.targetValue == 0, onBack = internalOnDismissRequest) BackHandler(enabled = anchoredDraggableState.targetValue == 0, onBack = internalOnDismissRequest)
content() content()
}, },
) )
LaunchedEffect(swipeState) { LaunchedEffect(anchoredDraggableState) {
scope.launch { swipeState.animateTo(0) } scope.launch { anchoredDraggableState.animateTo(0) }
snapshotFlow { swipeState.currentValue } snapshotFlow { anchoredDraggableState.currentValue }
.drop(1) .drop(1)
.filter { it == 1 } .filter { it == 1 }
.collectLatest { .collectLatest {
@ -178,17 +196,11 @@ fun AdaptiveSheet(
} }
} }
/** private fun <T> AnchoredDraggableState<T>.preUpPostDownNestedScrollConnection() = object : NestedScrollConnection {
* Yoinked from Swipeable.kt with modifications to disable
*/
private fun <T> SwipeableState<T>.preUpPostDownNestedScrollConnection(
enabled: Boolean = true,
anchor: Map<Float, T>,
) = object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.toFloat() val delta = available.toFloat()
return if (enabled && delta < 0 && source == NestedScrollSource.Drag) { return if (delta < 0 && source == NestedScrollSource.Drag) {
performDrag(delta).toOffset() dispatchRawDelta(delta).toOffset()
} else { } else {
Offset.Zero Offset.Zero
} }
@ -199,17 +211,17 @@ private fun <T> SwipeableState<T>.preUpPostDownNestedScrollConnection(
available: Offset, available: Offset,
source: NestedScrollSource, source: NestedScrollSource,
): Offset { ): Offset {
return if (enabled && source == NestedScrollSource.Drag) { return if (source == NestedScrollSource.Drag) {
performDrag(available.toFloat()).toOffset() dispatchRawDelta(available.toFloat()).toOffset()
} else { } else {
Offset.Zero Offset.Zero
} }
} }
override suspend fun onPreFling(available: Velocity): Velocity { override suspend fun onPreFling(available: Velocity): Velocity {
val toFling = Offset(available.x, available.y).toFloat() val toFling = available.toFloat()
return if (enabled && toFling < 0 && offset.value > anchor.keys.minOrNull()!!) { return if (toFling < 0 && offset > anchors.minAnchor()) {
performFling(velocity = toFling) settle(toFling)
// since we go to the anchor with tween settling, consume all for the best UX // since we go to the anchor with tween settling, consume all for the best UX
available available
} else { } else {
@ -218,15 +230,15 @@ private fun <T> SwipeableState<T>.preUpPostDownNestedScrollConnection(
} }
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
return if (enabled) { settle(velocity = available.toFloat())
performFling(velocity = Offset(available.x, available.y).toFloat()) return available
available
} else {
Velocity.Zero
}
} }
private fun Float.toOffset(): Offset = Offset(0f, this) private fun Float.toOffset(): Offset = Offset(0f, this)
@JvmName("velocityToFloat")
private fun Velocity.toFloat() = this.y
@JvmName("offsetToFloat")
private fun Offset.toFloat(): Float = this.y private fun Offset.toFloat(): Float = this.y
} }