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

Commit 0a53b6c2 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Use State in ViewModel instead of StateFlow

This simplifies a lot of the code and aligns with recommended
architecture.

Also, wire up isSplitShade

Test: atest com.android.systemui.qs.composefragment.viewmodel
Bug: 353253277
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Change-Id: Ic0c581a06d785153e741acc392041c311b2185a7
parent 436a2714
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -21,10 +21,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.testing.TestLifecycleOwner
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.resetMain
@@ -62,7 +63,7 @@ abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
    ): TestResult {
        return runTest {
            lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
            lifecycleOwner.lifecycleScope.launch { underTest.activate() }
            underTest.activateIn(kosmos.testScope)
            block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
        }
    }
+8 −6
Original line number Diff line number Diff line
@@ -18,12 +18,13 @@ package com.android.systemui.qs.composefragment.viewmodel

import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import org.junit.Test
import org.junit.runner.RunWith
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -32,6 +33,7 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
    AbstractQSFragmentComposeViewModelTest() {

@@ -39,18 +41,18 @@ class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
    fun forceQs_orRealExpansion() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val expansionState by collectLastValue(underTest.expansionState)

                with(testData) {
                    sysuiStatusBarStateController.setState(statusBarState)
                    underTest.isQSExpanded = expanded
                    underTest.isQsExpanded = expanded
                    underTest.isStackScrollerOverscrolling = stackScrollerOverScrolling
                    fakeDeviceEntryRepository.setBypassEnabled(bypassEnabled)
                    underTest.isTransitioningToFullShade = transitioningToFullShade
                    underTest.isInSplitShade = inSplitShade

                    underTest.qsExpansionValue = EXPANSION
                    assertThat(expansionState!!.progress)
                    underTest.setQsExpansionValue(EXPANSION)

                    runCurrent()
                    assertThat(underTest.expansionState.progress)
                        .isEqualTo(if (expectedForceQS) 1f else EXPANSION)
                }
            }
+37 −38
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.qs.composefragment.viewmodel
import android.app.StatusBarManager
import android.content.testableContext
import android.testing.TestableLooper.RunWithLooper
import androidx.compose.runtime.snapshots.Snapshot
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -33,6 +34,7 @@ import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,22 +42,21 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() {

    @Test
    fun qsExpansionValueChanges_correctExpansionState() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val expansionState by collectLastValue(underTest.expansionState)
                underTest.setQsExpansionValue(0f)
                assertThat(underTest.expansionState.progress).isEqualTo(0f)

                underTest.qsExpansionValue = 0f
                assertThat(expansionState!!.progress).isEqualTo(0f)
                underTest.setQsExpansionValue(0.3f)
                assertThat(underTest.expansionState.progress).isEqualTo(0.3f)

                underTest.qsExpansionValue = 0.3f
                assertThat(expansionState!!.progress).isEqualTo(0.3f)

                underTest.qsExpansionValue = 1f
                assertThat(expansionState!!.progress).isEqualTo(1f)
                underTest.setQsExpansionValue(1f)
                assertThat(underTest.expansionState.progress).isEqualTo(1f)
            }
        }

@@ -63,13 +64,11 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
    fun qsExpansionValueChanges_clamped() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val expansionState by collectLastValue(underTest.expansionState)

                underTest.qsExpansionValue = -1f
                assertThat(expansionState!!.progress).isEqualTo(0f)
                underTest.setQsExpansionValue(-1f)
                assertThat(underTest.expansionState.progress).isEqualTo(0f)

                underTest.qsExpansionValue = 2f
                assertThat(expansionState!!.progress).isEqualTo(1f)
                underTest.setQsExpansionValue(2f)
                assertThat(underTest.expansionState.progress).isEqualTo(1f)
            }
        }

@@ -77,15 +76,13 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
    fun qqsHeaderHeight_largeScreenHeader_0() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)

                testableContext.orCreateTestableResources.addOverride(
                    R.bool.config_use_large_screen_shade_header,
                    true,
                )
                fakeConfigurationRepository.onConfigurationChange()

                assertThat(qqsHeaderHeight).isEqualTo(0)
                assertThat(underTest.qqsHeaderHeight).isEqualTo(0)
            }
        }

@@ -93,15 +90,13 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
    fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)

                testableContext.orCreateTestableResources.addOverride(
                    R.bool.config_use_large_screen_shade_header,
                    false,
                )
                fakeConfigurationRepository.onConfigurationChange()

                assertThat(qqsHeaderHeight)
                assertThat(underTest.qqsHeaderHeight)
                    .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
            }
        }
