From e0bb09e902053730bcaead355b20cb9952935cb7 Mon Sep 17 00:00:00 2001 From: GG Bond Date: Wed, 27 Dec 2023 23:21:36 +0800 Subject: [PATCH] Update compose snapshot, Resolve #413 --- .../compose/material3/ModalBottomSheet2.kt | 989 ------------------ .../compose/material3/NavigationDrawer2.kt | 4 +- .../ehviewer/ui/screen/GalleryDetailScreen.kt | 4 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 4 +- settings.gradle.kts | 2 +- 5 files changed, 8 insertions(+), 995 deletions(-) delete mode 100644 app/src/main/kotlin/androidx/compose/material3/ModalBottomSheet2.kt diff --git a/app/src/main/kotlin/androidx/compose/material3/ModalBottomSheet2.kt b/app/src/main/kotlin/androidx/compose/material3/ModalBottomSheet2.kt deleted file mode 100644 index 2c25a3a0c..000000000 --- a/app/src/main/kotlin/androidx/compose/material3/ModalBottomSheet2.kt +++ /dev/null @@ -1,989 +0,0 @@ -@file:Suppress("ktlint:standard:property-naming", "ktlint:standard:function-naming") - -package androidx.compose.material3 - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.PixelFormat -import android.os.Build -import android.view.Gravity -import android.view.View -import android.view.ViewTreeObserver -import android.view.WindowManager -import androidx.activity.OnBackPressedDispatcher -import androidx.activity.OnBackPressedDispatcherOwner -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.animation.core.SpringSpec -import androidx.compose.animation.core.TweenSpec -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.Canvas -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.gestures.detectTapGestures -import androidx.compose.foundation.gestures.snapTo -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionContext -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCompositionContext -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.isSpecified -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.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.AbstractComposeView -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.platform.ViewRootForInspector -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.popup -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.lerp -import androidx.compose.ui.window.SecureFlagPolicy -import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.findViewTreeViewModelStoreOwner -import androidx.lifecycle.setViewTreeLifecycleOwner -import androidx.lifecycle.setViewTreeViewModelStoreOwner -import androidx.savedstate.findViewTreeSavedStateRegistryOwner -import androidx.savedstate.setViewTreeSavedStateRegistryOwner -import com.hippo.ehviewer.ui.tools.animateFloatMergeOneWayPredictiveBackAsState -import com.hippo.ehviewer.util.isAtLeastT -import java.util.UUID -import kotlin.math.absoluteValue -import kotlin.math.max -import kotlin.math.roundToInt -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.launch - -@Stable -@ExperimentalMaterial3Api -internal object AnchoredDraggableDefaults2 { - /** - * The default animation used by [AnchoredDraggableState]. - */ - @get:ExperimentalMaterial3Api - @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET") - @ExperimentalMaterial3Api - val AnimationSpec = SpringSpec() -} - -/** - * State of a sheet composable, such as [ModalBottomSheetFix] - * - * Contains states relating to its swipe position as well as animations between state values. - * - * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large - * enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move - * to the [Hidden] state if available when hiding the sheet, either programmatically or by user - * interaction. - * @param initialValue The initial value of the state. - * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change. - * @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always - * expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either - * programmatically or by user interaction. - */ -@Stable -@ExperimentalMaterial3Api -class SheetState2 -@Deprecated( - message = "This constructor is deprecated. " + - "Please use the constructor that provides a [Density]", - replaceWith = ReplaceWith( - "SheetState(" + - "skipPartiallyExpanded, LocalDensity.current, initialValue, " + - "confirmValueChange, skipHiddenState)", - ), -) -constructor( - internal val skipPartiallyExpanded: Boolean, - initialValue: SheetValue = SheetValue.Hidden, - val confirmValueChange: (SheetValue) -> Boolean = { true }, - internal val skipHiddenState: Boolean = false, -) { - - /** - * State of a sheet composable, such as [ModalBottomSheetFix] - * - * Contains states relating to its swipe position as well as animations between state values. - * - * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large - * enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move - * to the [Hidden] state if available when hiding the sheet, either programmatically or by user - * interaction. - * @param initialValue The initial value of the state. - * @param density The density that this state can use to convert values to and from dp. - * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change. - * @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always - * expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either - * programmatically or by user interaction. - */ - @ExperimentalMaterial3Api - @Suppress("Deprecation") - constructor( - skipPartiallyExpanded: Boolean, - density: Density, - initialValue: SheetValue = SheetValue.Hidden, - confirmValueChange: (SheetValue) -> Boolean = { true }, - skipHiddenState: Boolean = false, - ) : this(skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState) { - this.density = density - } - init { - if (skipPartiallyExpanded) { - require(initialValue != SheetValue.PartiallyExpanded) { - "The initial value must not be set to PartiallyExpanded if skipPartiallyExpanded " + - "is set to true." - } - } - if (skipHiddenState) { - require(initialValue != SheetValue.Hidden) { - "The initial value must not be set to Hidden if skipHiddenState is set to true." - } - } - } - - /** - * The current value of the state. - * - * If no swipe or animation is in progress, this corresponds to the state the bottom sheet is - * currently in. If a swipe or an animation is in progress, this corresponds the state the sheet - * was in before the swipe or animation started. - */ - - val currentValue: SheetValue get() = anchoredDraggableState.currentValue - - /** - * The target value of the bottom sheet state. - * - * If a swipe is in progress, this is the value that the sheet would animate to if the - * swipe finishes. If an animation is running, this is the target value of that animation. - * Finally, if no swipe or animation is in progress, this is the same as the [currentValue]. - */ - val targetValue: SheetValue get() = anchoredDraggableState.targetValue - - /** - * Whether the modal bottom sheet is visible. - */ - val isVisible: Boolean - get() = anchoredDraggableState.currentValue != SheetValue.Hidden - - /** - * Require the current offset (in pixels) of the bottom sheet. - * - * The offset will be initialized during the first measurement phase of the provided sheet - * content. - * - * These are the phases: - * Composition { -> Effects } -> Layout { Measurement -> Placement } -> Drawing - * - * During the first composition, an [IllegalStateException] is thrown. In subsequent - * compositions, the offset will be derived from the anchors of the previous pass. Always prefer - * accessing the offset from a LaunchedEffect as it will be scheduled to be executed the next - * frame, after layout. - * - * @throws IllegalStateException If the offset has not been initialized yet - */ - fun requireOffset(): Float = anchoredDraggableState.requireOffset() - - /** - * Whether the sheet has an expanded state defined. - */ - - val hasExpandedState: Boolean - get() = anchoredDraggableState.anchors.hasAnchorFor(SheetValue.Expanded) - - /** - * Whether the modal bottom sheet has a partially expanded state defined. - */ - val hasPartiallyExpandedState: Boolean - get() = anchoredDraggableState.anchors.hasAnchorFor(SheetValue.PartiallyExpanded) - - /** - * Fully expand the bottom sheet with animation and suspend until it is fully expanded or - * animation has been cancelled. - * * - * @throws [CancellationException] if the animation is interrupted - */ - suspend fun expand() { - anchoredDraggableState.animateTo(SheetValue.Expanded) - } - - /** - * Animate the bottom sheet and suspend until it is partially expanded or animation has been - * cancelled. - * @throws [CancellationException] if the animation is interrupted - * @throws [IllegalStateException] if [skipPartiallyExpanded] is set to true - */ - suspend fun partialExpand() { - check(!skipPartiallyExpanded) { - "Attempted to animate to partial expanded when skipPartiallyExpanded was enabled. Set" + - " skipPartiallyExpanded to false to use this function." - } - animateTo(SheetValue.PartiallyExpanded) - } - - /** - * Expand the bottom sheet with animation and suspend until it is [PartiallyExpanded] if defined - * else [Expanded]. - * @throws [CancellationException] if the animation is interrupted - */ - suspend fun show() { - val targetValue = when { - hasPartiallyExpandedState -> SheetValue.PartiallyExpanded - else -> SheetValue.Expanded - } - animateTo(targetValue) - } - - /** - * Hide the bottom sheet with animation and suspend until it is fully hidden or animation has - * been cancelled. - * @throws [CancellationException] if the animation is interrupted - */ - suspend fun hide() { - check(!skipHiddenState) { - "Attempted to animate to hidden when skipHiddenState was enabled. Set skipHiddenState" + - " to false to use this function." - } - animateTo(SheetValue.Hidden) - } - - /** - * Animate to a [targetValue]. - * If the [targetValue] is not in the set of anchors, the [currentValue] will be updated to the - * [targetValue] without updating the offset. - * - * @throws CancellationException if the interaction interrupted by another interaction like a - * gesture interaction or another programmatic interaction like a [animateTo] or [snapTo] call. - * - * @param targetValue The target value of the animation - */ - internal suspend fun animateTo( - targetValue: SheetValue, - velocity: Float = anchoredDraggableState.lastVelocity, - ) { - anchoredDraggableState.animateTo(targetValue, velocity) - } - - /** - * Snap to a [targetValue] without any animation. - * - * @throws CancellationException if the interaction interrupted by another interaction like a - * gesture interaction or another programmatic interaction like a [animateTo] or [snapTo] call. - * - * @param targetValue The target value of the animation - */ - internal suspend fun snapTo(targetValue: SheetValue) { - anchoredDraggableState.snapTo(targetValue) - } - - /** - * Find the closest anchor taking into account the velocity and settle at it with an animation. - */ - internal suspend fun settle(velocity: Float) { - anchoredDraggableState.settle(velocity) - } - - internal var anchoredDraggableState = AnchoredDraggableState( - initialValue = initialValue, - animationSpec = AnchoredDraggableDefaults2.AnimationSpec, - confirmValueChange = confirmValueChange, - positionalThreshold = { with(requireDensity()) { 56.dp.toPx() } }, - velocityThreshold = { with(requireDensity()) { 125.dp.toPx() } }, - ) - - internal val offset: Float get() = anchoredDraggableState.offset - - internal var density: Density? = null - private fun requireDensity() = requireNotNull(density) { - "SheetState did not have a density attached. Are you using SheetState with " + - "BottomSheetScaffold or ModalBottomSheet component?" - } - - companion object { - /** - * The default [Saver] implementation for [SheetState2]. - */ - fun Saver( - skipPartiallyExpanded: Boolean, - confirmValueChange: (SheetValue) -> Boolean, - density: Density, - ) = Saver( - save = { it.currentValue }, - restore = { savedValue -> - SheetState2(skipPartiallyExpanded, density, savedValue, confirmValueChange) - }, - ) - - /** - * The default [Saver] implementation for [SheetState2]. - */ - @Deprecated( - message = "This function is deprecated. Please use the overload where Density is" + - " provided.", - replaceWith = ReplaceWith( - "Saver(skipPartiallyExpanded, confirmValueChange, LocalDensity.current)", - ), - ) - @Suppress("Deprecation") - fun Saver( - skipPartiallyExpanded: Boolean, - confirmValueChange: (SheetValue) -> Boolean, - ) = Saver( - save = { it.currentValue }, - restore = { savedValue -> - SheetState2(skipPartiallyExpanded, savedValue, confirmValueChange) - }, - ) - } -} - -/** - * Material Design modal bottom sheet. - * - * Modal bottom sheets are used as an alternative to inline menus or simple dialogs on mobile, - * especially when offering a long list of action items, or when items require longer descriptions - * and icons. Like dialogs, modal bottom sheets appear in front of app content, disabling all other - * app functionality when they appear, and remaining on screen until confirmed, dismissed, or a - * required action has been taken. - * - * ![Bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material3/bottom_sheet.png) - * - * A simple example of a modal bottom sheet looks like this: - * - * @sample androidx.compose.material3.samples.ModalBottomSheetSample - * - * @param onDismissRequest Executes when the user clicks outside of the bottom sheet, after sheet - * animates to [Hidden]. - * @param modifier Optional [Modifier] for the bottom sheet. - * @param sheetState The state of the bottom sheet. - * @param sheetMaxWidth [Dp] that defines what the maximum width the sheet will take. - * Pass in [Dp.Unspecified] for a sheet that spans the entire screen width. - * @param shape The shape of the bottom sheet. - * @param containerColor The color used for the background of this bottom sheet - * @param contentColor The preferred color for content inside this bottom sheet. Defaults to either - * the matching content color for [containerColor], or to the current [LocalContentColor] if - * [containerColor] is not a color from the theme. - * @param tonalElevation The tonal elevation of this bottom sheet. - * @param scrimColor Color of the scrim that obscures content when the bottom sheet is open. - * @param dragHandle Optional visual marker to swipe the bottom sheet. - * @param windowInsets window insets to be passed to the bottom sheet window via [PaddingValues] - * params. - * @param properties [ModalBottomSheetProperties2] for further customization of this - * modal bottom sheet's behavior. - * @param content The content to be displayed inside the bottom sheet. - */ -@Composable -@ExperimentalMaterial3Api -fun ModalBottomSheetFix( - onDismissRequest: () -> Unit, - modifier: Modifier = Modifier, - sheetState: SheetState2 = rememberModalBottomSheetState(), - sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth, - shape: Shape = BottomSheetDefaults.ExpandedShape, - containerColor: Color = BottomSheetDefaults.ContainerColor, - contentColor: Color = contentColorFor(containerColor), - tonalElevation: Dp = BottomSheetDefaults.Elevation, - scrimColor: Color = BottomSheetDefaults.ScrimColor, - dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() }, - windowInsets: WindowInsets = BottomSheetDefaults.windowInsets, - properties: ModalBottomSheetProperties2 = ModalBottomSheetDefaults2.properties(), - content: @Composable ColumnScope.() -> Unit, -) { - // b/291735717 Remove this once deprecated methods without density are removed - val density = LocalDensity.current - SideEffect { - sheetState.density = density - } - val scope = rememberCoroutineScope() - val animateToDismiss: () -> Unit = { - if (sheetState.confirmValueChange(SheetValue.Hidden)) { - scope.launch { sheetState.hide() }.invokeOnCompletion { - if (!sheetState.isVisible) { - onDismissRequest() - } - } - } - } - LaunchedEffect(sheetState) { - snapshotFlow { - sheetState.targetValue == SheetValue.Hidden && !sheetState.isVisible - }.drop(1).collectLatest { - if (it) onDismissRequest() - } - } - val settleToDismiss: (velocity: Float) -> Unit = { - scope.launch { sheetState.settle(it) }.invokeOnCompletion { - if (!sheetState.isVisible) onDismissRequest() - } - } - - val popupOnDismissRequest: () -> Unit = { - if (sheetState.currentValue == SheetValue.Expanded && sheetState.hasPartiallyExpandedState) { - scope.launch { sheetState.partialExpand() } - } else { // Is expanded without collapsed state or is collapsed. - scope.launch { sheetState.hide() }.invokeOnCompletion { onDismissRequest() } - } - } - - ModalBottomSheetPopup( - properties = properties, - onDismissRequest = popupOnDismissRequest, - windowInsets = windowInsets, - ) { - val configuration = LocalConfiguration.current - val widthDp = configuration.screenWidthDp.dp - val height = configuration.screenHeightDp.dp - val predictiveState by animateFloatMergeOneWayPredictiveBackAsState(true) { - sheetState.hide() - onDismissRequest() - } - val predictiveModifier = Modifier.graphicsLayer { - val scale = lerp(1f, 0.90f, predictiveState.absoluteValue) - scaleX = scale - scaleY = scale - }.offset { - val ofsY = lerp(0f, height.toPx() * 0.0625f, predictiveState.absoluteValue).roundToInt() - val ofsX = lerp(0f, widthDp.toPx() * 0.025f, predictiveState).roundToInt() - IntOffset(ofsX, ofsY) - } - BoxWithConstraints(Modifier.fillMaxSize()) { - val fullHeight = constraints.maxHeight - Scrim( - color = scrimColor, - onDismissRequest = animateToDismiss, - visible = sheetState.targetValue != SheetValue.Hidden, - ) - Surface( - modifier = modifier - .widthIn(max = sheetMaxWidth) - .fillMaxWidth() - .align(Alignment.TopCenter) - .offset { - IntOffset( - 0, - sheetState - .requireOffset() - .toInt(), - ) - } then predictiveModifier - .nestedScroll( - remember(sheetState) { - ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( - sheetState = sheetState, - orientation = Orientation.Vertical, - onFling = settleToDismiss, - ) - }, - ) - .anchoredDraggable( - state = sheetState.anchoredDraggableState, - orientation = Orientation.Vertical, - enabled = sheetState.isVisible, - startDragImmediately = sheetState.anchoredDraggableState.isAnimationRunning, - ) - .modalBottomSheetAnchors( - sheetState = sheetState, - fullHeight = fullHeight.toFloat(), - ), - shape = shape, - color = containerColor, - contentColor = contentColor, - tonalElevation = tonalElevation, - ) { - Column(Modifier.fillMaxWidth()) { - if (dragHandle != null) { - Box(Modifier.align(Alignment.CenterHorizontally)) { - dragHandle() - } - } - content() - } - } - } - } - if (sheetState.hasExpandedState) { - LaunchedEffect(sheetState) { - sheetState.show() - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -internal fun ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( - sheetState: SheetState2, - orientation: Orientation, - onFling: (velocity: Float) -> Unit, -): NestedScrollConnection = object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val delta = available.toFloat() - return if (delta < 0 && source == NestedScrollSource.Drag) { - sheetState.anchoredDraggableState.dispatchRawDelta(delta).toOffset() - } else { - Offset.Zero - } - } - - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource, - ): Offset { - return if (source == NestedScrollSource.Drag) { - sheetState.anchoredDraggableState.dispatchRawDelta(available.toFloat()).toOffset() - } else { - Offset.Zero - } - } - - override suspend fun onPreFling(available: Velocity): Velocity { - val toFling = available.toFloat() - val currentOffset = sheetState.requireOffset() - val minAnchor = sheetState.anchoredDraggableState.anchors.minAnchor() - return if (toFling < 0 && currentOffset > minAnchor) { - onFling(toFling) - // since we go to the anchor with tween settling, consume all for the best UX - available - } else { - Velocity.Zero - } - } - - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - onFling(available.toFloat()) - return available - } - - private fun Float.toOffset(): Offset = Offset( - x = if (orientation == Orientation.Horizontal) this else 0f, - y = if (orientation == Orientation.Vertical) this else 0f, - ) - - @JvmName("velocityToFloat") - private fun Velocity.toFloat() = if (orientation == Orientation.Horizontal) x else y - - @JvmName("offsetToFloat") - private fun Offset.toFloat(): Float = if (orientation == Orientation.Horizontal) x else y -} - -/** - * Properties used to customize the behavior of a [ModalBottomSheetFix]. - * - * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom - * sheet's window. - * @param isFocusable Whether the modal bottom sheet is focusable. When true, - * the modal bottom sheet will receive IME events and key presses, such as when - * the back button is pressed. - * @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing - * the back button. If true, pressing the back button will call onDismissRequest. - * Note that [isFocusable] must be set to true in order to receive key events such as - * the back button - if the modal bottom sheet is not focusable then this property does nothing. - */ -@ExperimentalMaterial3Api -class ModalBottomSheetProperties2( - val securePolicy: SecureFlagPolicy, - val isFocusable: Boolean, - val shouldDismissOnBackPress: Boolean, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ModalBottomSheetProperties2) return false - - if (securePolicy != other.securePolicy) return false - if (isFocusable != other.isFocusable) return false - if (shouldDismissOnBackPress != other.shouldDismissOnBackPress) return false - - return true - } - - override fun hashCode(): Int { - var result = securePolicy.hashCode() - result = 31 * result + isFocusable.hashCode() - result = 31 * result + shouldDismissOnBackPress.hashCode() - return result - } -} - -/** - * Default values for [ModalBottomSheetFix] - */ -@Immutable -@ExperimentalMaterial3Api -object ModalBottomSheetDefaults2 { - /** - * Properties used to customize the behavior of a [ModalBottomSheetFix]. - * - * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom - * sheet's window. - * @param isFocusable Whether the modal bottom sheet is focusable. When true, - * the modal bottom sheet will receive IME events and key presses, such as when - * the back button is pressed. - * @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing - * the back button. If true, pressing the back button will call onDismissRequest. - * Note that [isFocusable] must be set to true in order to receive key events such as - * the back button - if the modal bottom sheet is not focusable then this property does nothing. - */ - fun properties( - securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit, - isFocusable: Boolean = true, - shouldDismissOnBackPress: Boolean = true, - ) = ModalBottomSheetProperties2(securePolicy, isFocusable, shouldDismissOnBackPress) -} - -/** - * Create and [remember] a [SheetState2] for [ModalBottomSheetFix]. - * - * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is tall enough, - * should be skipped. If true, the sheet will always expand to the [Expanded] state and move to the - * [Hidden] state when hiding the sheet, either programmatically or by user interaction. - * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change. - */ -@Composable -@ExperimentalMaterial3Api -fun rememberModalBottomSheetState( - skipPartiallyExpanded: Boolean = false, - confirmValueChange: (SheetValue) -> Boolean = { true }, -) = rememberSheetState(skipPartiallyExpanded, confirmValueChange, SheetValue.Hidden) - -@Composable -@ExperimentalMaterial3Api -internal fun rememberSheetState( - skipPartiallyExpanded: Boolean = false, - confirmValueChange: (SheetValue) -> Boolean = { true }, - initialValue: SheetValue = SheetValue.Hidden, - skipHiddenState: Boolean = false, -): SheetState2 { - val density = LocalDensity.current - return rememberSaveable( - skipPartiallyExpanded, - confirmValueChange, - saver = SheetState2.Saver( - skipPartiallyExpanded = skipPartiallyExpanded, - confirmValueChange = confirmValueChange, - density = density, - ), - ) { - SheetState2( - skipPartiallyExpanded, - density, - initialValue, - confirmValueChange, - skipHiddenState, - ) - } -} - -@Composable -private fun Scrim( - color: Color, - onDismissRequest: () -> Unit, - visible: Boolean, -) { - if (color.isSpecified) { - val alpha by animateFloatAsState( - targetValue = if (visible) 1f else 0f, - animationSpec = TweenSpec(), - label = "scrim", - ) - val dismissSheet = if (visible) { - Modifier - .pointerInput(onDismissRequest) { - detectTapGestures { - onDismissRequest() - } - } - .clearAndSetSemantics {} - } else { - Modifier - } - Canvas( - Modifier - .fillMaxSize() - .then(dismissSheet), - ) { - drawRect(color = color, alpha = alpha) - } - } -} - -@ExperimentalMaterial3Api -private fun Modifier.modalBottomSheetAnchors( - sheetState: SheetState2, - fullHeight: Float, -) = onSizeChanged { sheetSize -> - - val newAnchors = DraggableAnchors { - SheetValue.Hidden at fullHeight - if (sheetSize.height > (fullHeight / 2) && !sheetState.skipPartiallyExpanded) { - SheetValue.PartiallyExpanded at fullHeight / 2f - } - if (sheetSize.height != 0) { - SheetValue.Expanded at max(0f, fullHeight - sheetSize.height) - } - } - - val newTarget = when (sheetState.anchoredDraggableState.targetValue) { - SheetValue.Hidden -> SheetValue.Hidden - SheetValue.PartiallyExpanded, SheetValue.Expanded -> { - val hasPartiallyExpandedState = newAnchors.hasAnchorFor(SheetValue.PartiallyExpanded) - val newTarget = if (hasPartiallyExpandedState) { - SheetValue.PartiallyExpanded - } else if (newAnchors.hasAnchorFor(SheetValue.Expanded)) SheetValue.Expanded else SheetValue.Hidden - newTarget - } - } - - sheetState.anchoredDraggableState.updateAnchors(newAnchors, newTarget) -} - -/** - * Popup specific for modal bottom sheet. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun ModalBottomSheetPopup( - properties: ModalBottomSheetProperties2, - onDismissRequest: () -> Unit, - windowInsets: WindowInsets, - content: @Composable () -> Unit, -) { - val view = LocalView.current - val id = rememberSaveable { UUID.randomUUID() } - val parentComposition = rememberCompositionContext() - val currentContent by rememberUpdatedState(content) - val layoutDirection = LocalLayoutDirection.current - val configuration = LocalConfiguration.current - val modalBottomSheetWindow = remember(configuration) { - ModalBottomSheetWindow2( - properties = properties, - onDismissRequest = onDismissRequest, - composeView = view, - saveId = id, - ).apply { - setCustomContent( - parent = parentComposition, - content = { - Box( - Modifier - .semantics { this.popup() } - .windowInsetsPadding(windowInsets) - .then( - // TODO(b/290893168): Figure out a solution for APIs < 30. - if (Build.VERSION.SDK_INT >= 33) { - Modifier.imePadding() - } else { - Modifier - }, - ), - ) { - currentContent() - } - }, - ) - } - } - - DisposableEffect(modalBottomSheetWindow) { - modalBottomSheetWindow.show() - modalBottomSheetWindow.superSetLayoutDirection(layoutDirection) - onDispose { - modalBottomSheetWindow.disposeComposition() - modalBottomSheetWindow.dismiss() - } - } -} - -/** Custom compose view for [ModalBottomSheetFix] */ -@SuppressLint("ViewConstructor") -@OptIn(ExperimentalMaterial3Api::class) -private class ModalBottomSheetWindow2( - private val properties: ModalBottomSheetProperties2, - private var onDismissRequest: () -> Unit, - private val composeView: View, - saveId: UUID, -) : AbstractComposeView(composeView.context), - ViewTreeObserver.OnGlobalLayoutListener, - ViewRootForInspector, - OnBackPressedDispatcherOwner { - - init { - id = android.R.id.content - // Set up view owners - setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner()) - setViewTreeViewModelStoreOwner(composeView.findViewTreeViewModelStoreOwner()) - setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner()) - setTag(androidx.compose.ui.R.id.compose_view_saveable_id_tag, "Popup:$saveId") - // Enable children to draw their shadow by not clipping them - clipChildren = false - } - - override val lifecycle = composeView.findViewTreeLifecycleOwner()!!.lifecycle - override val onBackPressedDispatcher = OnBackPressedDispatcher { - onDismissRequest() - } - - private val windowManager = - composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - - private val displayWidth: Int - get() = context.resources.displayMetrics.widthPixels - - private val params: WindowManager.LayoutParams = - WindowManager.LayoutParams().apply { - // Position bottom sheet from the bottom of the screen - gravity = Gravity.BOTTOM or Gravity.START - // Application panel window - type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL - // Fill up the entire app view - width = displayWidth - height = WindowManager.LayoutParams.MATCH_PARENT - - // Format of screen pixels - format = PixelFormat.TRANSLUCENT - // Title used as fallback for a11y services - // TODO: Provide bottom sheet window resource - title = composeView.context.resources.getString( - androidx.compose.ui.R.string.default_popup_window_title, - ) - // Get the Window token from the parent view - token = composeView.applicationWindowToken - - // Flags specific to modal bottom sheet. - flags = flags and ( - WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - ).inv() - - flags = flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - - // Security flag - val secureFlagEnabled = - properties.securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled()) - if (secureFlagEnabled) { - flags = flags or WindowManager.LayoutParams.FLAG_SECURE - } else { - flags = flags and (WindowManager.LayoutParams.FLAG_SECURE.inv()) - } - - // Focusable - if (!properties.isFocusable) { - flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - } else { - flags = flags and (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()) - } - } - - private var content: @Composable () -> Unit by mutableStateOf({}) - - override var shouldCreateCompositionOnAttachedToWindow: Boolean = false - private set - - @Composable - override fun Content() { - CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides this) { - content() - } - } - - fun setCustomContent( - parent: CompositionContext? = null, - content: @Composable () -> Unit, - ) { - parent?.let { setParentCompositionContext(it) } - this.content = content - shouldCreateCompositionOnAttachedToWindow = true - } - - fun show() { - windowManager.addView(this, params) - if (isAtLeastT) { - onBackPressedDispatcher.setOnBackInvokedDispatcher(findOnBackInvokedDispatcher()!!) - } - } - - fun dismiss() { - setViewTreeLifecycleOwner(null) - setViewTreeSavedStateRegistryOwner(null) - composeView.viewTreeObserver.removeOnGlobalLayoutListener(this) - windowManager.removeViewImmediate(this) - } - - override fun onGlobalLayout() { - // No-op - } - - override fun setLayoutDirection(layoutDirection: Int) { - // Do nothing. ViewRootImpl will call this method attempting to set the layout direction - // from the context's locale, but we have one already from the parent composition. - } - - // Sets the "real" layout direction for our content that we obtain from the parent composition. - fun superSetLayoutDirection(layoutDirection: LayoutDirection) { - val direction = when (layoutDirection) { - LayoutDirection.Ltr -> android.util.LayoutDirection.LTR - LayoutDirection.Rtl -> android.util.LayoutDirection.RTL - } - super.setLayoutDirection(direction) - } -} - -// Taken from AndroidPopup.android.kt -private fun View.isFlagSecureEnabled(): Boolean { - val windowParams = rootView.layoutParams as? WindowManager.LayoutParams - if (windowParams != null) { - return (windowParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0 - } - return false -} - -// Taken from AndroidPopup.android.kt -private fun SecureFlagPolicy.shouldApplySecureFlag(isSecureFlagSetOnParent: Boolean): Boolean { - return when (this) { - SecureFlagPolicy.SecureOff -> false - SecureFlagPolicy.SecureOn -> true - SecureFlagPolicy.Inherit -> isSecureFlagSetOnParent - } -} diff --git a/app/src/main/kotlin/androidx/compose/material3/NavigationDrawer2.kt b/app/src/main/kotlin/androidx/compose/material3/NavigationDrawer2.kt index efd7e0e04..3f12d404f 100644 --- a/app/src/main/kotlin/androidx/compose/material3/NavigationDrawer2.kt +++ b/app/src/main/kotlin/androidx/compose/material3/NavigationDrawer2.kt @@ -5,6 +5,7 @@ package androidx.compose.material3 import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.animate +import androidx.compose.animation.core.exponentialDecay import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.gestures.AnchoredDraggableState @@ -73,7 +74,8 @@ class DrawerState2( val anchoredDraggableState = AnchoredDraggableState( initialValue = initialValue, - animationSpec = AnimationSpec, + snapAnimationSpec = AnimationSpec, + decayAnimationSpec = exponentialDecay(), confirmValueChange = confirmStateChange, positionalThreshold = { distance -> distance * DrawerPositionalThreshold }, velocityThreshold = { with(requireDensity()) { DrawerVelocityThreshold.toPx() } }, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt index 38cd905b1..18b9055d6 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt @@ -49,7 +49,7 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar -import androidx.compose.material3.ModalBottomSheetFix +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text @@ -955,7 +955,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa var showBottomSheet by remember { mutableStateOf(false) } if (showBottomSheet && galleryDetail != null) { - ModalBottomSheetFix( + ModalBottomSheet( onDismissRequest = { showBottomSheet = false }, windowInsets = WindowInsets(0, 0, 0, 0), ) { diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index d52638fa2..c1ed7e842 100644 --- a/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -46,7 +46,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheetFix +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -736,7 +736,7 @@ class ReaderActivity : EhActivity() { val coroutineScope = rememberCoroutineScope() fun dismiss() = it.cancel() val sheetState = rememberModalBottomSheetState() - ModalBottomSheetFix( + ModalBottomSheet( onDismissRequest = { dismiss() }, sheetState = sheetState, windowInsets = WindowInsets(0), diff --git a/settings.gradle.kts b/settings.gradle.kts index 68480440a..f5f567309 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://jitpack.io") maven("https://androidx.dev/storage/compose-compiler/repository/") - maven("https://androidx.dev/snapshots/builds/11212115/artifacts/repository") + maven("https://androidx.dev/snapshots/builds/11256865/artifacts/repository") } }