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

Commit 6b66acfe authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Move scrollable to Compose

Test: manual, with high display size
Fixes: 316571754
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: I1b3bafe4ba85e5de08d8715f500f05231d3cf5ef
parent 09128377
Loading
Loading
Loading
Loading
+25 −9
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package com.android.systemui.qs.ui.composable

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -104,6 +106,7 @@ private fun QuickSettingsContent(
    modifier: Modifier = Modifier,
) {
    val qsView by qsSceneAdapter.qsView.collectAsState(null)
    val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState()
    QuickSettingsTheme {
        val context = LocalContext.current

@@ -113,8 +116,20 @@ private fun QuickSettingsContent(
            }
        }
        qsView?.let { view ->
            Box(
                modifier =
                    modifier
                        .fillMaxWidth()
                        .then(
                            if (isCustomizing) {
                                Modifier.fillMaxHeight()
                            } else {
                                Modifier.wrapContentHeight()
                            }
                        )
            ) {
                AndroidView(
                modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)),
                    modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
                    factory = { _ ->
                        qsSceneAdapter.setState(state)
                        view
@@ -124,3 +139,4 @@ private fun QuickSettingsContent(
            }
        }
    }
}
+113 −59
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -32,9 +34,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -44,8 +50,10 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
@@ -53,6 +61,7 @@ import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.toTransitionSceneKey
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
@@ -109,15 +118,33 @@ private fun SceneScope.QuickSettingsScene(
) {
    // TODO(b/280887232): implement the real UI.
    Box(modifier = modifier.fillMaxSize()) {
        Box(modifier = Modifier.fillMaxSize()) {
        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
        val collapsedHeaderHeight =
            with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
        val lifecycleOwner = LocalLifecycleOwner.current
            val footerActionsViewModel = remember(lifecycleOwner, viewModel) {
        val footerActionsViewModel =
            remember(lifecycleOwner, viewModel) {
                viewModel.getFooterActionsViewModel(lifecycleOwner)
            }
        val scrollState = rememberScrollState()
        // When animating into the scene, we don't want it to be able to scroll, as it could mess
        // up with the expansion animation.
        val isScrollable =
            when (val state = layoutState.transitionState) {
                is TransitionState.Idle -> true
                is TransitionState.Transition -> {
                    state.fromScene == SceneKey.QuickSettings.toTransitionSceneKey()
                }
            }

        LaunchedEffect(isCustomizing, scrollState) {
            if (isCustomizing) {
                scrollState.scrollTo(0)
            }
        }

        // This is the background for the whole scene, as the elements don't necessarily provide
        // a background that extends to the edges.
        Spacer(
            modifier =
                Modifier.element(Shade.Elements.ScrimBackground)
@@ -127,7 +154,28 @@ private fun SceneScope.QuickSettingsScene(
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier =
                    Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
                Modifier.fillMaxSize()
                    // bottom should be tied to insets
                    .padding(bottom = 16.dp)
        ) {
            Box(modifier = Modifier.fillMaxSize().weight(1f)) {
                val shadeHeaderAndQuickSettingsModifier =
                    if (isCustomizing) {
                        Modifier.fillMaxHeight().align(Alignment.TopCenter)
                    } else {
                        Modifier.verticalNestedScrollToScene()
                            .verticalScroll(
                                scrollState,
                                enabled = isScrollable,
                            )
                            .clipScrollableContainer(Orientation.Horizontal)
                            .fillMaxWidth()
                            .wrapContentHeight(unbounded = true)
                            .align(Alignment.TopCenter)
                    }

                Column(
                    modifier = shadeHeaderAndQuickSettingsModifier,
                ) {
                    when (LocalWindowSizeClass.current.widthSizeClass) {
                        WindowWidthSizeClass.Compact ->
@@ -135,21 +183,23 @@ private fun SceneScope.QuickSettingsScene(
                                visible = !isCustomizing,
                                enter =
                                    expandVertically(
                                    animationSpec = tween(1000),
                                        animationSpec = tween(100),
                                        initialHeight = { collapsedHeaderHeight },
                                ) + fadeIn(tween(1000)),
                                    ) + fadeIn(tween(100)),
                                exit =
                                    shrinkVertically(
                                    animationSpec = tween(1000),
                                        animationSpec = tween(100),
                                        targetHeight = { collapsedHeaderHeight },
                                        shrinkTowards = Alignment.Top,
                                ) + fadeOut(tween(1000)),
                                    ) + fadeOut(tween(100)),
                            ) {
                                ExpandedShadeHeader(
                                    viewModel = viewModel.shadeHeaderViewModel,
                                    createTintedIconManager = createTintedIconManager,
                                createBatteryMeterViewController = createBatteryMeterViewController,
                                    createBatteryMeterViewController =
                                        createBatteryMeterViewController,
                                    statusBarIconController = statusBarIconController,
                                    modifier = Modifier.padding(horizontal = 16.dp),
                                )
                            }
                        else ->
@@ -158,18 +208,23 @@ private fun SceneScope.QuickSettingsScene(
                                createTintedIconManager = createTintedIconManager,
                                createBatteryMeterViewController = createBatteryMeterViewController,
                                statusBarIconController = statusBarIconController,
                                modifier = Modifier.padding(horizontal = 16.dp),
                            )
                    }
                    Spacer(modifier = Modifier.height(16.dp))
                    // This view has its own horizontal padding
                    QuickSettings(
                    modifier = Modifier.fillMaxHeight().weight(1f),
                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
                        viewModel.qsSceneAdapter,
                    )
                }
            }
            AnimatedVisibility(
                visible = !isCustomizing,
                modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
            ) {
                QuickSettingsTheme {
                    // This view has its own horizontal padding
                    // TODO(b/321716470) This should use a lifecycle tied to the scene.
                    FooterActions(
                        viewModel = footerActionsViewModel,
@@ -179,7 +234,6 @@ private fun SceneScope.QuickSettingsScene(
                }
            }
        }
        }
        HeadsUpNotificationSpace(
            viewModel = viewModel.notifications,
            isPeekFromBottom = true,
+58 −26
Original line number Diff line number Diff line
@@ -27,8 +27,11 @@ import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.Nullable;

import com.android.systemui.Dumpable;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.res.R;
@@ -53,6 +56,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
    private QuickStatusBarHeader mHeader;
    private float mQsExpansion;
    private QSCustomizer mQSCustomizer;
    private QSPanel mQSPanel;
    private NonInterceptingScrollView mQSPanelContainer;

    private int mHorizontalMargins;
@@ -72,6 +76,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
    protected void onFinishInflate() {
        super.onFinishInflate();
        mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
        mQSPanel = findViewById(R.id.quick_settings_panel);
        mHeader = findViewById(R.id.header);
        mQSCustomizer = findViewById(R.id.qs_customize);
        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -79,6 +84,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {

    void setSceneContainerEnabled(boolean enabled) {
        mSceneContainerEnabled = enabled;
        if (enabled) {
            mQSPanelContainer.removeAllViews();
            removeView(mQSPanelContainer);
            LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            addView(mQSPanel, 0, lp);
        }
    }

    @Override
@@ -97,9 +109,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
        // bottom and footer are inside the screen.
        MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();

        int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec);

        if (!mSceneContainerEnabled) {
            MarginLayoutParams layoutParams =
                    (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
            int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
                    - getPaddingBottom();
            int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
@@ -111,6 +125,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
            int width = mQSPanelContainer.getMeasuredWidth() + padding;
            super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        // QSCustomizer will always be the height of the screen, but do this after
        // other measuring to avoid changing the height of the QS.
        mQSCustomizer.measure(widthMeasureSpec,
@@ -130,14 +148,17 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
    @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        if (!mSceneContainerEnabled) {
            // Do not measure QSPanel again when doing super.onMeasure.
        // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect)
        // size to the one used for determining the number of rows and then the number of pages.
            // This prevents the pages in PagedTileLayout to be remeasured with a different
            // (incorrect) size to the one used for determining the number of rows and then the
            // number of pages.
            if (child != mQSPanelContainer) {
                super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
                        parentHeightMeasureSpec, heightUsed);
            }
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
@@ -151,6 +172,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
        updateClippingPath();
    }

    @Nullable
    public NonInterceptingScrollView getQSPanelContainer() {
        return mQSPanelContainer;
    }
@@ -172,11 +194,19 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
                                    .getDimensionPixelSize(
                                            R.dimen.large_screen_shade_header_height);
        }
        if (mQSPanelContainer != null) {
            mQSPanelContainer.setPaddingRelative(
                    mQSPanelContainer.getPaddingStart(),
                    mSceneContainerEnabled ? 0 : topPadding,
                    mQSPanelContainer.getPaddingEnd(),
                    mQSPanelContainer.getPaddingBottom());
        } else {
            mQSPanel.setPaddingRelative(
                    mQSPanel.getPaddingStart(),
                    mSceneContainerEnabled ? 0 : topPadding,
                    mQSPanel.getPaddingEnd(),
                    mQSPanel.getPaddingBottom());
        }

        int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
        int horizontalPadding = getResources().getDimensionPixelSize(
@@ -220,7 +250,9 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {

    public void setExpansion(float expansion) {
        mQsExpansion = expansion;
        if (mQSPanelContainer != null) {
            mQSPanelContainer.setScrollingEnabled(expansion > 0f);
        }
        updateExpansion();
    }

@@ -239,7 +271,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
                lp.rightMargin = mHorizontalMargins;
                lp.leftMargin = mHorizontalMargins;
            }
            if (view == mQSPanelContainer) {
            if (view == mQSPanelContainer || view == mQSPanel) {
                // QS panel lays out some of its content full width
                qsPanelController.setContentMargins(mContentHorizontalPadding,
                        mContentHorizontalPadding);
+9 −2
Original line number Diff line number Diff line
@@ -81,6 +81,9 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
    public void onInit() {
        mQuickStatusBarHeaderController.init();
        mView.setSceneContainerEnabled(mSceneContainerEnabled);
        if (mSceneContainerEnabled && mQsPanelController != null) {
            mQSPanelContainer.setOnTouchListener(null);
        }
    }

    public void setListening(boolean listening) {
@@ -91,14 +94,18 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
    protected void onViewAttached() {
        mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
        mConfigurationController.addCallback(mConfigurationListener);
        if (!mSceneContainerEnabled && mQSPanelContainer != null) {
            mQSPanelContainer.setOnTouchListener(mContainerTouchHandler);
        }
    }

    @Override
    protected void onViewDetached() {
        mConfigurationController.removeCallback(mConfigurationListener);
        if (mQSPanelContainer != null) {
            mQSPanelContainer.setOnTouchListener(null);
        }
    }

    public QSContainerImpl getView() {
        return mView;
+37 −23
Original line number Diff line number Diff line
@@ -247,6 +247,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
                        mScrollListener.onQsPanelScrollChanged(scrollY);
                    }
                });
        mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
        mHeader = mRootView.findViewById(R.id.header);
        mFooter = qsComponent.getQSFooter();

@@ -637,9 +638,14 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl

    @Override
    public int getHeightDiff() {
        if (mSceneContainerFlags.isEnabled()) {
            return mQSPanelController.getViewBottom() - mHeader.getBottom()
                    + mHeader.getPaddingBottom();
        } else {
            return mQSPanelScrollView.getBottom() - mHeader.getBottom()
                    + mHeader.getPaddingBottom();
        }
    }

    @Override
    public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
@@ -701,6 +707,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
        mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);

        if (!mSceneContainerFlags.isEnabled()) {
            float qsScrollViewTranslation =
                    onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
            mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
@@ -715,6 +722,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
                mQsBounds.right = mQSPanelScrollView.getWidth();
                mQsBounds.bottom = mQSPanelScrollView.getHeight();
            }
        }
        updateQsBounds();

        if (mQSSquishinessController != null) {
@@ -803,6 +811,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
            mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
                    mQSPanelScrollView.getHeight());
        }
        if (!mSceneContainerFlags.isEnabled()) {
            mQSPanelScrollView.setClipBounds(mQsBounds);

            mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
@@ -813,6 +822,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
                    top + mQSPanelScrollView.getMeasuredHeight()
                            - mQSPanelController.getPaddingBottom());
        }
    }

    private void updateMediaPositions() {
        if (Utils.useQsMediaPlayer(getContext())) {
@@ -884,7 +894,11 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        // The customize state changed, so our height changed.
        mContainer.updateExpansion();
        boolean customizing = isCustomizing();
        if (mSceneContainerFlags.isEnabled()) {
            mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        } else {
            mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        }
        mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        if (mFooterActionsView != null) {
            mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
Loading