@@ -120,17 +115,17 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
    fun statusBarState_followsController() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val statusBarState by collectLastValue(underTest.statusBarState)
                runCurrent()

                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
                runCurrent()
                assertThat(underTest.statusBarState).isEqualTo(StatusBarState.SHADE)

                sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
                runCurrent()
                assertThat(underTest.statusBarState).isEqualTo(StatusBarState.KEYGUARD)

                sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
                runCurrent()
                assertThat(underTest.statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
            }
        }

@@ -138,17 +133,18 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
    fun statusBarState_changesEarlyIfUpcomingStateIsKeyguard() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val statusBarState by collectLastValue(underTest.statusBarState)

                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
                sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE_LOCKED)
                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
                runCurrent()
                assertThat(underTest.statusBarState).isEqualTo(StatusBarState.SHADE)

                sysuiStatusBarStateController.setUpcomingState(StatusBarState.KEYGUARD)
                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
                runCurrent()
                assertThat(underTest.statusBarState).isEqualTo(StatusBarState.KEYGUARD)

                sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE)
                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
                runCurrent()
                assertThat(underTest.statusBarState).isEqualTo(StatusBarState.KEYGUARD)
            }
        }

@@ -156,16 +152,16 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
    fun qsEnabled_followsRepository() =
        with(kosmos) {
            testScope.testWithinLifecycle {
                val qsEnabled by collectLastValue(underTest.qsEnabled)

                fakeDisableFlagsRepository.disableFlags.value =
                    DisableFlagsModel(disable2 = QS_DISABLE_FLAG)
                runCurrent()

                assertThat(qsEnabled).isFalse()
                assertThat(underTest.isQsEnabled).isFalse()

                fakeDisableFlagsRepository.disableFlags.value = DisableFlagsModel()
                runCurrent()

                assertThat(qsEnabled).isTrue()
                assertThat(underTest.isQsEnabled).isTrue()
            }
        }

@@ -175,13 +171,16 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
            testScope.testWithinLifecycle {
                val squishiness by collectLastValue(tileSquishinessInteractor.squishiness)

                underTest.squishinessFractionValue = 0.3f
                underTest.squishinessFraction = 0.3f
                Snapshot.sendApplyNotifications()
                assertThat(squishiness).isWithin(epsilon).of(0.3f.constrainSquishiness())

                underTest.squishinessFractionValue = 0f
                underTest.squishinessFraction = 0f
                Snapshot.sendApplyNotifications()
                assertThat(squishiness).isWithin(epsilon).of(0f.constrainSquishiness())

                underTest.squishinessFractionValue = 1f
                underTest.squishinessFraction = 1f
                Snapshot.sendApplyNotifications()
                assertThat(squishiness).isWithin(epsilon).of(1f.constrainSquishiness())
            }
        }
