Loading packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +17 −6 Original line number Diff line number Diff line Loading @@ -18,20 +18,31 @@ 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.GridLayoutType 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 import dagger.Provides import dagger.multibindings.IntoSet @Module interface PanelsModule { @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository @Binds @IntoMap @GridLayoutTypeKey(InfiniteGridLayoutType::class) fun bindGridLayout(impl: InfiniteGridLayout): GridLayout companion object { @Provides @IntoSet fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> { return Pair(InfiniteGridLayoutType, gridLayout) } @Provides fun provideGridLayoutMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> ): Map<GridLayoutType, GridLayout> { return entries.toMap() } } } packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +0 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,5 @@ interface GridLayout { fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, tile: @Composable (TileViewModel) -> Unit, ) } packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +167 −8 Original line number Diff line number Diff line Loading @@ -16,32 +16,77 @@ 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.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect 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 androidx.compose.ui.res.integerResource import com.android.compose.theme.colorAttr import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.mapLatest class InfiniteGridLayout @Inject constructor() : GridLayout { @SysUISingleton class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) : 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) } } } val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) LazyVerticalGrid( columns = Loading @@ -54,14 +99,128 @@ class InfiniteGridLayout @Inject constructor() : GridLayout { Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), modifier = modifier ) { tiles.forEach { item(span = { it.span() }) { tile(it) } } items( tiles.size, span = { index -> val iconOnly = iconTilesSpecs.contains(tiles[index].spec) if (iconOnly) { GridItemSpan(1) } else { GridItemSpan(2) } } ) { index -> Tile( tiles[index], iconTilesSpecs.contains(tiles[index].spec), Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) ) } } } private fun TileViewModel.span(): GridItemSpan = @OptIn(ExperimentalCoroutinesApi::class) @Composable private fun Tile( tile: TileViewModel, iconOnly: Boolean, modifier: Modifier, ) { val state: TileUiState by tile.state .mapLatest { it.toUiState() } .collectAsState(initial = tile.currentState.toUiState()) val context = LocalContext.current val horizontalAlignment = if (iconOnly) { GridItemSpan(1) Alignment.CenterHorizontally } else { GridItemSpan(2) Alignment.Start } Row( modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) .clickable { tile.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) { Icon.Resource(it.resId, null) } else { Icon.Loaded(it.getDrawable(context), null) } } } TileIcon(icon, colorAttr(state.colors.icon)) if (!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: Icon, 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 Icon.Loaded -> icon.drawable is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) } } if (loadedDrawable !is Animatable) { Icon( icon = icon, tint = color, modifier = modifier, ) } else if (icon is Icon.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 ) } } } packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.ktdeleted 100644 → 0 +0 −155 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.qs.panels.ui.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 ) } } packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +2 −7 Original line number Diff line number Diff line Loading @@ -16,21 +16,16 @@ 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 gridLayout by viewModel.gridLayout.collectAsState() val tiles by viewModel.tileViewModels.collectAsState(emptyList()) gridLayout.TileGrid(tiles, modifier) { Tile(it, modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))) } gridLayout.TileGrid(tiles, modifier) } Loading
packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +17 −6 Original line number Diff line number Diff line Loading @@ -18,20 +18,31 @@ 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.GridLayoutType 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 import dagger.Provides import dagger.multibindings.IntoSet @Module interface PanelsModule { @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository @Binds @IntoMap @GridLayoutTypeKey(InfiniteGridLayoutType::class) fun bindGridLayout(impl: InfiniteGridLayout): GridLayout companion object { @Provides @IntoSet fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> { return Pair(InfiniteGridLayoutType, gridLayout) } @Provides fun provideGridLayoutMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> ): Map<GridLayoutType, GridLayout> { return entries.toMap() } } }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +0 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,5 @@ interface GridLayout { fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, tile: @Composable (TileViewModel) -> Unit, ) }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +167 −8 Original line number Diff line number Diff line Loading @@ -16,32 +16,77 @@ 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.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect 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 androidx.compose.ui.res.integerResource import com.android.compose.theme.colorAttr import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.mapLatest class InfiniteGridLayout @Inject constructor() : GridLayout { @SysUISingleton class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) : 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) } } } val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) LazyVerticalGrid( columns = Loading @@ -54,14 +99,128 @@ class InfiniteGridLayout @Inject constructor() : GridLayout { Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), modifier = modifier ) { tiles.forEach { item(span = { it.span() }) { tile(it) } } items( tiles.size, span = { index -> val iconOnly = iconTilesSpecs.contains(tiles[index].spec) if (iconOnly) { GridItemSpan(1) } else { GridItemSpan(2) } } ) { index -> Tile( tiles[index], iconTilesSpecs.contains(tiles[index].spec), Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) ) } } } private fun TileViewModel.span(): GridItemSpan = @OptIn(ExperimentalCoroutinesApi::class) @Composable private fun Tile( tile: TileViewModel, iconOnly: Boolean, modifier: Modifier, ) { val state: TileUiState by tile.state .mapLatest { it.toUiState() } .collectAsState(initial = tile.currentState.toUiState()) val context = LocalContext.current val horizontalAlignment = if (iconOnly) { GridItemSpan(1) Alignment.CenterHorizontally } else { GridItemSpan(2) Alignment.Start } Row( modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) .clickable { tile.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) { Icon.Resource(it.resId, null) } else { Icon.Loaded(it.getDrawable(context), null) } } } TileIcon(icon, colorAttr(state.colors.icon)) if (!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: Icon, 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 Icon.Loaded -> icon.drawable is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) } } if (loadedDrawable !is Animatable) { Icon( icon = icon, tint = color, modifier = modifier, ) } else if (icon is Icon.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 ) } } }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.ktdeleted 100644 → 0 +0 −155 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.qs.panels.ui.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 ) } }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +2 −7 Original line number Diff line number Diff line Loading @@ -16,21 +16,16 @@ 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 gridLayout by viewModel.gridLayout.collectAsState() val tiles by viewModel.tileViewModels.collectAsState(emptyList()) gridLayout.TileGrid(tiles, modifier) { Tile(it, modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))) } gridLayout.TileGrid(tiles, modifier) }