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

Commit c5d2ffd5 authored by Shawn Lee's avatar Shawn Lee
Browse files

[flexiglass] Implement dual shade Shade Header chips

Adds chips to dual shade header that are highlighted and tappable, with proper contents in portrait and landscape. Also adds secondary shade header to QS shade overlay.

Bug: 384766545
Test: manually verified shade layout in portrait and large screen
Test: added unit tests for onClick logic
Flag: com.android.systemui.scene_container
Change-Id: If93367eccfbd498c739d70aa16de5115495ca150
parent 0e690c85
Loading
Loading
Loading
Loading
+21 −15
Original line number Diff line number Diff line
@@ -41,9 +41,10 @@ import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
@@ -60,6 +61,8 @@ constructor(
    private val tintedIconManagerFactory: TintedIconManager.Factory,
    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
    private val statusBarIconController: StatusBarIconController,
    private val notificationIconContainerStatusBarViewBinder:
        NotificationIconContainerStatusBarViewBinder,
    private val shadeSession: SaveableSession,
    private val stackScrollView: Lazy<NotificationScrollView>,
    private val clockSection: DefaultClockSection,
@@ -79,7 +82,6 @@ constructor(

    @Composable
    override fun ContentScope.Content(modifier: Modifier) {

        val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings)

        val viewModel =
@@ -92,25 +94,28 @@ constructor(
            }

        OverlayShade(
            isShadeLayoutWide = viewModel.isShadeLayoutWide,
            panelAlignment = Alignment.TopStart,
            modifier = modifier,
            onScrimClicked = viewModel::onScrimClicked,
        ) {
            Box {
                Column {
                    if (viewModel.showHeader) {
                        val burnIn = rememberBurnIn(clockInteractor)

                        CollapsedShadeHeader(
            header = {
                OverlayShadeHeader(
                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                    createTintedIconManager = tintedIconManagerFactory::create,
                            createBatteryMeterViewController =
                                batteryMeterViewControllerFactory::create,
                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
                    statusBarIconController = statusBarIconController,
                    notificationIconContainerStatusBarViewBinder =
                        notificationIconContainerStatusBarViewBinder,
                    modifier =
                        Modifier.element(NotificationsShade.Elements.StatusBar)
                            .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
                )
            },
        ) {
            Box {
                Column {
                    if (viewModel.showHeader) {
                        val burnIn = rememberBurnIn(clockInteractor)

                        with(clockSection) {
                            SmallClock(
@@ -126,16 +131,17 @@ constructor(
                        stackScrollView = stackScrollView.get(),
                        viewModel = placeholderViewModel,
                        maxScrimTop = { 0f },
                        shouldPunchHoleBehindScrim = false,
                        stackTopPadding = notificationStackPadding,
                        stackBottomPadding = notificationStackPadding,
                        shouldPunchHoleBehindScrim = false,
                        shouldFillMaxSize = false,
                        shouldShowScrim = false,
                        supportNestedScrolling = false,
                        modifier = Modifier.fillMaxWidth(),
                    )
                }
                // Communicates the bottom position of the drawable area within the shade to NSSL.
                // Communicates the bottom position of the drawable area within the shade to
                // NSSL.
                NotificationStackCutoffGuideline(
                    stackScrollView = stackScrollView.get(),
                    viewModel = placeholderViewModel,
+74 −42
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
@@ -49,6 +50,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.NotificationsShade
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.flags.QsDetailedView
@@ -61,8 +63,11 @@ import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsView
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
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.SingleShadeMeasurePolicy
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -82,6 +87,8 @@ constructor(
    private val tintedIconManagerFactory: TintedIconManager.Factory,
    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
    private val statusBarIconController: StatusBarIconController,
    private val notificationIconContainerStatusBarViewBinder:
        NotificationIconContainerStatusBarViewBinder,
    private val notificationStackScrollView: Lazy<NotificationScrollView>,
    private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
) : Overlay {
@@ -108,12 +115,35 @@ constructor(
        // set the bounds to null when the QuickSettings overlay disappears
        DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }

        Box(modifier = modifier) {
            SnoozeableHeadsUpNotificationSpace(
                stackScrollView = notificationStackScrollView.get(),
                viewModel =
                    rememberViewModel("QuickSettingsShadeOverlay") {
                        notificationsPlaceholderViewModelFactory.create()
                    },
            )
            OverlayShade(
                isShadeLayoutWide = viewModel.isShadeLayoutWide,
                panelAlignment = Alignment.TopEnd,
            modifier = modifier,
                onScrimClicked = viewModel::onScrimClicked,
                header = {
                    OverlayShadeHeader(
                        viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                        createTintedIconManager = tintedIconManagerFactory::create,
                        createBatteryMeterViewController =
                            batteryMeterViewControllerFactory::create,
                        statusBarIconController = statusBarIconController,
                        notificationIconContainerStatusBarViewBinder =
                            notificationIconContainerStatusBarViewBinder,
                        modifier =
                            Modifier.element(NotificationsShade.Elements.StatusBar)
                                .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
                    )
                },
            ) {
            Column(
                ShadeBody(
                    viewModel = viewModel.quickSettingsContainerViewModel,
                    modifier =
                        Modifier.onPlaced { coordinates ->
                            val boundsInWindow = coordinates.boundsInWindow()
@@ -131,30 +161,23 @@ constructor(
                                    bottomRadius = panelCornerRadius,
                                )
                            viewModel.onPanelShapeChanged(shape)
                    }
            ) {
                if (viewModel.showHeader) {
                    CollapsedShadeHeader(
                        },
                    header = {
                        if (viewModel.isShadeLayoutWide) {
                            QuickSettingsOverlayHeader(
                                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                        createTintedIconManager = tintedIconManagerFactory::create,
                                createBatteryMeterViewController =
                                    batteryMeterViewControllerFactory::create,
                        statusBarIconController = statusBarIconController,
                                modifier =
                                    Modifier.padding(top = QuickSettingsShade.Dimensions.Padding),
                            )
                        }
                ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
            }

            SnoozeableHeadsUpNotificationSpace(
                stackScrollView = notificationStackScrollView.get(),
                viewModel =
                    rememberViewModel("QuickSettingsShadeOverlay") {
                        notificationsPlaceholderViewModelFactory.create()
                    },
                )
            }
        }
    }
}

// The possible states of the `ShadeBody`.
sealed interface ShadeBodyState {
@@ -166,7 +189,11 @@ sealed interface ShadeBodyState {
}

@Composable
fun ContentScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
fun ContentScope.ShadeBody(
    viewModel: QuickSettingsContainerViewModel,
    modifier: Modifier = Modifier,
    header: @Composable () -> Unit,
) {
    val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
    val tileDetails =
        if (QsDetailedView.isEnabled) viewModel.detailsViewModel.activeTileDetails else null
@@ -185,16 +212,19 @@ fun ContentScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
                EditMode(
                    viewModel = viewModel.editModeViewModel,
                    modifier =
                        Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
                        modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
                )
            }

            ShadeBodyState.TileDetails -> {
                TileDetails(viewModel.detailsViewModel)
                TileDetails(modifier = modifier, viewModel.detailsViewModel)
            }

            else -> {
                QuickSettingsLayout(
                    viewModel = viewModel,
                    modifier = Modifier.sysuiResTag("quick_settings_panel"),
                    modifier = modifier.sysuiResTag("quick_settings_panel"),
                    header = header,
                )
            }
        }
@@ -206,6 +236,7 @@ fun ContentScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
fun ContentScope.QuickSettingsLayout(
    viewModel: QuickSettingsContainerViewModel,
    modifier: Modifier = Modifier,
    header: @Composable () -> Unit,
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
@@ -217,6 +248,7 @@ fun ContentScope.QuickSettingsLayout(
                bottom = QuickSettingsShade.Dimensions.Padding,
            ),
    ) {
        header()
        Toolbar(
            modifier =
                Modifier.fillMaxWidth().requiredHeight(QuickSettingsShade.Dimensions.ToolbarHeight),
+24 −4
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
@@ -65,9 +66,11 @@ import com.android.systemui.res.R
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
fun ContentScope.OverlayShade(
    isShadeLayoutWide: Boolean,
    panelAlignment: Alignment,
    onScrimClicked: () -> Unit,
    modifier: Modifier = Modifier,
    header: @Composable () -> Unit,
    content: @Composable () -> Unit,
) {
    // TODO(b/384653288) This should be removed when b/378470603 is done.
@@ -90,13 +93,19 @@ fun ContentScope.OverlayShade(

        Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) {
            Panel(
                isShadeLayoutWide = isShadeLayoutWide,
                modifier =
                    Modifier.element(OverlayShade.Elements.Panel)
                        .overscroll(verticalOverscrollEffect)
                        .panelSize(),
                header = header,
                content = content,
            )
        }

        if (isShadeLayoutWide) {
            header()
        }
    }
}

@@ -113,7 +122,12 @@ private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modif
}

@Composable
private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
private fun ContentScope.Panel(
    isShadeLayoutWide: Boolean,
    modifier: Modifier = Modifier,
    header: @Composable () -> Unit,
    content: @Composable () -> Unit,
) {
    Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) {
        Spacer(
            modifier =
@@ -125,11 +139,17 @@ private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composab
                    )
        )

        // This content is intentionally rendered as a separate element from the background in order
        // to allow for more flexibility when defining transitions.
        Column {
            if (!isShadeLayoutWide) {
                header()
            }

            // This content is intentionally rendered as a separate element from the background in
            // order to allow for more flexibility when defining transitions.
            content()
        }
    }
}

@Composable
private fun Modifier.panelSize(): Modifier {
+372 −146

File changed.

Preview size limit exceeded, changes collapsed.

+8 −7
Original line number Diff line number Diff line
@@ -6,17 +6,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.shadeHeaderText
import com.android.compose.theme.colorAttr
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel

@Composable
fun VariableDayDate(
    viewModel: ShadeHeaderViewModel,
    modifier: Modifier = Modifier,
) {
fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
    val longerText = viewModel.longerDateText.collectAsStateWithLifecycle()
    val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle()

    val textColor =
        if (viewModel.highlightNotificationIcons) colorAttr(android.R.attr.textColorPrimaryInverse)
        else colorAttr(android.R.attr.textColorPrimary)

    Layout(
        contents =
            listOf(
@@ -24,7 +25,7 @@ fun VariableDayDate(
                    Text(
                        text = longerText.value,
                        style = MaterialTheme.typography.bodyMedium,
                        color = MaterialTheme.colorScheme.shadeHeaderText,
                        color = textColor,
                        maxLines = 1,
                    )
                },
@@ -32,7 +33,7 @@ fun VariableDayDate(
                    Text(
                        text = shorterText.value,
                        style = MaterialTheme.typography.bodyMedium,
                        color = MaterialTheme.colorScheme.shadeHeaderText,
                        color = textColor,
                        maxLines = 1,
                    )
                },
Loading