diff options
| author | 2024-03-28 16:04:18 -0400 | |
|---|---|---|
| committer | 2024-04-19 14:04:55 -0400 | |
| commit | cdeef27f8bc5fff4aab126f4aeb9ae0bb70e7c2c (patch) | |
| tree | c8fdd0de887b2e2b11da61154a101eee915b30fb | |
| parent | 46f27a9cba3e1ef0f98554a202f05ffae23aad5e (diff) | |
Adding viewmodel and composable for tile layout refactor
Added the start of the tile composable with only the background colors and labels.
Missing the rest of the UI elements, clicks/long clicks, logging, etc.
Bug: 331598956
Flag: ACONFIG com.android.systemui.qs_ui_refactor DEVELOPMENT
Test: TileGridViewModelTest
Test: adb shell am start -n com.android.systemui/com.google.android.systemui.qs.ui.activity.QSActivity
Change-Id: I3ced8ee1d315ce5d2e98ae785ad19a1669a9f51a
22 files changed, 828 insertions, 1 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index fa9d507dbff5..4a73d85e77b7 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -70,6 +70,9 @@ <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">4</integer> + <!-- The number of columns in the infinite grid QuickSettings --> + <integer name="quick_settings_infinite_grid_num_columns">4</integer> + <!-- Override column number for quick settings. For now, this value has effect only when flag lockscreen.enable_landscape is enabled. TODO (b/293252410) - change this comment/resource when flag is enabled --> 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 1307296dd2b7..ee4eeb8b9e32 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 @@ -18,10 +18,20 @@ package com.android.systemui.qs.panels.dagger import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl +import com.android.systemui.qs.panels.shared.model.GridLayoutTypeKey +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.ui.compose.GridLayout +import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout import dagger.Binds import dagger.Module +import dagger.multibindings.IntoMap @Module interface PanelsModule { @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository + + @Binds + @IntoMap + @GridLayoutTypeKey(InfiniteGridLayoutType::class) + fun bindGridLayout(impl: InfiniteGridLayout): GridLayout } 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 new file mode 100644 index 000000000000..02dd33ebdbc2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt @@ -0,0 +1,29 @@ +/* + * 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.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +@SysUISingleton +class GridLayoutTypeRepository @Inject constructor() { + val layout: Flow<GridLayoutType> = flowOf(InfiniteGridLayoutType) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt new file mode 100644 index 000000000000..b6be5780bb60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt @@ -0,0 +1,28 @@ +/* + * 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.qs.panels.data.repository.GridLayoutTypeRepository +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) { + val layout: Flow<GridLayoutType> = repo.layout +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt index 367c67093605..1aec193ca029 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt @@ -24,6 +24,6 @@ import kotlinx.coroutines.flow.Flow /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */ @SysUISingleton -class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) { +class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) { val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs } 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 new file mode 100644 index 000000000000..23110dcaa560 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.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.shared.model + +/** + * Grid type for a QS grid layout. + * + * Used to inject grid layouts with Dagger and the [GridLayoutTypeKey] annotation. + */ +interface GridLayoutType + +/** Grid type representing a scrollable vertical grid. */ +data object InfiniteGridLayoutType : GridLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt new file mode 100644 index 000000000000..0dbaaba9dfac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt @@ -0,0 +1,28 @@ +/* + * 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.shared.model + +import dagger.MapKey +import kotlin.reflect.KClass + +/** + * Dagger map key to associate a [GridLayoutType] with its + * [com.android.systemui.qs.panels.ui.compose.GridLayout]. + */ +@Retention(AnnotationRetention.RUNTIME) +@MapKey +annotation class GridLayoutTypeKey(val value: KClass<out 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 new file mode 100644 index 000000000000..920cbe77e42f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -0,0 +1,30 @@ +/* + * 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.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel + +interface GridLayout { + @Composable + fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + tile: @Composable (TileViewModel) -> Unit, + ) +} 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 new file mode 100644 index 000000000000..4d0089e70e80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -0,0 +1,67 @@ +/* + * 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.Arrangement +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.integerResource +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.res.R +import javax.inject.Inject + +class InfiniteGridLayout @Inject constructor() : GridLayout { + + @Composable + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + tile: @Composable (TileViewModel) -> Unit + ) { + DisposableEffect(tiles) { + val token = Any() + tiles.forEach { it.startListening(token) } + onDispose { tiles.forEach { it.stopListening(token) } } + } + + LazyVerticalGrid( + columns = + GridCells.Fixed( + integerResource(R.integer.quick_settings_infinite_grid_num_columns) + ), + verticalArrangement = + Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + horizontalArrangement = + Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), + modifier = modifier + ) { + tiles.forEach { item(span = { it.span() }) { tile(it) } } + } + } + + private fun TileViewModel.span(): GridItemSpan = + if (iconOnly) { + GridItemSpan(1) + } else { + GridItemSpan(2) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt new file mode 100644 index 000000000000..35f29705fab2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -0,0 +1,155 @@ +/* + * 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 android.graphics.drawable.Animatable +import android.text.TextUtils +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import com.android.compose.theme.colorAttr +import com.android.systemui.common.shared.model.Icon as IconModel +import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.qs.panels.ui.viewmodel.TileUiState +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R +import kotlinx.coroutines.delay + +@Composable +fun Tile( + tileViewModel: TileViewModel, + modifier: Modifier = Modifier, +) { + val state: TileUiState by tileViewModel.state.collectAsState(tileViewModel.currentState) + val context = LocalContext.current + val horizontalAlignment = + if (state.iconOnly) { + Alignment.CenterHorizontally + } else { + Alignment.Start + } + + Row( + modifier = + modifier + .fillMaxWidth() + .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) + .clickable { tileViewModel.onClick(null) } + .background(colorAttr(state.colors.background)) + .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = + Arrangement.spacedBy( + space = dimensionResource(id = R.dimen.qs_label_container_margin), + alignment = horizontalAlignment + ) + ) { + val icon = + remember(state.icon) { + state.icon.get().let { + if (it is QSTileImpl.ResourceIcon) { + IconModel.Resource(it.resId, null) + } else { + IconModel.Loaded(it.getDrawable(context), null) + } + } + } + TileIcon(icon, colorAttr(state.colors.icon)) + + if (!state.iconOnly) { + Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { + Text( + state.label.toString(), + color = colorAttr(state.colors.label), + modifier = Modifier.basicMarquee(), + ) + if (!TextUtils.isEmpty(state.secondaryLabel)) { + Text( + state.secondaryLabel.toString(), + color = colorAttr(state.colors.secondaryLabel), + modifier = Modifier.basicMarquee(), + ) + } + } + } + } +} + +@OptIn(ExperimentalAnimationGraphicsApi::class) +@Composable +private fun TileIcon(icon: IconModel, color: Color) { + val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size)) + val context = LocalContext.current + val loadedDrawable = + remember(icon, context) { + when (icon) { + is IconModel.Loaded -> icon.drawable + is IconModel.Resource -> AppCompatResources.getDrawable(context, icon.res) + } + } + if (loadedDrawable !is Animatable) { + Icon( + icon = icon, + tint = color, + modifier = modifier, + ) + } else if (icon is IconModel.Resource) { + val image = AnimatedImageVector.animatedVectorResource(id = icon.res) + var atEnd by remember(icon.res) { mutableStateOf(false) } + LaunchedEffect(key1 = icon.res) { + delay(350) + atEnd = true + } + val painter = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) + Image( + painter = painter, + contentDescription = null, + colorFilter = ColorFilter.tint(color = color), + modifier = modifier + ) + } +} 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 new file mode 100644 index 000000000000..a528eed4a346 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -0,0 +1,36 @@ +/* + * 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.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel +import com.android.systemui.res.R + +@Composable +fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { + val gridLayout by viewModel.gridLayout.collectAsState(InfiniteGridLayout()) + val tiles by viewModel.tileViewModels.collectAsState(emptyList()) + + gridLayout.TileGrid(tiles, modifier) { + Tile(it, modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileColorAttributes.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileColorAttributes.kt new file mode 100644 index 000000000000..1290bf34d1f1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileColorAttributes.kt @@ -0,0 +1,61 @@ +/* + * 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 android.service.quicksettings.Tile +import androidx.annotation.AttrRes +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.res.R + +data class TileColorAttributes( + @AttrRes val background: Int = 0, + @AttrRes val label: Int = 0, + @AttrRes val secondaryLabel: Int = 0, + @AttrRes val icon: Int = 0, +) + +val ActiveTileColorAttributes = + TileColorAttributes( + background = R.attr.shadeActive, + label = R.attr.onShadeActive, + secondaryLabel = R.attr.onShadeActiveVariant, + icon = R.attr.onShadeActive, + ) + +val InactiveTileColorAttributes = + TileColorAttributes( + background = R.attr.shadeInactive, + label = R.attr.onShadeInactive, + secondaryLabel = R.attr.onShadeInactiveVariant, + icon = R.attr.onShadeInactiveVariant, + ) + +val UnavailableTileColorAttributes = + TileColorAttributes( + background = R.attr.shadeDisabled, + label = R.attr.outline, + secondaryLabel = R.attr.outline, + icon = R.attr.outline, + ) + +fun QSTile.State.colors(): TileColorAttributes = + when (state) { + Tile.STATE_UNAVAILABLE -> UnavailableTileColorAttributes + Tile.STATE_ACTIVE -> ActiveTileColorAttributes + Tile.STATE_INACTIVE -> InactiveTileColorAttributes + else -> TileColorAttributes() + } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt new file mode 100644 index 000000000000..fc134602e9cb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt @@ -0,0 +1,50 @@ +/* + * 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.qs.panels.domain.interactor.GridLayoutTypeInteractor +import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.panels.ui.compose.GridLayout +import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +@SysUISingleton +class TileGridViewModel +@Inject +constructor( + gridLayoutTypeInteractor: GridLayoutTypeInteractor, + gridLayoutMap: Map<Class<out GridLayoutType>, @JvmSuppressWildcards GridLayout>, + tilesInteractor: CurrentTilesInteractor, + iconTilesInteractor: IconTilesInteractor, +) { + val gridLayout: Flow<GridLayout> = + gridLayoutTypeInteractor.layout.map { + gridLayoutMap[it::class.java] ?: InfiniteGridLayout() + } + val tileViewModels: Flow<List<TileViewModel>> = + combine(tilesInteractor.currentTiles, iconTilesInteractor.iconTilesSpecs) { + tiles, + iconTilesSpecs -> + tiles.map { TileViewModel(it.tile, iconTilesSpecs.contains(it.spec)) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt new file mode 100644 index 000000000000..f4b7255693e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -0,0 +1,38 @@ +/* + * 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 java.util.function.Supplier + +data class TileUiState( + val label: CharSequence, + val secondaryLabel: CharSequence, + val colors: TileColorAttributes, + val icon: Supplier<QSTile.Icon>, + val iconOnly: Boolean, +) + +fun QSTile.State.toUiState(iconOnly: Boolean): TileUiState { + return TileUiState( + label ?: "", + secondaryLabel ?: "", + colors(), + icon?.let { Supplier { icon } } ?: iconSupplier, + iconOnly, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt new file mode 100644 index 000000000000..08e9119a6c76 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt @@ -0,0 +1,58 @@ +/* + * 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 android.view.View +import android.view.View.OnLongClickListener +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +class TileViewModel(private val tile: QSTile, val iconOnly: Boolean = false) : + OnLongClickListener, View.OnClickListener { + val state: Flow<TileUiState> = + conflatedCallbackFlow { + val callback = QSTile.Callback { trySend(it.copy()) } + + tile.addCallback(callback) + + awaitClose { tile.removeCallback(callback) } + } + .onStart { emit(tile.state) } + .map { it.toUiState(iconOnly) } + .distinctUntilChanged() + + val currentState: TileUiState + get() = tile.state.toUiState(iconOnly) + + override fun onClick(view: View?) { + tile.click(view) + } + + override fun onLongClick(view: View?): Boolean { + tile.longClick(view) + return true + } + + fun startListening(token: Any) = tile.setListening(token, true) + + fun stopListening(token: Any) = tile.setListening(token, false) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt index a3c2cbba1b1b..d6325c049823 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel import javax.inject.Inject @SysUISingleton @@ -25,4 +26,5 @@ class QuickSettingsContainerViewModel @Inject constructor( val brightnessSliderViewModel: BrightnessSliderViewModel, + val tileGridViewModel: TileGridViewModel, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt new file mode 100644 index 000000000000..e8c5fd978319 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt @@ -0,0 +1,84 @@ +/* + * 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 android.testing.AndroidTestingRunner +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.plugins.qs.QSTile +import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository +import com.android.systemui.qs.pipeline.domain.interactor.FakeQSTile +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.qsTileFactory +import com.android.systemui.testKosmos +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class TileGridViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos().apply { qsTileFactory = FakeQSFactory(::tileCreator) } + private val underTest = with(kosmos) { tileGridViewModel } + + @Test + fun noIconTiles() = + with(kosmos) { + testScope.runTest { + val latest by collectLastValue(underTest.tileViewModels) + + tileSpecRepository.setTiles( + 0, + listOf( + TileSpec.create("bluetooth"), + TileSpec.create("internet"), + TileSpec.create("alarm") + ) + ) + + latest!!.forEach { Assert.assertFalse(it.iconOnly) } + } + } + + @Test + fun withIconTiles() = + with(kosmos) { + testScope.runTest { + val latest by collectLastValue(underTest.tileViewModels) + + tileSpecRepository.setTiles( + 0, + listOf( + TileSpec.create("airplane"), + TileSpec.create("flashlight"), + TileSpec.create("rotation") + ) + ) + + latest!!.forEach { Assert.assertTrue(it.iconOnly) } + } + } + + private fun tileCreator(spec: String): QSTile { + return FakeQSTile(0).apply { tileSpec = spec } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt new file mode 100644 index 000000000000..f846d5712497 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * 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.kosmos.Kosmos + +val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt new file mode 100644 index 000000000000..685e77223fd5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * 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.kosmos.Kosmos + +val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt new file mode 100644 index 000000000000..e44fa0717602 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.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.gridLayoutTypeRepository + +val Kosmos.gridLayoutTypeInteractor by + Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt new file mode 100644 index 000000000000..eaa702f9f6da --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.iconTilesRepository + +val Kosmos.iconTilesInteractor by Kosmos.Fixture { IconTilesInteractor(iconTilesRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt new file mode 100644 index 000000000000..9851f043e39a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt @@ -0,0 +1,34 @@ +/* + * 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.qs.panels.domain.interactor.gridLayoutTypeInteractor +import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor + +val Kosmos.tileGridViewModel by + Kosmos.Fixture { + TileGridViewModel( + gridLayoutTypeInteractor, + mapOf(Pair(InfiniteGridLayoutType::class.java, InfiniteGridLayout())), + currentTilesInteractor, + iconTilesInteractor + ) + } |