diff options
| author | 2024-06-20 15:13:37 -0400 | |
|---|---|---|
| committer | 2024-06-21 10:12:33 -0400 | |
| commit | d03bedb9ffaba7f7071c5c6682bb10158abc165d (patch) | |
| tree | 811bbe9b53ca4530f4ca948ff606c8bbaced646c | |
| parent | f43c33083038780ebe47ddb2d162ad8635cede4a (diff) | |
Add a PaginatedGridLayout
The layout delegates to other layouts to show each page, as well the
edit mode.
Right now, the delegation is hardcoded in PanelsModule.
Test: atest com.android.systemui.qs.panels
Test: manual
Fixes: 347915821
Flag: com.android.systemui.qs_ui_refactor
Change-Id: I6a0dc3d4b2b56a5f3a41d366706839fe3c393b29
34 files changed, 1076 insertions, 50 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt index a0d6be9c68dc..93b03743a87e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt @@ -155,6 +155,7 @@ private fun QuickSettingsLayout( viewModel = viewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), + viewModel.editModeViewModel::startEditing, ) Button( onClick = { viewModel.editModeViewModel.startEditing() }, @@ -169,7 +170,7 @@ object QuickSettingsShade { object Dimensions { val Padding = 16.dp val BrightnessSliderHeight = 64.dp - val GridMaxHeight = 400.dp + val GridMaxHeight = 800.dp } object Transitions { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt new file mode 100644 index 000000000000..14d60943149f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt @@ -0,0 +1,62 @@ +/* + * 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.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R +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 PaginatedGridRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + + val underTest = kosmos.paginatedGridRepository + + @Test + fun rows_followsConfig() = + with(kosmos) { + testScope.runTest { + val rows by collectLastValue(underTest.rows) + + setRowsInConfig(3) + assertThat(rows).isEqualTo(3) + + setRowsInConfig(6) + assertThat(rows).isEqualTo(6) + } + } + + private fun setRowsInConfig(rows: Int) = + with(kosmos) { + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_max_rows, + rows, + ) + fakeConfigurationRepository.onConfigurationChange() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt new file mode 100644 index 000000000000..914a09597d65 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt @@ -0,0 +1,123 @@ +/* + * 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.compose + +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.IconTilesRepository +import com.android.systemui.qs.panels.data.repository.iconTilesRepository +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 +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 InfiniteGridLayoutTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + iconTilesRepository = + object : IconTilesRepository { + override fun isIconTile(spec: TileSpec): Boolean { + return spec.spec.startsWith("small") + } + } + } + + private val underTest = + with(kosmos) { + InfiniteGridLayout( + iconTilesViewModel, + fixedColumnsSizeViewModel, + ) + } + + @Test + fun correctPagination_underOnePage_sameOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile() + ) + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + assertThat(pages).hasSize(1) + assertThat(pages[0]).isEqualTo(tiles) + } + } + + @Test + fun correctPagination_twoPages_sameOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile(), + smallTile(), + largeTile(), + ) + // --- Page 1 --- + // [L L] [S] [S] + // [L L] [L L] + // [S] [S] [L L] + // --- Page 2 --- + // [L L] [S] [S] + // [L L] + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + assertThat(pages).hasSize(2) + assertThat(pages[0]).isEqualTo(tiles.take(8)) + assertThat(pages[1]).isEqualTo(tiles.drop(8)) + } + } + + companion object { + fun largeTile() = MockTileViewModel(TileSpec.create("large")) + + fun smallTile() = MockTileViewModel(TileSpec.create("small")) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt new file mode 100644 index 000000000000..6df3f8d1bdd5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt @@ -0,0 +1,81 @@ +/* + * 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.compose + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PaginatableGridLayoutTest : SysuiTestCase() { + @Test + fun correctRows_gapsAtEnd() { + val columns = 6 + + val sizedTiles = + listOf( + largeTile(), + extraLargeTile(), + largeTile(), + smallTile(), + largeTile(), + ) + + // [L L] [XL XL XL] + // [L L] [S] [L L] + + val rows = PaginatableGridLayout.splitInRows(sizedTiles, columns) + + assertThat(rows).hasSize(2) + assertThat(rows[0]).isEqualTo(sizedTiles.take(2)) + assertThat(rows[1]).isEqualTo(sizedTiles.drop(2)) + } + + @Test + fun correctRows_fullLastRow_noEmptyRow() { + val columns = 6 + + val sizedTiles = + listOf( + largeTile(), + extraLargeTile(), + smallTile(), + ) + + // [L L] [XL XL XL] [S] + + val rows = PaginatableGridLayout.splitInRows(sizedTiles, columns) + + assertThat(rows).hasSize(1) + assertThat(rows[0]).isEqualTo(sizedTiles) + } + + companion object { + fun extraLargeTile() = SizedTile(MockTileViewModel(TileSpec.create("XLarge")), 3) + + fun largeTile() = SizedTile(MockTileViewModel(TileSpec.create("large")), 2) + + fun smallTile() = SizedTile(MockTileViewModel(TileSpec.create("small")), 1) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt new file mode 100644 index 000000000000..3354b4d4116b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt @@ -0,0 +1,125 @@ +/* + * 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.compose + +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.IconTilesRepository +import com.android.systemui.qs.panels.data.repository.iconTilesRepository +import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel +import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.testKosmos +import com.google.common.truth.Truth +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PartitionedGridLayoutTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + iconTilesRepository = + object : IconTilesRepository { + override fun isIconTile(spec: TileSpec): Boolean { + return spec.spec.startsWith("small") + } + } + } + + private val underTest = with(kosmos) { PartitionedGridLayout(partitionedGridViewModel) } + + @Test + fun correctPagination_underOnePage_partitioned_sameRelativeOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + largeTile(), + smallTile() + ) + val (smallTiles, largeTiles) = + tiles.partition { iconTilesViewModel.isIconTile(it.spec) } + + // [L L] [L L] + // [L L] + // [S] [S] [S] + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + Truth.assertThat(pages).hasSize(1) + Truth.assertThat(pages[0]).isEqualTo(largeTiles + smallTiles) + } + } + + @Test + fun correctPagination_twoPages_partitioned_sameRelativeOrder() = + with(kosmos) { + testScope.runTest { + val rows = 3 + val columns = 4 + + val tiles = + listOf( + largeTile(), + smallTile(), + smallTile(), + largeTile(), + smallTile(), + smallTile(), + largeTile(), + smallTile(), + smallTile(), + ) + // --- Page 1 --- + // [L L] [L L] + // [L L] + // [S] [S] [S] [S] + // --- Page 2 --- + // [S] [S] + + val (smallTiles, largeTiles) = + tiles.partition { iconTilesViewModel.isIconTile(it.spec) } + + val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns) + + val expectedPage0 = largeTiles + smallTiles.take(4) + val expectedPage1 = smallTiles.drop(4) + + Truth.assertThat(pages).hasSize(2) + Truth.assertThat(pages[0]).isEqualTo(expectedPage0) + Truth.assertThat(pages[1]).isEqualTo(expectedPage1) + } + } + + companion object { + fun largeTile() = MockTileViewModel(TileSpec.create("large")) + + fun smallTile() = MockTileViewModel(TileSpec.create("small")) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PaginatedBaseLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PaginatedBaseLayoutType.kt new file mode 100644 index 000000000000..0285cbdbfac1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PaginatedBaseLayoutType.kt @@ -0,0 +1,24 @@ +/* + * 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.dagger + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class PaginatedBaseLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 7b679932de3e..c2143619c1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -30,18 +30,21 @@ import com.android.systemui.qs.panels.shared.model.GridConsistencyLog import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog 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.PartitionedGridLayoutType import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType 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.PartitionedGridLayout import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout +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 import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModelImpl import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModelImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -62,14 +65,24 @@ interface PanelsModule { @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel - @Binds fun bindGridSizeViewModel(impl: InfiniteGridSizeViewModelImpl): InfiniteGridSizeViewModel + @Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel @Binds fun bindIconLabelVisibilityViewModel( impl: IconLabelVisibilityViewModelImpl ): IconLabelVisibilityViewModel - @Binds @Named("Default") fun bindDefaultGridLayout(impl: PartitionedGridLayout): GridLayout + @Binds + @PaginatedBaseLayoutType + fun bindPaginatedBaseGridLayout(impl: PartitionedGridLayout): PaginatableGridLayout + + @Binds + @PaginatedBaseLayoutType + fun bindPaginatedBaseConsistencyInteractor( + impl: NoopGridConsistencyInteractor + ): GridTypeConsistencyInteractor + + @Binds @Named("Default") fun bindDefaultGridLayout(impl: PaginatedGridLayout): GridLayout companion object { @Provides @@ -109,6 +122,14 @@ interface PanelsModule { } @Provides + @IntoSet + fun providePaginatedGridLayout( + gridLayout: PaginatedGridLayout + ): Pair<GridLayoutType, GridLayout> { + return Pair(PaginatedGridLayoutType, gridLayout) + } + + @Provides fun provideGridLayoutMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> ): Map<GridLayoutType, GridLayout> { @@ -147,6 +168,14 @@ interface PanelsModule { } @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> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt index 43ccdf663478..32ce973cbe58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @SysUISingleton -class InfiniteGridSizeRepository @Inject constructor() { +class FixedColumnsRepository @Inject constructor() { // Number of columns in the narrowest state for consistency private val _columns = MutableStateFlow(4) val columns: StateFlow<Int> = _columns.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt index 44d8688f0c4d..47c4ffd6a2cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.shared.model.GridLayoutType -import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType +import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,13 +26,14 @@ import kotlinx.coroutines.flow.asStateFlow interface GridLayoutTypeRepository { val layout: StateFlow<GridLayoutType> + fun setLayout(type: GridLayoutType) } @SysUISingleton class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository { private val _layout: MutableStateFlow<GridLayoutType> = - MutableStateFlow(PartitionedGridLayoutType) + MutableStateFlow(PaginatedGridLayoutType) override val layout = _layout.asStateFlow() override fun setLayout(type: GridLayoutType) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt new file mode 100644 index 000000000000..26b2e2b7bd66 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt @@ -0,0 +1,43 @@ +/* + * 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.data.repository + +import android.content.res.Resources +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.emitOnStart +import javax.inject.Inject +import kotlinx.coroutines.flow.map + +/** + * Provides the number of [rows] to use with a paginated grid, by tracking the resource + * [R.integer.quick_settings_max_rows]. + */ +@SysUISingleton +class PaginatedGridRepository +@Inject +constructor( + @Main private val resources: Resources, + configurationRepository: ConfigurationRepository, +) { + val rows = + configurationRepository.onConfigurationChange.emitOnStart().map { + resources.getInteger(R.integer.quick_settings_max_rows) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt index 13c6072340c6..9591002b3d3d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt @@ -17,11 +17,11 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository +import com.android.systemui.qs.panels.data.repository.FixedColumnsRepository import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @SysUISingleton -class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) { +class FixedColumnsSizeInteractor @Inject constructor(repo: FixedColumnsRepository) { val columns: StateFlow<Int> = repo.columns } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt index e99c64c8c1f3..0fe79af06a54 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -28,7 +28,7 @@ class InfiniteGridConsistencyInteractor @Inject constructor( private val iconTilesInteractor: IconTilesInteractor, - private val gridSizeInteractor: InfiniteGridSizeInteractor + private val gridSizeInteractor: FixedColumnsSizeInteractor ) : GridTypeConsistencyInteractor { /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractor.kt index 97ceacc6926d..d7d1ce9797ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractor.kt @@ -17,10 +17,14 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.panels.data.repository.PaginatedGridRepository import javax.inject.Inject @SysUISingleton -class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor { - override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles +class PaginatedGridInteractor +@Inject +constructor(paginatedGridRepository: PaginatedGridRepository) { + val rows = paginatedGridRepository.rows + + val defaultRows = 4 } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt index 9550ddb30e78..b1942fee9b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt @@ -34,3 +34,6 @@ data object StretchedGridLayoutType : GridLayoutType /** Grid type grouping large tiles on top and icon tiles at the bottom. */ data object PartitionedGridLayoutType : GridLayoutType + +/** Grid type for a paginated list of tiles. It will delegate to some other layout type. */ +data object PaginatedGridLayoutType : GridLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index 8806931a888a..e2f6bcf2e872 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,15 +18,19 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec +/** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { @Composable fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, + editModeStart: () -> Unit, ) @Composable @@ -37,3 +41,49 @@ interface GridLayout { onRemoveTile: (TileSpec) -> Unit, ) } + +/** + * A type of [GridLayout] that can be paginated, to use together with [PaginatedGridLayout]. + * + * [splitIntoPages] determines how to split a list of tiles based on the number of rows and columns + * available. + */ +interface PaginatableGridLayout : GridLayout { + fun splitIntoPages( + tiles: List<TileViewModel>, + rows: Int, + columns: Int, + ): List<List<TileViewModel>> + + companion object { + + /** + * Splits a list of [SizedTile] into rows, each with at most [columns] occupied. + * + * It will leave gaps at the end of a row if the next [SizedTile] has [SizedTile.width] that + * is larger than the space remaining in the row. + */ + fun splitInRows( + tiles: List<SizedTile<TileViewModel>>, + columns: Int + ): List<List<SizedTile<TileViewModel>>> { + val row = TileRow<TileViewModel>(columns) + + return buildList { + for (tile in tiles) { + check(tile.width <= columns) + if (!row.maybeAddTile(tile)) { + // Couldn't add tile to previous row, create a row with the current tiles + // and start a new one + add(row.tiles) + row.clear() + row.maybeAddTile(tile) + } + } + if (row.tiles.isNotEmpty()) { + add(row.tiles) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index 2f0fe221a2b4..ea97f0de2bb8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -26,9 +26,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.res.R @@ -39,13 +40,14 @@ class InfiniteGridLayout @Inject constructor( private val iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: InfiniteGridSizeViewModel, -) : GridLayout { + private val gridSizeViewModel: FixedColumnsSizeViewModel, +) : PaginatableGridLayout { @Composable override fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, + editModeStart: () -> Unit, ) { DisposableEffect(tiles) { val token = Any() @@ -55,16 +57,8 @@ constructor( val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { - items( - tiles.size, - span = { index -> - if (iconTilesViewModel.isIconTile(tiles[index].spec)) { - GridItemSpan(1) - } else { - GridItemSpan(2) - } - } - ) { index -> + items(tiles.size, span = { index -> GridItemSpan(tiles[index].spec.width()) }) { index + -> Tile( tile = tiles[index], iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec), @@ -92,4 +86,22 @@ constructor( onRemoveTile = onRemoveTile, ) } + + override fun splitIntoPages( + tiles: List<TileViewModel>, + rows: Int, + columns: Int, + ): List<List<TileViewModel>> { + + return PaginatableGridLayout.splitInRows( + tiles.map { SizedTile(it, it.spec.width()) }, + columns, + ) + .chunked(rows) + .map { it.flatten().map { it.tile } } + } + + private fun TileSpec.width(): Int { + return if (iconTilesViewModel.isIconTile(this)) 1 else 2 + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt new file mode 100644 index 000000000000..7de221612161 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt @@ -0,0 +1,166 @@ +/* + * 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.compose + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.pager.PagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.semantics.pageLeft +import androidx.compose.ui.semantics.pageRight +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.absoluteValue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun PagerDots( + pagerState: PagerState, + activeColor: Color, + nonActiveColor: Color, + modifier: Modifier = Modifier, + dotSize: Dp = 6.dp, + spaceSize: Dp = 4.dp, +) { + if (pagerState.pageCount < 2) { + return + } + val inPageTransition by + remember(pagerState) { + derivedStateOf { + pagerState.currentPageOffsetFraction.absoluteValue > 0.01 && + !pagerState.isOverscrolling() + } + } + val coroutineScope = rememberCoroutineScope() + Row( + modifier = + modifier + .wrapContentWidth() + .pagerDotsSemantics( + pagerState, + coroutineScope, + ), + horizontalArrangement = spacedBy(spaceSize), + verticalAlignment = Alignment.CenterVertically + ) { + if (!inPageTransition) { + repeat(pagerState.pageCount) { i -> + // We use canvas directly to only invalidate the draw phase when the page is + // changing. + Canvas(Modifier.size(dotSize)) { + if (pagerState.currentPage == i) { + drawCircle(activeColor) + } else { + drawCircle(nonActiveColor) + } + } + } + } else { + val doubleDotWidth = dotSize * 2 + spaceSize + val cornerRadius = dotSize / 2 + val width by + animateDpAsState(targetValue = if (inPageTransition) doubleDotWidth else dotSize) + + fun DrawScope.drawDoubleRect() { + drawRoundRect( + color = activeColor, + size = Size(width.toPx(), dotSize.toPx()), + cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx()) + ) + } + + repeat(pagerState.pageCount) { page -> + Canvas(Modifier.size(dotSize)) { + val withPrevious = pagerState.currentPageOffsetFraction < 0 + val ltr = layoutDirection == LayoutDirection.Ltr + if ( + withPrevious && page == (pagerState.currentPage - 1) || + !withPrevious && page == pagerState.currentPage + ) { + if (ltr) { + drawDoubleRect() + } + } else if ( + withPrevious && page == pagerState.currentPage || + !withPrevious && page == (pagerState.currentPage + 1) + ) { + if (!ltr) { + drawDoubleRect() + } + } else { + drawCircle(nonActiveColor) + } + } + } + } + } +} + +private fun Modifier.pagerDotsSemantics( + pagerState: PagerState, + coroutineScope: CoroutineScope, +): Modifier { + return then( + Modifier.semantics { + pageLeft { + if (pagerState.canScrollBackward) { + coroutineScope.launch { + pagerState.animateScrollToPage(pagerState.currentPage - 1) + } + true + } else { + false + } + } + pageRight { + if (pagerState.canScrollForward) { + coroutineScope.launch { + pagerState.animateScrollToPage(pagerState.currentPage + 1) + } + true + } else { + false + } + } + stateDescription = "Page ${pagerState.settledPage + 1} of ${pagerState.pageCount}" + } + ) +} + +private fun PagerState.isOverscrolling(): Boolean { + val position = currentPage + currentPageOffsetFraction + return position < 0 || position > pageCount - 1 +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt new file mode 100644 index 000000000000..2ee957e89f48 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -0,0 +1,117 @@ +/* + * 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.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType +import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight +import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing +import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.res.R +import javax.inject.Inject + +class PaginatedGridLayout +@Inject +constructor( + private val viewModel: PaginatedGridViewModel, + @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, +) : GridLayout by delegateGridLayout { + @Composable + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + editModeStart: () -> Unit, + ) { + DisposableEffect(tiles) { + val token = Any() + tiles.forEach { it.startListening(token) } + onDispose { tiles.forEach { it.stopListening(token) } } + } + val columns by viewModel.columns.collectAsStateWithLifecycle() + val rows by viewModel.rows.collectAsStateWithLifecycle() + + val pages = + remember(tiles, columns, rows) { + delegateGridLayout.splitIntoPages(tiles, rows = rows, columns = columns) + } + + val pagerState = rememberPagerState(0) { pages.size } + + Column { + HorizontalPager( + state = pagerState, + modifier = Modifier, + pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp, + beyondViewportPageCount = 1, + verticalAlignment = Alignment.Top, + ) { + val page = pages[it] + + delegateGridLayout.TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) + } + Box( + modifier = Modifier.height(FooterHeight).fillMaxWidth(), + ) { + PagerDots( + pagerState = pagerState, + activeColor = MaterialTheme.colorScheme.primary, + nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.align(Alignment.Center) + ) + CompositionLocalProvider(value = LocalContentColor provides Color.White) { + IconButton( + onClick = editModeStart, + modifier = Modifier.align(Alignment.CenterEnd), + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(id = R.string.qs_edit) + ) + } + } + } + } + } + + private object Dimensions { + val FooterHeight = 48.dp + val InterPageSpacing = 16.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt index 9233e76f09e6..7f5e474f2fa7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.modifiers.background import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel @@ -63,9 +64,13 @@ import javax.inject.Inject @SysUISingleton class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) : - GridLayout { + PaginatableGridLayout { @Composable - override fun TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + editModeStart: () -> Unit, + ) { DisposableEffect(tiles) { val token = Any() tiles.forEach { it.startListening(token) } @@ -169,6 +174,20 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition } } + override fun splitIntoPages( + tiles: List<TileViewModel>, + rows: Int, + columns: Int, + ): List<List<TileViewModel>> { + val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) } + + val sizedLargeTiles = largeTiles.map { SizedTile(it, 2) } + val sizedSmallTiles = smallTiles.map { SizedTile(it, 1) } + val largeTilesRows = PaginatableGridLayout.splitInRows(sizedLargeTiles, columns) + val smallTilesRows = PaginatableGridLayout.splitInRows(sizedSmallTiles, columns) + return (largeTilesRows + smallTilesRows).chunked(rows).map { it.flatten().map { it.tile } } + } + @Composable private fun CurrentTiles( tiles: List<EditTileViewModel>, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt index 7f4e0a7047b8..4a9010270ac5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt @@ -30,8 +30,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.res.R @@ -42,13 +42,14 @@ class StretchedGridLayout @Inject constructor( private val iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: InfiniteGridSizeViewModel, + private val gridSizeViewModel: FixedColumnsSizeViewModel, ) : GridLayout { @Composable override fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, + editModeStart: () -> Unit, ) { DisposableEffect(tiles) { val token = Any() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 2dab7c3d61ca..8c57d41b2123 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -23,9 +23,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { +fun TileGrid( + viewModel: TileGridViewModel, + modifier: Modifier = Modifier, + editModeStart: () -> Unit +) { val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) - gridLayout.TileGrid(tiles, modifier) + gridLayout.TileGrid(tiles, modifier, editModeStart) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt index a4ee58f0963c..865c86b4096d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt @@ -17,16 +17,16 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor +import com.android.systemui.qs.panels.domain.interactor.FixedColumnsSizeInteractor import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow -interface InfiniteGridSizeViewModel { +interface FixedColumnsSizeViewModel { val columns: StateFlow<Int> } @SysUISingleton -class InfiniteGridSizeViewModelImpl @Inject constructor(interactor: InfiniteGridSizeInteractor) : - InfiniteGridSizeViewModel { +class FixedColumnsSizeViewModelImpl @Inject constructor(interactor: FixedColumnsSizeInteractor) : + FixedColumnsSizeViewModel { override val columns: StateFlow<Int> = interactor.columns } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt new file mode 100644 index 000000000000..28bf47400c4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -0,0 +1,46 @@ +/* + * 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 com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +@SysUISingleton +class PaginatedGridViewModel +@Inject +constructor( + iconTilesViewModel: IconTilesViewModel, + gridSizeViewModel: FixedColumnsSizeViewModel, + iconLabelVisibilityViewModel: IconLabelVisibilityViewModel, + paginatedGridInteractor: PaginatedGridInteractor, + @Application applicationScope: CoroutineScope, +) : + IconTilesViewModel by iconTilesViewModel, + FixedColumnsSizeViewModel by gridSizeViewModel, + IconLabelVisibilityViewModel by iconLabelVisibilityViewModel { + val rows = + paginatedGridInteractor.rows.stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + paginatedGridInteractor.defaultRows, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt index 730cf635972d..2049edbd544f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt @@ -24,9 +24,9 @@ class PartitionedGridViewModel @Inject constructor( iconTilesViewModel: IconTilesViewModel, - gridSizeViewModel: InfiniteGridSizeViewModel, + gridSizeViewModel: FixedColumnsSizeViewModel, iconLabelVisibilityViewModel: IconLabelVisibilityViewModel, ) : IconTilesViewModel by iconTilesViewModel, - InfiniteGridSizeViewModel by gridSizeViewModel, + FixedColumnsSizeViewModel by gridSizeViewModel, IconLabelVisibilityViewModel by iconLabelVisibilityViewModel diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt index d8af3fa98d7b..2f5daaa35286 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() } +val Kosmos.fixedColumnsRepository by Kosmos.Fixture { FixedColumnsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryKosmos.kt new file mode 100644 index 000000000000..696c4bf7bbaf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryKosmos.kt @@ -0,0 +1,27 @@ +/* + * 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.data.repository + +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase + +val Kosmos.paginatedGridRepository by + Kosmos.Fixture { + testCase.context.orCreateTestableResources + PaginatedGridRepository(testCase.context.resources, configurationRepository) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt index 6e1197737630..f4d281db627b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository +import com.android.systemui.qs.panels.data.repository.fixedColumnsRepository -val Kosmos.infiniteGridSizeInteractor by - Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) } +val Kosmos.fixedColumnsSizeInteractor by + Kosmos.Fixture { FixedColumnsSizeInteractor(fixedColumnsRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt index 7f387d7c706d..320c2ec1bb99 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt @@ -20,5 +20,5 @@ import com.android.systemui.kosmos.Kosmos val Kosmos.infiniteGridConsistencyInteractor by Kosmos.Fixture { - InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor) + InfiniteGridConsistencyInteractor(iconTilesInteractor, fixedColumnsSizeInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt index 82cfaf50f823..be00152cb511 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel -import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridSizeViewModel val Kosmos.infiniteGridLayout by - Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, infiniteGridSizeViewModel) } + Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractorKosmos.kt new file mode 100644 index 000000000000..a922e5df2853 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PaginatedGridInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.paginatedGridRepository + +val Kosmos.paginatedGridInteractor by + Kosmos.Fixture { PaginatedGridInteractor(paginatedGridRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt index f6dfb8bcd47b..feadc9133bbf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.domain.interactor.infiniteGridSizeInteractor +import com.android.systemui.qs.panels.domain.interactor.fixedColumnsSizeInteractor -val Kosmos.infiniteGridSizeViewModel by - Kosmos.Fixture { InfiniteGridSizeViewModelImpl(infiniteGridSizeInteractor) } +val Kosmos.fixedColumnsSizeViewModel by + Kosmos.Fixture { FixedColumnsSizeViewModelImpl(fixedColumnsSizeInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MockTileViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MockTileViewModel.kt new file mode 100644 index 000000000000..5386ecef37aa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MockTileViewModel.kt @@ -0,0 +1,33 @@ +/* + * 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 com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +fun MockTileViewModel( + spec: TileSpec, + state: StateFlow<QSTile.State> = MutableStateFlow(QSTile.State()) +): TileViewModel = mock { + whenever(this.spec).thenReturn(spec) + whenever(this.state).thenReturn(state) + whenever(this.currentState).thenReturn(state.value) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt new file mode 100644 index 000000000000..85e92651cba2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * 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 com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor + +val Kosmos.paginatedGridViewModel by + Kosmos.Fixture { + PaginatedGridViewModel( + iconTilesViewModel, + fixedColumnsSizeViewModel, + iconLabelVisibilityViewModel, + paginatedGridInteractor, + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt index b07cc7d8612d..fde174d7241e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt @@ -22,7 +22,7 @@ val Kosmos.partitionedGridViewModel by Kosmos.Fixture { PartitionedGridViewModel( iconTilesViewModel, - infiniteGridSizeViewModel, + fixedColumnsSizeViewModel, iconLabelVisibilityViewModel, ) } |