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

Commit fd6c7736 authored by Olivier St-Onge's avatar Olivier St-Onge
Browse files

Separate the normal and edit versions of tile composables

The logic for both versions is different enough that separating them heavily simplifies each implementation
Also changes the available tile grid in Edit mode to use a normal column instead of a lazy grid to fix a scrolling bug

Bug: 363000068
Flag: com.android.systemui.qs_ui_refactor
Test: Using QSActivity
Change-Id: Ia86a39026bf4114549c7fe9ab9f8b21794a0e18a
parent b89d1d8b
Loading
Loading
Loading
Loading
+0 −183
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.domain.interactor

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
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

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class GridConsistencyInteractorTest : SysuiTestCase() {

    data object NoopGridLayoutType : GridLayoutType

    private val kosmos =
        testKosmos().apply {
            defaultLargeTilesRepository =
                object : DefaultLargeTilesRepository {
                    override val defaultLargeTiles =
                        setOf(
                            TileSpec.create("largeA"),
                            TileSpec.create("largeB"),
                            TileSpec.create("largeC"),
                            TileSpec.create("largeD"),
                        )
                }
            gridConsistencyInteractorsMap =
                mapOf(
                    Pair(NoopGridLayoutType, noopGridConsistencyInteractor),
                    Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
                )
        }

    private val underTest = with(kosmos) { gridConsistencyInteractor }

    @Before
    fun setUp() {
        // Mostly testing InfiniteGridConsistencyInteractor because it reorders tiles
        with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
        underTest.start()
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
        with(kosmos) {
            testScope.runTest {
                // Using the no-op grid consistency interactor
                gridLayoutTypeRepository.setLayout(NoopGridLayoutType)

                // Setting an invalid layout with holes
                // [ Large A ] [ sa ]
                // [ Large B ] [ Large C ]
                // [ sb ] [ Large D ]
                val newTiles =
                    listOf(
                        TileSpec.create("largeA"),
                        TileSpec.create("smallA"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeD"),
                    )
                tileSpecRepository.setTiles(0, newTiles)

                runCurrent()

                val tiles = currentTilesInteractor.currentTiles.value
                val tileSpecs = tiles.map { it.spec }

                // Saved tiles should be unchanged
                assertThat(tileSpecs).isEqualTo(newTiles)
            }
        }

    @Test
    fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
        with(kosmos) {
            testScope.runTest {
                // Setting a valid layout with holes
                // [ Large A ] [ sa ][ sb ]
                // [ Large B ] [ Large C ]
                // [ Large D ]
                val newTiles =
                    listOf(
                        TileSpec.create("largeA"),
                        TileSpec.create("smallA"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("largeD"),
                    )
                tileSpecRepository.setTiles(0, newTiles)

                runCurrent()

                val tiles = currentTilesInteractor.currentTiles.value
                val tileSpecs = tiles.map { it.spec }

                // Saved tiles should be unchanged
                assertThat(tileSpecs).isEqualTo(newTiles)
            }
        }

    @Test
    fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
        with(kosmos) {
            testScope.runTest {
                // Setting an invalid layout with holes
                // [ sa ] [ Large A ]
                // [ Large B ] [ sb ] [ sc ]
                // [ sd ] [ se ] [ Large C ]
                val newTiles =
                    listOf(
                        TileSpec.create("smallA"),
                        TileSpec.create("largeA"),
                        TileSpec.create("largeB"),
                        TileSpec.create("smallB"),
                        TileSpec.create("smallC"),
                        TileSpec.create("smallD"),
                        TileSpec.create("smallE"),
                        TileSpec.create("largeC"),
                    )
                tileSpecRepository.setTiles(0, newTiles)

                runCurrent()

                val tiles = currentTilesInteractor.currentTiles.value
                val tileSpecs = tiles.map { it.spec }

                // Expected grid
                // [ sa ] [ Large A ] [ sb ]
                // [ Large B ] [ sc ] [ sd ]
                // [ se ] [ Large C ]
                val expectedTiles =
                    listOf(
                        TileSpec.create("smallA"),
                        TileSpec.create("largeA"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeB"),
                        TileSpec.create("smallC"),
                        TileSpec.create("smallD"),
                        TileSpec.create("smallE"),
                        TileSpec.create("largeC"),
                    )

                // Saved tiles should be unchanged
                assertThat(tileSpecs).isEqualTo(expectedTiles)
            }
        }
}
+0 −187
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.domain.interactor

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {

    private val kosmos =
        testKosmos().apply {
            defaultLargeTilesRepository =
                object : DefaultLargeTilesRepository {
                    override val defaultLargeTiles: Set<TileSpec> =
                        setOf(
                            TileSpec.create("largeA"),
                            TileSpec.create("largeB"),
                            TileSpec.create("largeC"),
                            TileSpec.create("largeD"),
                        )
                }
        }
    private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }

    @Test
    fun validTiles_returnsUnchangedList() =
        with(kosmos) {
            testScope.runTest {
                // Original grid
                // [ Large A ] [ sa ][ sb ]
                // [ Large B ] [ Large C ]
                // [ Large D ]
                val tiles =
                    listOf(
                        TileSpec.create("largeA"),
                        TileSpec.create("smallA"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("largeD"),
                    )

                val newTiles = underTest.reconcileTiles(tiles)

                assertThat(newTiles).isEqualTo(tiles)
            }
        }

    @Test
    fun invalidTiles_moveIconTileForward() =
        with(kosmos) {
            testScope.runTest {
                // Original grid
                // [ Large A ] [ sa ]
                // [ Large B ] [ Large C ]
                // [ sb ] [ Large D ]
                val tiles =
                    listOf(
                        TileSpec.create("largeA"),
                        TileSpec.create("smallA"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeD"),
                    )
                // Expected grid
                // [ Large A ] [ sa ][ sb ]
                // [ Large B ] [ Large C ]
                // [ Large D ]
                val expectedTiles =
                    listOf(
                        TileSpec.create("largeA"),
                        TileSpec.create("smallA"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("largeD"),
                    )

                val newTiles = underTest.reconcileTiles(tiles)

                assertThat(newTiles).isEqualTo(expectedTiles)
            }
        }

    @Test
    fun invalidTiles_moveIconTileBack() =
        with(kosmos) {
            testScope.runTest {
                // Original grid
                // [ sa ] [ Large A ]
                // [ Large B ] [ Large C ]
                // [ Large D ]
                val tiles =
                    listOf(
                        TileSpec.create("smallA"),
                        TileSpec.create("largeA"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("largeD"),
                    )
                // Expected grid
                // [ Large A ] [ Large B ]
                // [ Large C ] [ Large D ]
                // [ sa ]
                val expectedTiles =
                    listOf(
                        TileSpec.create("largeA"),
                        TileSpec.create("largeB"),
                        TileSpec.create("largeC"),
                        TileSpec.create("largeD"),
                        TileSpec.create("smallA"),
                    )

                val newTiles = underTest.reconcileTiles(tiles)

                assertThat(newTiles).isEqualTo(expectedTiles)
            }
        }

    @Test
    fun invalidTiles_multipleCorrections() =
        with(kosmos) {
            testScope.runTest {
                // Original grid
                // [ sa ] [ Large A ]
                // [ Large B ] [ sb ] [ sc ]
                // [ sd ] [ se ] [ Large C ]
                val tiles =
                    listOf(
                        TileSpec.create("smallA"),
                        TileSpec.create("largeA"),
                        TileSpec.create("largeB"),
                        TileSpec.create("smallB"),
                        TileSpec.create("smallC"),
                        TileSpec.create("smallD"),
                        TileSpec.create("smallE"),
                        TileSpec.create("largeC"),
                    )
                // Expected grid
                // [ sa ] [ Large A ] [ sb ]
                // [ Large B ] [ sc ] [ sd ]
                // [ se ] [ Large C ]
                val expectedTiles =
                    listOf(
                        TileSpec.create("smallA"),
                        TileSpec.create("largeA"),
                        TileSpec.create("smallB"),
                        TileSpec.create("largeB"),
                        TileSpec.create("smallC"),
                        TileSpec.create("smallD"),
                        TileSpec.create("smallE"),
                        TileSpec.create("largeC"),
                    )

                val newTiles = underTest.reconcileTiles(tiles)

                assertThat(newTiles).isEqualTo(expectedTiles)
            }
        }
}
+3 −7
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
@@ -44,12 +45,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() {
        }

    private val underTest =
        with(kosmos) {
            InfiniteGridLayout(
                iconTilesViewModel,
                fixedColumnsSizeViewModel,
            )
        }
        with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }

    @Test
    fun correctPagination_underOnePage_sameOrder() =
@@ -65,7 +61,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() {
                        smallTile(),
                        largeTile(),
                        largeTile(),
                        smallTile()
                        smallTile(),
                    )

                val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
+1 −38
Original line number Diff line number Diff line
@@ -23,17 +23,14 @@ import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositor
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
@@ -56,11 +53,6 @@ interface PanelsModule {
    @Binds
    fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository

    @Binds
    fun bindDefaultGridConsistencyInteractor(
        impl: NoopGridConsistencyInteractor
    ): GridTypeConsistencyInteractor

    @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel

    @Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel
@@ -74,12 +66,6 @@ interface PanelsModule {
    @PaginatedBaseLayoutType
    fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout

    @Binds
    @PaginatedBaseLayoutType
    fun bindPaginatedBaseConsistencyInteractor(
        impl: NoopGridConsistencyInteractor
    ): GridTypeConsistencyInteractor

    @Binds @Named("Default") fun bindDefaultGridLayout(impl: PaginatedGridLayout): GridLayout

    companion object {
@@ -117,28 +103,5 @@ interface PanelsModule {
        ): Set<GridLayoutType> {
            return entries.map { it.first }.toSet()
        }

        @Provides
        @IntoSet
        fun provideGridConsistencyInteractor(
            consistencyInteractor: InfiniteGridConsistencyInteractor
        ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
            return Pair(InfiniteGridLayoutType, consistencyInteractor)
        }

        @Provides
        @IntoSet
        fun providePaginatedGridConsistencyInteractor(
            @PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor,
        ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
            return Pair(PaginatedGridLayoutType, consistencyInteractor)
        }

        @Provides
        fun provideGridConsistencyInteractorMap(
            entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
        ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
            return entries.toMap()
        }
    }
}
+0 −75
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.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

@SysUISingleton
class GridConsistencyInteractor
@Inject
constructor(
    private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
    private val currentTilesInteractor: CurrentTilesInteractor,
    private val consistencyInteractors:
        Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
    private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
    @PanelsLog private val logBuffer: LogBuffer,
    @Application private val applicationScope: CoroutineScope,
) {
    fun start() {
        applicationScope.launch {
            gridLayoutTypeInteractor.layout.collectLatest { type ->
                val consistencyInteractor =
                    consistencyInteractors[type] ?: defaultConsistencyInteractor
                currentTilesInteractor.currentTiles
                    .map { tiles -> tiles.map { it.spec } }
                    .collectLatest { tiles ->
                        val newTiles = consistencyInteractor.reconcileTiles(tiles)
                        if (newTiles != tiles) {
                            currentTilesInteractor.setTiles(newTiles)
                            logChange(newTiles)
                        }
                    }
            }
        }
    }

    private fun logChange(tiles: List<TileSpec>) {
        logBuffer.log(
            LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
            LogLevel.DEBUG,
            { str1 = tiles.toString() },
            { "Tiles reordered: $str1" }
        )
    }

    private companion object {
        const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
    }
}
Loading