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

Commit ba9d9187 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Simplify shade header highlight chips rendering." into main

parents 985278f6 64d110db
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.shade.ui.composable.isFullWidthShade
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import dagger.Lazy
@@ -115,6 +116,8 @@ constructor(
                    }
                OverlayShadeHeader(
                    viewModel = headerViewModel,
                    notificationsHighlight = ShadeHeader.ChipHighlight.Strong,
                    quickSettingsHighlight = ShadeHeader.ChipHighlight.Weak,
                    showClock = !isFullWidth,
                    modifier = Modifier.element(NotificationsShade.Elements.StatusBar),
                )
+3 −0
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.QuickSettingsOverlayHeader
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.shade.ui.composable.isFullWidthShade
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
@@ -152,6 +153,8 @@ constructor(
                header = {
                    OverlayShadeHeader(
                        viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
                        notificationsHighlight = ShadeHeader.ChipHighlight.Weak,
                        quickSettingsHighlight = ShadeHeader.ChipHighlight.Strong,
                        showClock = true,
                        modifier = Modifier.element(QuickSettingsShade.Elements.StatusBar),
                    )
+46 −21
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -99,7 +100,6 @@ import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddi
import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingVertical
import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -138,13 +138,37 @@ object ShadeHeader {
        val ChipPaddingVertical = 4.dp

        val StatusBarHeight: Dp
            @Composable
            get() = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
            @Composable get() = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
    }

    object TestTags {
        const val Root = "shade_header_root"
    }

    /** Represents the background highlighting of a header icons chip. */
    sealed interface ChipHighlight {
        val backgroundColor: Color
            @Composable @ReadOnlyComposable get

        val foregroundColor: Color
            @Composable @ReadOnlyComposable get

        data object Weak : ChipHighlight {
            override val backgroundColor: Color
                @Composable get() = MaterialTheme.colorScheme.surface.copy(alpha = 0.1f)

            override val foregroundColor: Color
                @Composable get() = MaterialTheme.colorScheme.onSurface
        }

        data object Strong : ChipHighlight {
            override val backgroundColor: Color
                @Composable get() = MaterialTheme.colorScheme.primaryContainer

            override val foregroundColor: Color
                @Composable get() = MaterialTheme.colorScheme.onPrimaryContainer
        }
    }
}

/** The status bar that appears above the Shade scene on small screens */
@@ -309,6 +333,8 @@ fun ContentScope.ExpandedShadeHeader(
@Composable
fun ContentScope.OverlayShadeHeader(
    viewModel: ShadeHeaderViewModel,
    notificationsHighlight: ShadeHeader.ChipHighlight,
    quickSettingsHighlight: ShadeHeader.ChipHighlight,
    showClock: Boolean,
    modifier: Modifier = Modifier,
) {
@@ -326,7 +352,6 @@ fun ContentScope.OverlayShadeHeader(
                horizontalArrangement = Arrangement.spacedBy(5.dp),
                modifier = Modifier.padding(horizontal = horizontalPadding),
            ) {
                val chipHighlight = viewModel.notificationsChipHighlight
                if (showClock) {
                    Clock(
                        onClick = viewModel::onClockClicked,
@@ -335,7 +360,7 @@ fun ContentScope.OverlayShadeHeader(
                }
                NotificationsChip(
                    onClick = viewModel::onNotificationIconChipClicked,
                    backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
                    backgroundColor = notificationsHighlight.backgroundColor,
                    modifier =
                        Modifier.bouncy(
                            isEnabled = viewModel.animateNotificationsChipBounce,
@@ -350,7 +375,7 @@ fun ContentScope.OverlayShadeHeader(
                    VariableDayDate(
                        longerDateText = viewModel.longerDateText,
                        shorterDateText = viewModel.shorterDateText,
                        textColor = chipHighlight.foregroundColor(MaterialTheme.colorScheme),
                        textColor = notificationsHighlight.foregroundColor,
                    )
                }
            }
@@ -361,9 +386,8 @@ fun ContentScope.OverlayShadeHeader(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier.padding(horizontal = horizontalPadding),
            ) {
                val chipHighlight = viewModel.quickSettingsChipHighlight
                SystemIconChip(
                    backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
                    backgroundColor = quickSettingsHighlight.backgroundColor,
                    onClick = viewModel::onSystemIconChipClicked,
                    modifier =
                        Modifier.bouncy(
@@ -380,16 +404,18 @@ fun ContentScope.OverlayShadeHeader(
                        with(LocalDensity.current) {
                            (if (NewStatusBarIcons.isEnabled) 3.sp else 6.sp).toDp()
                        }
                    val isHighlighted = quickSettingsHighlight is ShadeHeader.ChipHighlight.Strong
                    StatusIcons(
                        viewModel = viewModel,
                        useExpandedFormat = false,
                        modifier = Modifier.padding(end = paddingEnd).weight(1f, fill = false),
                        isHighlighted = isHighlighted,
                    )
                    BatteryIcon(
                        createBatteryMeterViewController =
                            viewModel.createBatteryMeterViewController,
                        useExpandedFormat = false,
                        chipHighlight = chipHighlight,
                        isHighlighted = isHighlighted,
                    )
                }
                if (isPrivacyChipVisible) {
@@ -523,7 +549,7 @@ private fun BatteryIcon(
    createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
    useExpandedFormat: Boolean,
    modifier: Modifier = Modifier,
    chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
    isHighlighted: Boolean = false,
) {
    val localContext = LocalContext.current
    val themedContext =
@@ -557,11 +583,11 @@ private fun BatteryIcon(
                if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON
            )
            // TODO(b/397223606): Get the actual spec for this.
            if (chipHighlight is HeaderChipHighlight.Strong) {
                batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
            } else if (chipHighlight is HeaderChipHighlight.Weak) {
                batteryIcon.updateColors(primaryColor, inverseColor, primaryColor)
            }
            batteryIcon.updateColors(
                primaryColor,
                inverseColor,
                if (isHighlighted) inverseColor else primaryColor,
            )
        },
        modifier = modifier,
    )
@@ -642,6 +668,7 @@ private fun ContentScope.StatusIcons(
    viewModel: ShadeHeaderViewModel,
    useExpandedFormat: Boolean,
    modifier: Modifier = Modifier,
    isHighlighted: Boolean = false,
) {
    val localContext = LocalContext.current
    val themedContext =
@@ -672,11 +699,9 @@ private fun ContentScope.StatusIcons(
        viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS)
    }

    val chipHighlight = viewModel.quickSettingsChipHighlight

    // TODO(408001821): Use composable system status icons here instead.
    AndroidView(
        factory = { context ->
        factory = {
            iconManager.setTint(primaryColor, inverseColor)
            viewModel.statusBarIconController.addIconGroup(iconManager)

@@ -712,9 +737,9 @@ private fun ContentScope.StatusIcons(
            }

            // TODO(b/397223606): Get the actual spec for this.
            if (chipHighlight is HeaderChipHighlight.Strong) {
            if (isHighlighted) {
                iconManager.setTint(inverseColor, primaryColor)
            } else if (chipHighlight is HeaderChipHighlight.Weak) {
            } else {
                iconManager.setTint(primaryColor, inverseColor)
            }
        },
@@ -726,7 +751,7 @@ private fun ContentScope.StatusIcons(
private fun NotificationsChip(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    backgroundColor: Color = Color.Unspecified,
    backgroundColor: Color,
    content: @Composable BoxScope.() -> Unit,
) {
    val interactionSource = remember { MutableInteractionSource() }
+0 −88
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
@@ -25,11 +24,8 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.shade.domain.interactor.enableSplitShade
import com.android.systemui.shade.domain.interactor.shadeMode
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
@@ -248,90 +244,6 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
        }

    @Test
    fun highlightChips_notifsOpenInSingleShade_bothNone() =
        testScope.runTest {
            kosmos.enableSingleShade()
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            setScene(Scenes.Shade)
            assertThat(currentScene).isEqualTo(Scenes.Shade)
            assertThat(currentOverlays).isEmpty()

            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
        }

    @Test
    fun highlightChips_notifsOpenInSplitShade_bothNone() =
        testScope.runTest {
            kosmos.enableSplitShade()
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            setScene(Scenes.Shade)
            assertThat(currentScene).isEqualTo(Scenes.Shade)
            assertThat(currentOverlays).isEmpty()

            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
        }

    @Test
    fun highlightChips_quickSettingsOpenInSingleShade_bothNone() =
        testScope.runTest {
            kosmos.enableSingleShade()
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
            setScene(Scenes.QuickSettings)
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
            assertThat(currentOverlays).isEmpty()

            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
        }

    @Test
    fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() =
        testScope.runTest {
            // Test the lockscreen scenario.
            setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)

            // Test the unlocked scenario.
            setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade)
            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
        }

    @Test
    fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() =
        testScope.runTest {
            // Test the lockscreen scenario.
            setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)

            // Test the unlocked scenario.
            setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade)
            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
        }

    @Test
    fun highlightChips_noOverlaysInDualShade_bothNone() =
        testScope.runTest {
            // Test the lockscreen scenario.
            setupDualShadeState(scene = Scenes.Lockscreen)
            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)

            // Test the unlocked scenario.
            setupDualShadeState(scene = Scenes.Gone)
            assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
            assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
        }

    companion object {
        private val SUB_1 =
            SubscriptionModel(
+3 −62
Original line number Diff line number Diff line
@@ -24,9 +24,7 @@ import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.provider.Settings
import android.view.ViewGroup
import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.IntRect
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.battery.BatteryMeterViewController
@@ -98,34 +96,6 @@ constructor(
        (ViewGroup, StatusBarLocation) -> BatteryMeterViewController =
        batteryMeterViewControllerFactory::create

    val notificationsChipHighlight: HeaderChipHighlight by
        hydrator.hydratedStateOf(
            traceName = "notificationsChipHighlight",
            initialValue = HeaderChipHighlight.None,
            source =
                sceneInteractor.currentOverlays.map { overlays ->
                    when {
                        Overlays.NotificationsShade in overlays -> HeaderChipHighlight.Strong
                        Overlays.QuickSettingsShade in overlays -> HeaderChipHighlight.Weak
                        else -> HeaderChipHighlight.None
                    }
                },
        )

    val quickSettingsChipHighlight: HeaderChipHighlight by
        hydrator.hydratedStateOf(
            traceName = "quickSettingsChipHighlight",
            initialValue = HeaderChipHighlight.None,
            source =
                sceneInteractor.currentOverlays.map { overlays ->
                    when {
                        Overlays.QuickSettingsShade in overlays -> HeaderChipHighlight.Strong
                        Overlays.NotificationsShade in overlays -> HeaderChipHighlight.Weak
                        else -> HeaderChipHighlight.None
                    }
                },
        )

    /** True if there is exactly one mobile connection. */
    val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier

@@ -264,39 +234,10 @@ constructor(
        dualShadeEducationInteractor.onDualShadeEducationElementBoundsChange(element, bounds)
    }

    /** Represents the background highlight of a header icons chip. */
    sealed interface HeaderChipHighlight {

        fun backgroundColor(colorScheme: ColorScheme): Color

        fun foregroundColor(colorScheme: ColorScheme): Color

        data object None : HeaderChipHighlight {
            override fun backgroundColor(colorScheme: ColorScheme): Color = Color.Unspecified

            override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
        }

        data object Weak : HeaderChipHighlight {
            override fun backgroundColor(colorScheme: ColorScheme): Color =
                colorScheme.surface.copy(alpha = 0.1f)

            override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSurface
        }

        data object Strong : HeaderChipHighlight {
            override fun backgroundColor(colorScheme: ColorScheme): Color =
                colorScheme.primaryContainer

            override fun foregroundColor(colorScheme: ColorScheme): Color =
                colorScheme.onPrimaryContainer
        }
    }

    private fun getFormatFromPattern(pattern: String?): DateFormat {
        val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault())
        format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
        return format
        return DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault()).apply {
            setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
        }
    }

    @AssistedFactory