Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a53ba59a authored by Luca Zuccarini's avatar Luca Zuccarini Committed by Android (Google) Code Review
Browse files

Merge "Introduce ComposableControllerFactory." into main

parents 4204387b 39a6efe9
Loading
Loading
Loading
Loading
+60 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.animation

import android.content.ComponentName
import android.util.Log
import com.android.systemui.animation.ActivityTransitionAnimator.Controller
import com.android.systemui.animation.ActivityTransitionAnimator.ControllerFactory
import kotlinx.coroutines.flow.MutableStateFlow

private const val TAG = "ComposableControllerFactory"

/**
 * [ControllerFactory] extension for Compose. Since composables are not guaranteed to be part of the
 * composition when [ControllerFactory.createController] is called, this class provides a way for
 * the composable to register itself at the time of composition, and deregister itself when
 * disposed.
 */
abstract class ComposableControllerFactory(
    cookie: ActivityTransitionAnimator.TransitionCookie,
    component: ComponentName?,
    launchCujType: Int? = null,
    returnCujType: Int? = null,
) : ControllerFactory(cookie, component, launchCujType, returnCujType) {
    /**
     * The object to be used to create [Controller]s, when its associate composable is in the
     * composition.
     */
    protected val expandable = MutableStateFlow<Expandable?>(null)

    /** To be called when the composable to be animated enters composition. */
    fun onCompose(expandable: Expandable) {
        if (TransitionAnimator.DEBUG) {
            Log.d(TAG, "Composable entered composition (expandable=$expandable")
        }
        this.expandable.value = expandable
    }

    /** To be called when the composable to be animated exits composition. */
    fun onDispose() {
        if (TransitionAnimator.DEBUG) {
            Log.d(TAG, "Composable left composition (expandable=${this.expandable.value}")
        }
        this.expandable.value = null
    }
}
+24 −1
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay
import com.android.systemui.animation.ComposableControllerFactory
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.TransitionAnimator
import kotlin.math.max
@@ -119,6 +120,10 @@ import kotlin.math.min
 *    }
 * ```
 *
 * [transitionControllerFactory] must be defined when this [Expandable] is registered for a
 * long-term launch or return animation, to ensure that animation controllers can be created
 * correctly.
 *
 * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
 * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
 */
@@ -134,10 +139,17 @@ fun Expandable(
    // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
    // proven that the new implementation is robust.
    useModifierBasedImplementation: Boolean = false,
    transitionControllerFactory: ComposableControllerFactory? = null,
    content: @Composable (Expandable) -> Unit,
) {
    Expandable(
        rememberExpandableController(color, shape, contentColor, borderStroke),
        rememberExpandableController(
            color,
            shape,
            contentColor,
            borderStroke,
            transitionControllerFactory,
        ),
        modifier,
        onClick,
        interactionSource,
@@ -183,6 +195,17 @@ fun Expandable(
) {
    val controller = controller as ExpandableControllerImpl

    if (controller.transitionControllerFactory != null) {
        DisposableEffect(controller.transitionControllerFactory) {
            // Notify the transition controller factory that the expandable is now available, so it
            // can move forward with any pending requests.
            controller.transitionControllerFactory.onCompose(controller.expandable)
            // Once this composable is gone, the transition controller factory must be notified so
            // it doesn't accepts requests providing stale content.
            onDispose { controller.transitionControllerFactory.onDispose() }
        }
    }

    if (useModifierBasedImplementation) {
        Box(modifier.expandable(controller, onClick, interactionSource)) {
            WrappedContent(controller.expandable, controller.contentColor, content)
+5 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.ComposableControllerFactory
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -77,6 +78,7 @@ fun rememberExpandableController(
    shape: Shape,
    contentColor: Color = contentColorFor(color),
    borderStroke: BorderStroke? = null,
    transitionControllerFactory: ComposableControllerFactory? = null,
): ExpandableController {
    val composeViewRoot = LocalView.current
    val density = LocalDensity.current
@@ -95,6 +97,7 @@ fun rememberExpandableController(
            composeViewRoot,
            density,
            layoutDirection,
            transitionControllerFactory,
        ) {
            ExpandableControllerImpl(
                color,
@@ -103,6 +106,7 @@ fun rememberExpandableController(
                borderStroke,
                composeViewRoot,
                density,
                transitionControllerFactory,
                layoutDirection,
                { isComposed },
            )
@@ -127,6 +131,7 @@ internal class ExpandableControllerImpl(
    internal val borderStroke: BorderStroke?,
    internal val composeViewRoot: View,
    internal val density: Density,
    internal val transitionControllerFactory: ComposableControllerFactory?,
    private val layoutDirection: LayoutDirection,
    private val isComposed: () -> Boolean,
) : ExpandableController {