+25 −25
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.onPlaced
@@ -218,7 +219,7 @@ constructor(
                { notificationScrimClippingParams.params.top },
                // Only allow scrolling when we are fully expanded. That way, we don't intercept
                // swipes in lockscreen (when somehow QS is receiving touches).
                { scrollState.canScrollForward && viewModel.expansionState.value.progress >= 1f },
                { scrollState.canScrollForward && viewModel.isQsFullyExpanded },
            )
        frame.addView(
            composeView,
@@ -231,10 +232,8 @@ constructor(
    @Composable
    private fun Content() {
        PlatformTheme {
            val visible by viewModel.qsVisible.collectAsStateWithLifecycle()

            AnimatedVisibility(
                visible = visible,
                visible = viewModel.isQsVisible,
                modifier =
                    Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
                        notificationScrimClippingParams.isEnabled
@@ -254,7 +253,7 @@ constructor(
                    label = "EditModeAnimatedContent",
                ) { editing ->
                    if (editing) {
                        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
                        val qqsPadding = viewModel.qqsHeaderHeight
                        EditMode(
                            viewModel = viewModel.containerViewModel.editModeViewModel,
                            modifier =
@@ -283,7 +282,7 @@ constructor(
    private fun CollapsableQuickSettingsSTL() {
        val sceneState = remember {
            MutableSceneTransitionLayoutState(
                viewModel.expansionState.value.toIdleSceneKey(),
                viewModel.expansionState.toIdleSceneKey(),
                transitions =
                    transitions {
                        from(QuickQuickSettings, QuickSettings) {
@@ -294,7 +293,10 @@ constructor(
        }

        LaunchedEffect(Unit) {
            synchronizeQsState(sceneState, viewModel.expansionState.map { it.progress })
            synchronizeQsState(
                sceneState,
                snapshotFlow { viewModel.expansionState }.map { it.progress },
            )
        }

        SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
@@ -329,7 +331,7 @@ constructor(
    }

    override fun setHeightOverride(desiredHeight: Int) {
        viewModel.heightOverrideValue = desiredHeight
        viewModel.heightOverride = desiredHeight
    }

    override fun setHeaderClickable(qsExpansionEnabled: Boolean) {
@@ -349,7 +351,7 @@ constructor(
    }

    override fun setExpanded(qsExpanded: Boolean) {
        viewModel.isQSExpanded = qsExpanded
        viewModel.isQsExpanded = qsExpanded
    }

    override fun setListening(listening: Boolean) {
@@ -357,7 +359,7 @@ constructor(
    }

    override fun setQsVisible(qsVisible: Boolean) {
        viewModel.isQSVisible = qsVisible
        viewModel.isQsVisible = qsVisible
    }

    override fun isShowingDetail(): Boolean {
@@ -378,9 +380,9 @@ constructor(
        headerTranslation: Float,
        squishinessFraction: Float,
    ) {
        viewModel.qsExpansionValue = qsExpansionFraction
        viewModel.panelExpansionFractionValue = panelExpansionFraction
        viewModel.squishinessFractionValue = squishinessFraction
        viewModel.setQsExpansionValue(qsExpansionFraction)
        viewModel.panelExpansionFraction = panelExpansionFraction
        viewModel.squishinessFraction = squishinessFraction

        // TODO(b/353254353) Handle header translation
    }
@@ -415,8 +417,8 @@ constructor(
        // TODO (b/353253280)
    }

    override fun setInSplitShade(shouldTranslate: Boolean) {
        // TODO (b/356435605)
    override fun setInSplitShade(isInSplitShade: Boolean) {
        viewModel.isInSplitShade = isInSplitShade
    }

    override fun setTransitionToFullShadeProgress(
@@ -425,9 +427,9 @@ constructor(
        qsSquishinessFraction: Float,
    ) {
        viewModel.isTransitioningToFullShade = isTransitioningToFullShade
        viewModel.lockscreenToShadeProgressValue = qsTransitionFraction
        viewModel.lockscreenToShadeProgress = qsTransitionFraction
        if (isTransitioningToFullShade) {
            viewModel.squishinessFractionValue = qsSquishinessFraction
            viewModel.squishinessFraction = qsSquishinessFraction
        }
    }

@@ -452,7 +454,7 @@ constructor(
    }

    override fun isFullyCollapsed(): Boolean {
        return viewModel.qsExpansionValue <= 0f
        return viewModel.isQsFullyCollapsed
    }

    override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
@@ -468,7 +470,7 @@ constructor(
    }

    override fun setIsNotificationPanelFullWidth(isFullWidth: Boolean) {
        viewModel.isSmallScreenValue = isFullWidth
        viewModel.isSmallScreen = isFullWidth
    }

    override fun getHeaderTop(): Int {
@@ -522,7 +524,7 @@ constructor(

    @Composable
    private fun SceneScope.QuickQuickSettingsElement() {
        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
        val qqsPadding = viewModel.qqsHeaderHeight
        val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom)
        DisposableEffect(Unit) {
            qqsVisible.value = true
@@ -559,8 +561,7 @@ constructor(
                        }
                        .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() })
            ) {
                val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
                if (qsEnabled) {
                if (viewModel.isQsEnabled) {
                    QuickQuickSettings(
                        viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
                        modifier =
@@ -583,7 +584,7 @@ constructor(

    @Composable
    private fun SceneScope.QuickSettingsElement() {
        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
        val qqsPadding = viewModel.qqsHeaderHeight
        val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
        Column(
            modifier =
@@ -591,8 +592,7 @@ constructor(
                    stringResource(id = R.string.accessibility_quick_settings_collapse)
                )
        ) {
            val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
            if (qsEnabled) {
            if (viewModel.isQsEnabled) {
                Box(
                    modifier =
                        Modifier.element(ElementKeys.QuickSettingsContent).fillMaxSize().weight(1f)
+146 −238

File changed.

Preview size limit exceeded, changes collapsed.