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

Commit 8b86c4f5 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge changes I564a68ad,Ib281ca28,Ia9b6396d,I88502012,Ic5ab3f38, ... into main

* changes:
  [flexiglass] Dump the flows on the NotificationsPlaceholderViewModel
  [flexiglass] Rename NotificationStackView to NotificationScrollView
  [flexiglass] Use null bounds to disable the clipping in the NSSL.
  [flexiglass] Start adding the HeadsUpNotificationSpace to other Composables
  [flexiglass] Notification composables now set the particular placeholder values from the right place.
  [flexiglass] Add NotificationStackView (NSV) interface
parents 0d98199a f6639398
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.notifications.ui.composable.NotificationStack
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -94,7 +94,7 @@ constructor(
            return
        }

        NotificationStack(
        ConstrainedNotificationStack(
            viewModel = viewModel,
            modifier =
                modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
+76 −45
Original line number Diff line number Diff line
@@ -36,12 +36,14 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
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.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
@@ -60,7 +62,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.ElementKey
@@ -68,12 +69,12 @@ import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -81,7 +82,8 @@ import kotlin.math.roundToInt
object Notifications {
    object Elements {
        val NotificationScrim = ElementKey("NotificationScrim")
        val NotificationPlaceholder = ElementKey("NotificationPlaceholder")
        val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
        val HeadsUpNotificationPlaceholder = ElementKey("HeadsUpNotificationPlaceholder")
        val ShelfSpace = ElementKey("ShelfSpace")
    }

@@ -91,12 +93,6 @@ object Notifications {
        const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
        const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
    }

    enum class Form {
        HunFromTop,
        Stack,
        HunFromBottom,
    }
}

/**
@@ -109,24 +105,48 @@ fun SceneScope.HeadsUpNotificationSpace(
    modifier: Modifier = Modifier,
    isPeekFromBottom: Boolean = false,
) {
    NotificationPlaceholder(
        viewModel = viewModel,
        form = if (isPeekFromBottom) Form.HunFromBottom else Form.HunFromTop,
        modifier = modifier,
    )
    val headsUpHeight = viewModel.headsUpHeight.collectAsState()

    Element(
        Notifications.Elements.HeadsUpNotificationPlaceholder,
        modifier =
            modifier
                .height { headsUpHeight.value.roundToInt() }
                .fillMaxWidth()
                .debugBackground(viewModel, DEBUG_HUN_COLOR)
                .onGloballyPositioned { coordinates: LayoutCoordinates ->
                    val boundsInWindow = coordinates.boundsInWindow()
                    debugLog(viewModel) {
                        "HUNS onGloballyPositioned:" +
                            " size=${coordinates.size}" +
                            " bounds=$boundsInWindow"
                    }
                    viewModel.onHeadsUpTopChanged(boundsInWindow.top)
                }
    ) {
        content {}
    }
}

/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.NotificationStack(
fun SceneScope.ConstrainedNotificationStack(
    viewModel: NotificationsPlaceholderViewModel,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier =
            modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) }
    ) {
        NotificationPlaceholder(
            viewModel = viewModel,
        form = Form.Stack,
        modifier = modifier,
            modifier = Modifier.fillMaxSize(),
        )
        HeadsUpNotificationSpace(
            viewModel = viewModel,
            modifier = Modifier.align(Alignment.TopCenter),
        )
    }
}

/**
@@ -169,6 +189,9 @@ fun SceneScope.NotificationScrollingStack(
    // entire height of the scrim is visible on screen.
    val scrimOffset = remember { mutableStateOf(0f) }

    // set the bounds to null when the scrim disappears
    DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } }

    val minScrimTop = with(density) { ShadeHeader.Dimensions.CollapsedHeight.toPx() }

    // The minimum offset for the scrim. The scrim is considered fully expanded when it
@@ -235,6 +258,22 @@ fun SceneScope.NotificationScrollingStack(
                            .let { scrimRounding.value.toRoundedCornerShape(it) }
                    clip = true
                }
                .onGloballyPositioned { coordinates ->
                    val boundsInWindow = coordinates.boundsInWindow()
                    debugLog(viewModel) {
                        "SCRIM onGloballyPositioned:" +
                            " size=${coordinates.size}" +
                            " bounds=$boundsInWindow"
                    }
                    viewModel.onScrimBoundsChanged(
                        ShadeScrimBounds(
                            left = boundsInWindow.left,
                            top = boundsInWindow.top,
                            right = boundsInWindow.right,
                            bottom = boundsInWindow.bottom,
                        )
                    )
                }
    ) {
        // Creates a cutout in the background scrim in the shape of the notifications scrim.
        // Only visible when notif scrim alpha < 1, during shade expansion.
@@ -254,11 +293,10 @@ fun SceneScope.NotificationScrollingStack(
                            } else 1f
                    }
                    .background(MaterialTheme.colorScheme.surface)
                    .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
                    .debugBackground(viewModel, DEBUG_BOX_COLOR)
        ) {
            NotificationPlaceholder(
                viewModel = viewModel,
                form = Form.Stack,
                modifier =
                    Modifier.verticalNestedScrollToScene(
                            topBehavior = NestedScrollBehavior.EdgeWithPreview,
@@ -284,6 +322,7 @@ fun SceneScope.NotificationScrollingStack(
                        .height { (stackHeight.value + navBarHeight).roundToInt() },
            )
        }
        HeadsUpNotificationSpace(viewModel = viewModel)
    }
}

@@ -304,14 +343,10 @@ fun SceneScope.NotificationShelfSpace(
        modifier
            .element(key = Notifications.Elements.ShelfSpace)
            .fillMaxWidth()
            .onSizeChanged { size: IntSize ->
                debugLog(viewModel) { "SHELF onSizeChanged: size=$size" }
            }
            .onPlaced { coordinates: LayoutCoordinates ->
                debugLog(viewModel) {
                    ("SHELF onPlaced:" +
                        " size=${coordinates.size}" +
                        " position=${coordinates.positionInWindow()}" +
                        " bounds=${coordinates.boundsInWindow()}")
                }
            }
@@ -326,32 +361,26 @@ fun SceneScope.NotificationShelfSpace(
@Composable
private fun SceneScope.NotificationPlaceholder(
    viewModel: NotificationsPlaceholderViewModel,
    form: Form,
    modifier: Modifier = Modifier,
) {
    val elementKey = Notifications.Elements.NotificationPlaceholder
    Element(
        elementKey,
        Notifications.Elements.NotificationStackPlaceholder,
        modifier =
            modifier
                .debugBackground(viewModel)
                .onSizeChanged { size: IntSize ->
                    debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
                }
                .debugBackground(viewModel, DEBUG_STACK_COLOR)
                .onSizeChanged { size -> debugLog(viewModel) { "STACK onSizeChanged: size=$size" } }
                .onGloballyPositioned { coordinates: LayoutCoordinates ->
                    viewModel.onContentTopChanged(coordinates.positionInWindow().y)
                    val positionInWindow = coordinates.positionInWindow()
                    debugLog(viewModel) {
                        "STACK onGloballyPositioned:" +
                            " size=${coordinates.size}" +
                            " position=${coordinates.positionInWindow()}" +
                            " position=$positionInWindow" +
                            " bounds=${coordinates.boundsInWindow()}"
                    }
                    val boundsInWindow = coordinates.boundsInWindow()
                    viewModel.onBoundsChanged(
                        left = boundsInWindow.left,
                        top = boundsInWindow.top,
                        right = boundsInWindow.right,
                        bottom = boundsInWindow.bottom,
                    // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not
                    viewModel.onStackBoundsChanged(
                        top = positionInWindow.y,
                        bottom = positionInWindow.y + coordinates.size.height,
                    )
                }
    ) {
@@ -388,7 +417,7 @@ private inline fun debugLog(

private fun Modifier.debugBackground(
    viewModel: NotificationsPlaceholderViewModel,
    color: Color = DEBUG_COLOR,
    color: Color,
): Modifier =
    if (viewModel.isVisualDebuggingEnabled) {
        background(color)
@@ -397,8 +426,8 @@ private fun Modifier.debugBackground(
    }

fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
    val topRadius = if (roundTop) radius else 0.dp
    val bottomRadius = if (roundBottom) radius else 0.dp
    val topRadius = if (isTopRounded) radius else 0.dp
    val bottomRadius = if (isBottomRounded) radius else 0.dp
    return RoundedCornerShape(
        topStart = topRadius,
        topEnd = topRadius,
@@ -408,4 +437,6 @@ fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
}

private const val TAG = "FlexiNotifs"
private val DEBUG_COLOR = Color(1f, 0f, 0f, 0.2f)
private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
+34 −15
Original line number Diff line number Diff line
@@ -31,7 +31,9 @@ import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -57,27 +59,44 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
        }
    private val testScope = kosmos.testScope
    private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel }
    private val appearanceViewModel by lazy { kosmos.notificationStackAppearanceViewModel }
    private val appearanceViewModel by lazy { kosmos.notificationScrollViewModel }
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }

    @Test
    fun updateBounds() =
        testScope.runTest {
            val clipping by collectLastValue(appearanceViewModel.shadeScrimClipping)

            val top = 200f
            val left = 0f
            val bottom = 550f
            val right = 100f
            placeholderViewModel.onBoundsChanged(
                left = left,
                top = top,
                right = right,
                bottom = bottom
            val radius = MutableStateFlow(32)
            val viewPosition = MutableStateFlow(ViewPosition(0, 0))
            val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, viewPosition))

            placeholderViewModel.onScrimBoundsChanged(
                ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
            )
            assertThat(shape)
                .isEqualTo(
                    ShadeScrimShape(
                        bounds =
                            ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
                        topRadius = 32,
                        bottomRadius = 0
                    )
                )

            viewPosition.value = ViewPosition(200, 15)
            radius.value = 24
            placeholderViewModel.onScrimBoundsChanged(
                ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
            )
            assertThat(shape)
                .isEqualTo(
                    ShadeScrimShape(
                        bounds =
                            ShadeScrimBounds(left = 10f, top = 185f, right = 100f, bottom = 535f),
                        topRadius = 24,
                        bottomRadius = 0
                    )
                )
            assertThat(clipping?.bounds)
                .isEqualTo(ShadeScrimBounds(left = left, top = top, right = right, bottom = bottom))
        }

    @Test
+2 −2
Original line number Diff line number Diff line
@@ -68,11 +68,11 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {

            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
            assertThat(stackRounding)
                .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = false))
                .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false))

            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            assertThat(stackRounding)
                .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = true))
                .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true))
        }

    @Test(expected = IllegalStateException::class)
+11 −17
Original line number Diff line number Diff line
@@ -16,14 +16,10 @@

package com.android.systemui.statusbar.notification.stack.ui.viewmodel

import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -40,23 +36,21 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
    private val underTest = kosmos.notificationsPlaceholderViewModel

    @Test
    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
    fun onBoundsChanged_setsNotificationContainerBounds() =
    fun onBoundsChanged() =
        kosmos.testScope.runTest {
            underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
            val containerBounds by
                collectLastValue(kosmos.keyguardInteractor.notificationContainerBounds)
            val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f)
            underTest.onScrimBoundsChanged(bounds)
            val stackBounds by
                collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
            assertThat(containerBounds)
                .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
            assertThat(stackBounds)
                .isEqualTo(ShadeScrimBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
            assertThat(stackBounds).isEqualTo(bounds)
        }

    @Test
    fun onContentTopChanged_setsContentTop() {
        underTest.onContentTopChanged(padding = 5f)
    fun onStackBoundsChanged() =
        kosmos.testScope.runTest {
            underTest.onStackBoundsChanged(top = 5f, bottom = 500f)
            assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f)
            assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value)
                .isEqualTo(500f)
        }
}
Loading