diff options
3 files changed, 65 insertions, 43 deletions
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index ae75e6c089ca..d79fbd608b7b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -39,7 +39,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf @@ -175,21 +174,7 @@ fun Expandable( val wrappedContent = remember(content) { movableContentOf { expandable: Expandable -> - CompositionLocalProvider(LocalContentColor provides contentColor) { - // We make sure that the content itself (wrapped by the background) is at least - // 40.dp, which is the same as the M3 buttons. This applies even if onClick is - // null, to make it easier to write expandables that are sometimes clickable and - // sometimes not. There shouldn't be any Expandable smaller than 40dp because if - // the expandable is not clickable directly, then something in its content - // should be (and with a size >= 40dp). - val minSize = 40.dp - Box( - Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), - contentAlignment = Alignment.Center, - ) { - content(expandable) - } - } + WrappedContent(expandable, contentColor, content) } } @@ -209,11 +194,7 @@ fun Expandable( // Make sure we don't read animatorState directly here to avoid recomposition every time the // state changes (i.e. every frame of the animation). - val isAnimating by remember { - derivedStateOf { - controller.animatorState.value != null && controller.overlay.value != null - } - } + val isAnimating = controller.isAnimating // If this expandable is expanded when it's being directly clicked on, let's ensure that it has // the minimum interactive size followed by all M3 components (48.dp). @@ -262,28 +243,11 @@ fun Expandable( } } else -> { - val clickModifier = - if (onClick != null) { - if (interactionSource != null) { - // If the caller provided an interaction source, then that means that they - // will draw the click indication themselves. - Modifier.clickable(interactionSource, indication = null) { - onClick(controller.expandable) - } - } else { - // If no interaction source is provided, we draw the default indication (a - // ripple) and make sure it's clipped by the expandable shape. - Modifier.clip(shape).clickable { onClick(controller.expandable) } - } - } else { - Modifier - } - Box( modifier .updateExpandableSize() .then(minInteractiveSizeModifier) - .then(clickModifier) + .then(clickModifier(controller, onClick, interactionSource)) .background(color, shape) .border(controller) .onGloballyPositioned { @@ -296,6 +260,50 @@ fun Expandable( } } +@Composable +private fun WrappedContent( + expandable: Expandable, + contentColor: Color, + content: @Composable (Expandable) -> Unit, +) { + CompositionLocalProvider(LocalContentColor provides contentColor) { + // We make sure that the content itself (wrapped by the background) is at least 40.dp, which + // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to + // write expandables that are sometimes clickable and sometimes not. There shouldn't be any + // Expandable smaller than 40dp because if the expandable is not clickable directly, then + // something in its content should be (and with a size >= 40dp). + val minSize = 40.dp + Box( + Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), + contentAlignment = Alignment.Center, + ) { + content(expandable) + } + } +} + +private fun clickModifier( + controller: ExpandableControllerImpl, + onClick: ((Expandable) -> Unit)?, + interactionSource: MutableInteractionSource?, +): Modifier { + if (onClick == null) { + return Modifier + } + + if (interactionSource != null) { + // If the caller provided an interaction source, then that means that they will draw the + // click indication themselves. + return Modifier.clickable(interactionSource, indication = null) { + onClick(controller.expandable) + } + } + + // If no interaction source is provided, we draw the default indication (a ripple) and make sure + // it's clipped by the expandable shape. + return Modifier.clip(controller.shape).clickable { onClick(controller.expandable) } +} + /** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */ @Composable private fun AnimatedContentInOverlay( diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index c5d2802c8941..16150f813b05 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -27,6 +27,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset @@ -53,6 +55,9 @@ interface ExpandableController { /** The [Expandable] controlled by this controller. */ val expandable: Expandable + /** Whether this controller is currently animating a launch. */ + val isAnimating: Boolean + /** Called when the [Expandable] stop being included in the composition. */ fun onDispose() } @@ -182,6 +187,10 @@ internal class ExpandableControllerImpl( } } + override val isAnimating: Boolean by derivedStateOf { + animatorState.value != null && overlay.value != null + } + override fun onDispose() { activityControllerForDisposal?.onDispose() activityControllerForDisposal = null diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt index f5c3a834a8d7..089da4b932b2 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt @@ -42,13 +42,19 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner @Composable fun Modifier.drawInOverlay(): Modifier { val containerState = remember { ContainerState() } + FullScreenComposeViewInOverlay { Modifier.container(containerState) } + return this.drawInContainer(containerState, enabled = { true }) +} + +@Composable +internal fun FullScreenComposeViewInOverlay(modifier: (ComposeView) -> Modifier = { Modifier }) { val context = LocalContext.current val localView = LocalView.current val compositionContext = rememberCompositionContext() val displayMetrics = context.resources.displayMetrics val displaySize = IntSize(displayMetrics.widthPixels, displayMetrics.heightPixels) - DisposableEffect(containerState, context, localView, compositionContext, displaySize) { + DisposableEffect(context, localView, compositionContext, displaySize) { val overlay = localView.rootView.overlay as ViewGroupOverlay val view = ComposeView(context).apply { @@ -59,7 +65,8 @@ fun Modifier.drawInOverlay(): Modifier { setViewTreeViewModelStoreOwner(localView.findViewTreeViewModelStoreOwner()) setViewTreeSavedStateRegistryOwner(localView.findViewTreeSavedStateRegistryOwner()) - setContent { Box(Modifier.fillMaxSize().container(containerState)) } + val view = this + setContent { Box(modifier(view).fillMaxSize()) } } overlay.add(view) @@ -74,6 +81,4 @@ fun Modifier.drawInOverlay(): Modifier { onDispose { overlay.remove(view) } } - - return this.drawInContainer(containerState, enabled = { true }) } |