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

Commit 74f932b8 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Prevent constant recomposition of TileGrid

Using flow operators in a Composable will cause constant recompositions,
instead collect to a State and transform that.

Also, make some key classes @Immutable

Test: manual, log recompositions
Bug: 331598956
Flag: com.android.systemui.qs_ui_refactor
Change-Id: I92e371db9ae7e0270c2e0b3043cb18a27839b80c
parent 2fa844b3
Loading
Loading
Loading
Loading
+18 −13
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.graphics.drawable.Animatable
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
@@ -81,7 +82,6 @@ import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
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.pipeline.domain.interactor.CurrentTilesInteractor
@@ -91,7 +91,6 @@ import com.android.systemui.res.R
import java.util.function.Supplier
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.mapLatest

object TileType

@@ -103,29 +102,27 @@ fun Tile(
    showLabels: Boolean = false,
    modifier: Modifier,
) {
    val state: TileUiState by
        tile.state
            .mapLatest { it.toUiState() }
            .collectAsStateWithLifecycle(tile.currentState.toUiState())
    val colors = TileDefaults.getColorForState(state.state)
    val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
    val uiState = remember(state) { state.toUiState() }
    val colors = TileDefaults.getColorForState(uiState.state)

    TileContainer(
        colors = colors,
        showLabels = showLabels,
        label = state.label.toString(),
        label = uiState.label,
        iconOnly = iconOnly,
        clickEnabled = true,
        onClick = tile::onClick,
        onLongClick = tile::onLongClick,
        modifier = modifier,
    ) {
        val icon = getTileIcon(icon = state.icon)
        val icon = getTileIcon(icon = uiState.icon)
        if (iconOnly) {
            TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
        } else {
            LargeTileContent(
                label = state.label.toString(),
                secondaryLabel = state.secondaryLabel.toString(),
                label = uiState.label,
                secondaryLabel = uiState.secondaryLabel,
                icon = icon,
                colors = colors,
                clickEnabled = true,
@@ -234,19 +231,26 @@ private fun LargeTileContent(
            Text(
                label,
                color = colors.label,
                modifier = Modifier.basicMarquee(),
                modifier = Modifier.tileMarquee(),
            )
            if (!TextUtils.isEmpty(secondaryLabel)) {
                Text(
                    secondaryLabel ?: "",
                    color = colors.secondaryLabel,
                    modifier = Modifier.basicMarquee(),
                    modifier = Modifier.tileMarquee(),
                )
            }
        }
    }
}

private fun Modifier.tileMarquee(): Modifier {
    return basicMarquee(
        iterations = 1,
        initialDelayMillis = 200,
    )
}

@Composable
fun TileLazyGrid(
    modifier: Modifier = Modifier,
@@ -452,6 +456,7 @@ private fun TileIcon(
    animateToEnd: Boolean = false,
    modifier: Modifier = Modifier,
) {
    Log.d("Fabian", "Recomposing tile icon")
    val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
    val context = LocalContext.current
    val loadedDrawable =
+18 −8
Original line number Diff line number Diff line
@@ -26,8 +26,8 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
@@ -54,14 +54,24 @@ constructor(
            quickQuickSettingsRowInteractor.defaultRows
        )

    val tileViewModels: Flow<List<SizedTile<TileViewModel>>> =
        columns.flatMapLatest { columns ->
    val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
        columns
            .flatMapLatest { columns ->
                tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
                    tiles
                        .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
                        .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
                }
            }
            .stateIn(
                applicationScope,
                SharingStarted.WhileSubscribed(),
                tilesInteractor.currentTiles.value
                    .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
                    .let {
                        splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
                    }
            )

    private val TileSpec.width: Int
        get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
+6 −4
Original line number Diff line number Diff line
@@ -16,20 +16,22 @@

package com.android.systemui.qs.panels.ui.viewmodel

import androidx.compose.runtime.Immutable
import com.android.systemui.plugins.qs.QSTile
import java.util.function.Supplier

@Immutable
data class TileUiState(
    val label: CharSequence,
    val secondaryLabel: CharSequence,
    val label: String,
    val secondaryLabel: String,
    val state: Int,
    val icon: Supplier<QSTile.Icon>,
)

fun QSTile.State.toUiState(): TileUiState {
    return TileUiState(
        label ?: "",
        secondaryLabel ?: "",
        label?.toString() ?: "",
        secondaryLabel?.toString() ?: "",
        state,
        icon?.let { Supplier { icon } } ?: iconSupplier,
    )
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.qs.panels.ui.viewmodel

import androidx.compose.runtime.Immutable
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart

@Immutable
class TileViewModel(private val tile: QSTile, val spec: TileSpec) {
    val state: Flow<QSTile.State> =
        conflatedCallbackFlow {