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

Commit 836e5a96 authored by Jiaming Cheng's avatar Jiaming Cheng
Browse files

[QSDetailedView] Add the generic view and view models

1) Created the DetailsViewModel which controls which details view
to show in the QuickSettingsShadeOverlay
2) Created the TileDetailsViewModel which is the base view model
class and will be extended by the feature side .
3) create the generic TileDetailsView composable
4) Apply this new pattern to the internet detailed view as an example.
The implementation is not done yet. This cl only enabled the entry
point to the new detailed view from the internet tile.
See this screenshot: https://screenshot.googleplex.com/BE2BYexkJ98uLJD

Flag: com.android.systemui.qs_tile_detailed_view
Test: DetailsViewModelTest, CurrentTilesInteractorImplTest
Bug: b/377532639, b/378513979, b/378514731, b/378514116
Change-Id: Ifd50e9401a1e374bbb5e8ff8780640ef0b7483f9
parent b8c910cc
Loading
Loading
Loading
Loading
+41 −12
Original line number Diff line number Diff line
@@ -44,8 +44,11 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileDetails
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
@@ -116,20 +119,45 @@ constructor(
    }
}

// A sealed interface to represent the possible states of the `ShadeBody`
sealed interface ShadeBodyState {
    data object Editing : ShadeBodyState
    data object TileDetails : ShadeBodyState
    data object Default : ShadeBodyState
}

// Function to map the current state of the `ShadeBody`
fun checkQsState(isEditing: Boolean, tileDetails: TileDetailsViewModel?): ShadeBodyState {
    if (isEditing) {
        return ShadeBodyState.Editing
    } else if (tileDetails != null && QsDetailedView.isEnabled) {
        return ShadeBodyState.TileDetails
    }
    return ShadeBodyState.Default
}

@Composable
fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
    val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
    val tileDetails = viewModel.detailsViewModel.activeTileDetails

    AnimatedContent(
        targetState = isEditing,
        targetState = checkQsState(isEditing, tileDetails),
        transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) },
    ) { editing ->
        if (editing) {
    ) { state ->
        when (state) {
            ShadeBodyState.Editing -> {
                EditMode(
                    viewModel = viewModel.editModeViewModel,
                modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(QuickSettingsShade.Dimensions.Padding),
                )
        } else {
            }
            ShadeBodyState.TileDetails -> {
                TileDetails(viewModel.detailsViewModel)
            }
            else -> {
                QuickSettingsLayout(
                    viewModel = viewModel,
                    modifier = Modifier.sysuiResTag("quick_settings_panel"),
@@ -137,6 +165,7 @@ fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
            }
        }
    }
}

/** Column containing Brightness and QS tiles. */
@Composable
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.panels.ui.viewmodel


import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FakeQSTile
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@SmallTest
class DetailsViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private lateinit var underTest: DetailsViewModel
    private val spec = TileSpec.create("internet")
    private val specNoDetails = TileSpec.create("NoDetailsTile")

    @Before
    fun setUp() {
        underTest = kosmos.detailsViewModel
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun changeTileDetailsViewModel() = with(kosmos) {
        testScope.runTest {
            val specs = listOf(
                spec,
                specNoDetails,
            )
            tileSpecRepository.setTiles(0, specs)
            runCurrent()

            val tiles = currentTilesInteractor.currentTiles.value

            assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
            assertThat(tiles!![1].spec).isEqualTo(specNoDetails)
            (tiles!![1].tile as FakeQSTile).hasDetailsViewModel = false

            assertThat(underTest.activeTileDetails).isNull()

            // Click on the tile who has the `spec`.
            assertThat(underTest.onTileClicked(spec)).isTrue()
            assertThat(underTest.activeTileDetails).isNotNull()
            assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")

            // Click on a tile who dose not have a valid spec.
            assertThat(underTest.onTileClicked(null)).isFalse()
            assertThat(underTest.activeTileDetails).isNull()

            // Click again on the tile who has the `spec`.
            assertThat(underTest.onTileClicked(spec)).isTrue()
            assertThat(underTest.activeTileDetails).isNotNull()
            assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")

            // Click on a tile who dose not have a detailed view.
            assertThat(underTest.onTileClicked(specNoDetails)).isFalse()
            assertThat(underTest.activeTileDetails).isNull()

            underTest.closeDetailedView()
            assertThat(underTest.activeTileDetails).isNull()

            assertThat(underTest.onTileClicked(null)).isFalse()
        }
    }
}
+27 −1
Original line number Diff line number Diff line
@@ -691,6 +691,32 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
        }


    @Test
    fun getTileDetails() =
        testScope.runTest(USER_INFO_0) {
            val tiles by collectLastValue(underTest.currentTiles)
            val tileA = TileSpec.create("a")
            val tileB = TileSpec.create("b")
            val tileNoDetails = TileSpec.create("NoDetails")

            val specs = listOf(tileA, tileB, tileNoDetails)

            assertThat(tiles!!.isEmpty()).isTrue()

            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
            assertThat(tiles!!.size).isEqualTo(3)

            // The third tile doesn't have a details view.
            assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
            (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false

            assertThat(tiles!![0].tile.detailsViewModel.getTitle()).isEqualTo("a")
            assertThat(tiles!![1].tile.detailsViewModel.getTitle()).isEqualTo("b")
            assertThat(tiles!![2].tile.detailsViewModel).isNull()
        }


    private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
        this.state = state
        this.label = label
@@ -770,7 +796,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
        private val USER_INFO_0 = UserInfo().apply { id = 0 }
        private val USER_INFO_1 = UserInfo().apply { id = 1 }

        private val VALID_TILES = setOf("a", "b", "c", "d", "e")
        private val VALID_TILES = setOf("a", "b", "c", "d", "e", "NoDetails")
        private val TEST_COMPONENT = ComponentName("pkg", "cls")
        private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
    }
+2 −0
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ java_library {
        "SystemUICommon",
        "SystemUILogLib",
        "androidx.annotation_annotation",
        "androidx.compose.ui_ui",
        "androidx.compose.runtime_runtime",
    ],
}

+5 −0
Original line number Diff line number Diff line
@@ -120,6 +120,11 @@ public interface QSTile {
     */
    boolean isListening();

    /**
     * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView.
     */
    default TileDetailsViewModel getDetailsViewModel() { return null; }

    @ProvidesInterface(version = Callback.VERSION)
    interface Callback {
        static final int VERSION = 2;
Loading