Kaynağa Gözat

AdaptiveSheet: Migrate deprecated swipeable (#9642)

Ivan Iskandar 1 yıl önce
ebeveyn
işleme
7c90fe0f7d

+ 62 - 50
presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt

@@ -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
 }