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