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

Commit 622fc57e authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Use Compose FooterActions directly in Scene

Instead of using the composable in QSImpl, instead directly compose it
into the scene (after flagging it off in QSImpl).

Test: manual
Test: atest QuickSettingsSceneViewModelTest
Test: atest QSImplTest
Fixes: 316570403
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: I8d764200527655381173fdc14f4b879a5482890e
parent 2791d50e
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
@@ -16,17 +16,14 @@

package com.android.systemui.qs.ui.composable

import android.view.ContextThemeWrapper
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
@@ -53,14 +50,6 @@ object QuickSettings {
    }
}

@Composable
private fun QuickSettingsTheme(content: @Composable () -> Unit) {
    val context = LocalContext.current
    val themedContext =
        remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
    CompositionLocalProvider(LocalContext provides themedContext) { content() }
}

private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
    return when (val transitionState = layoutState.transitionState) {
        is TransitionState.Idle -> {
+23 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.material3.MaterialTheme
@@ -36,9 +37,11 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.windowsizeclass.LocalWindowSizeClass
@@ -46,6 +49,7 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
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
@@ -109,6 +113,11 @@ private fun SceneScope.QuickSettingsScene(
            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) {
                viewModel.getFooterActionsViewModel(lifecycleOwner)
            }

            Spacer(
                modifier =
                    Modifier.element(Shade.Elements.ScrimBackground)
@@ -153,9 +162,22 @@ private fun SceneScope.QuickSettingsScene(
                }
                Spacer(modifier = Modifier.height(16.dp))
                QuickSettings(
                    modifier = Modifier.fillMaxHeight(),
                    modifier = Modifier.fillMaxHeight().weight(1f),
                    viewModel.qsSceneAdapter,
                )
                AnimatedVisibility(
                    visible = !isCustomizing,
                    modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
                ) {
                    QuickSettingsTheme {
                        // TODO(b/321716470) This should use a lifecycle tied to the scene.
                        FooterActions(
                            viewModel = footerActionsViewModel,
                            qsVisibilityLifecycleOwner = lifecycleOwner,
                            modifier = Modifier.element(QuickSettings.Elements.FooterActions)
                        )
                    }
                }
            }
        }
        HeadsUpNotificationSpace(
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.qs.ui.composable

import android.view.ContextThemeWrapper
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.android.systemui.res.R

@Composable
fun QuickSettingsTheme(content: @Composable () -> Unit) {
    val context = LocalContext.current
    val themedContext =
        remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
    CompositionLocalProvider(LocalContext provides themedContext) { content() }
}
+22 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Direction
@@ -39,12 +41,16 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
import org.mockito.Mockito.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -56,6 +62,12 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
    private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
    private val footerActionsViewModel = mock<FooterActionsViewModel>()
    private val footerActionsViewModelFactory =
        mock<FooterActionsViewModel.Factory> {
            whenever(create(any())).thenReturn(footerActionsViewModel)
        }
    private val footerActionsController = mock<FooterActionsController>()

    private var mobileIconsViewModel: MobileIconsViewModel =
        MobileIconsViewModel(
@@ -94,6 +106,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
                shadeHeaderViewModel = shadeHeaderViewModel,
                qsSceneAdapter = qsFlexiglassAdapter,
                notifications = kosmos.notificationsPlaceholderViewModel,
                footerActionsViewModelFactory,
                footerActionsController,
            )
    }

@@ -125,4 +139,12 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
                    )
                )
        }

    @Test
    fun gettingViewModelInitializesControllerOnlyOnce() {
        underTest.getFooterActionsViewModel(mock())
        underTest.getFooterActionsViewModel(mock())

        verify(footerActionsController, times(1)).init()
    }
}
+28 −9
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -171,8 +172,11 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
    private CommandQueue mCommandQueue;

    private View mRootView;
    @Nullable
    private View mFooterActionsView;

    private final SceneContainerFlags mSceneContainerFlags;

    @Inject
    public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
            SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -185,7 +189,8 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
            FooterActionsViewModel.Factory footerActionsViewModelFactory,
            FooterActionsViewBinder footerActionsViewBinder,
            LargeScreenShadeInterpolator largeScreenShadeInterpolator,
            FeatureFlags featureFlags) {
            FeatureFlags featureFlags,
            SceneContainerFlags sceneContainerFlags) {
        mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
        mQsMediaHost = qsMediaHost;
        mQqsMediaHost = qqsMediaHost;
@@ -201,6 +206,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        mFooterActionsViewModelFactory = footerActionsViewModelFactory;
        mFooterActionsViewBinder = footerActionsViewBinder;
        mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
        mSceneContainerFlags = sceneContainerFlags;
    }

    /**
@@ -216,10 +222,17 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        mQSPanelController.init();
        mQuickQSPanelController.init();

        if (!mSceneContainerFlags.isEnabled()) {
            mQSFooterActionsViewModel = mFooterActionsViewModelFactory
                    .create(mListeningAndVisibilityLifecycleOwner);
            bindFooterActionsView(mRootView);
            mFooterActionsController.init();
        } else {
            View footerView = mRootView.findViewById(R.id.qs_footer_actions);
            if (footerView != null) {
                ((ViewGroup) footerView.getParent()).removeView(footerView);
            }
        }

        mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view);
        mQSPanelScrollView.addOnLayoutChangeListener(
@@ -481,7 +494,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
                || mHeaderAnimating || mShowCollapsedOnKeyguard);
        mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
        if (mFooterActionsView != null) {
            mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
        }
        mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                || (mQsExpanded && !mStackScrollerOverscrolling));
        mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -678,8 +693,10 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
        float footerActionsExpansion =
                onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
        if (mQSFooterActionsViewModel != null) {
            mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
                    mInSplitShade);
        }
        mQSPanelController.setRevealExpansion(expansion);
        mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
        mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
@@ -869,7 +886,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
        boolean customizing = isCustomizing();
        mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        if (mFooterActionsView != null) {
            mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        }
        mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
        // Let the panel know the position changed and it needs to update where notifications
        // and whatnot are.
Loading