Loading packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt +0 −21 Original line number Diff line number Diff line Loading @@ -16,15 +16,10 @@ package com.android.systemui.common.ui.compose.windowinsets import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle Loading @@ -36,9 +31,6 @@ val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() } /** The corner radius in px of the current display. */ val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp } /** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */ val LocalRawScreenHeight = staticCompositionLocalOf { 0f } @Composable fun ScreenDecorProvider( displayCutout: StateFlow<DisplayCutout>, Loading @@ -48,22 +40,9 @@ fun ScreenDecorProvider( val cutout by displayCutout.collectAsStateWithLifecycle() val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() } val density = LocalDensity.current val navBarHeight = with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding() val screenHeight = with(density) { (LocalConfiguration.current.screenHeightDp.dp + maxOf(statusBarHeight, displayCutoutHeight)) .toPx() } + navBarHeight CompositionLocalProvider( LocalScreenCornerRadius provides screenCornerRadiusDp, LocalDisplayCutout provides cutout, LocalRawScreenHeight provides screenHeight, ) { content() } Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +8 −5 Original line number Diff line number Diff line Loading @@ -24,9 +24,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityNestedScrollConnection import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import kotlin.math.max import kotlin.math.roundToInt import kotlin.math.tanh Loading @@ -36,9 +38,10 @@ import kotlinx.coroutines.launch @Composable fun Modifier.stackVerticalOverscroll( coroutineScope: CoroutineScope, canScrollForward: () -> Boolean canScrollForward: () -> Boolean, ): Modifier { val screenHeight = LocalRawScreenHeight.current val screenHeight = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } val overscrollOffset = remember { Animatable(0f) } val stackNestedScrollConnection = remember { NotificationStackNestedScrollConnection( Loading @@ -60,10 +63,10 @@ fun Modifier.stackVerticalOverscroll( overscrollOffset.animateTo( targetValue = 0f, initialVelocity = velocityAvailable, animationSpec = tween() animationSpec = tween(), ) } } }, ) } Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +112 −37 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background Loading @@ -29,6 +30,8 @@ import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollBy 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.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.absoluteOffset Loading @@ -36,9 +39,11 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme Loading Loading @@ -68,6 +73,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp Loading @@ -81,7 +87,6 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession Loading @@ -96,6 +101,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch object Notifications { Loading Loading @@ -171,7 +177,7 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( setCurrent = { scrollOffset = it }, min = minScrollOffset, max = maxScrollOffset, delta delta, ) } Loading Loading @@ -209,8 +215,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( calculateHeadsUpPlaceholderYOffset( scrollOffset.roundToInt(), minScrollOffset.roundToInt(), stackScrollView.topHeadsUpHeight ) stackScrollView.topHeadsUpHeight, ), ) } .thenIf(isHeadsUp) { Loading @@ -218,11 +224,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( bottomBehavior = NestedScrollBehavior.EdgeAlways ) .nestedScroll(nestedScrollConnection) .scrollable( orientation = Orientation.Vertical, state = scrollableState, ) } .scrollable(orientation = Orientation.Vertical, state = scrollableState) }, ) } Loading Loading @@ -259,6 +262,7 @@ fun SceneScope.ConstrainedNotificationStack( * Adds the space where notification stack should appear in the scene, with a scrim and nested * scrolling. */ @OptIn(ExperimentalLayoutApi::class) @Composable fun SceneScope.NotificationScrollingStack( shadeSession: SaveableSession, Loading Loading @@ -291,7 +295,7 @@ fun SceneScope.NotificationScrollingStack( val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp val screenHeight = LocalRawScreenHeight.current val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } /** * The height in px of the contents of notification stack. Depending on the number of Loading Loading @@ -325,6 +329,14 @@ fun SceneScope.NotificationScrollingStack( screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() } } val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false) // The bottom Y bound of the currently focused remote input notification. val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f) // The top y bound of the IME. val imeTop = remember { mutableFloatStateOf(0f) } // we are not scrolled to the top unless the scrim is at its maximum offset. LaunchedEffect(viewModel, scrimOffset) { snapshotFlow { scrimOffset.value >= 0f } Loading @@ -342,15 +354,34 @@ fun SceneScope.NotificationScrollingStack( LaunchedEffect(syntheticScroll, scrimOffset, scrollState) { snapshotFlow { syntheticScroll.value } .collect { delta -> val minOffset = minScrimOffset() if (scrimOffset.value > minOffset) { val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f) scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset)) if (remainingDelta > 0f) { scrollState.scrollBy(remainingDelta) scrollNotificationStack( scope = coroutineScope, delta = delta, animate = false, scrimOffset = scrimOffset, minScrimOffset = minScrimOffset, scrollState = scrollState, ) } } else { scrollState.scrollTo(delta.roundToInt()) } // if remote input state changes, compare the row and IME's overlap and offset the scrim and // placeholder accordingly. LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) { imeTop.floatValue = 0f snapshotFlow { imeTop.floatValue } .collect { imeTopValue -> // only scroll the stack if ime value has been populated (ime placeholder has been // composed at least once), and our remote input row overlaps with the ime bounds. if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) { scrollNotificationStack( scope = coroutineScope, delta = remoteInputRowBottom - imeTopValue, animate = true, scrimOffset = scrimOffset, minScrimOffset = minScrimOffset, scrollState = scrollState, ) } } } Loading Loading @@ -394,12 +425,12 @@ fun SceneScope.NotificationScrollingStack( scrimOffset.value < 0 && layoutState.isTransitioning( from = Scenes.Shade, to = Scenes.QuickSettings to = Scenes.QuickSettings, ) ) { IntOffset( x = 0, y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt() y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(), ) } else { IntOffset(x = 0, y = scrimOffset.value.roundToInt()) Loading Loading @@ -458,13 +489,11 @@ fun SceneScope.NotificationScrollingStack( .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() } .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { NotificationPlaceholder( stackScrollView = stackScrollView, viewModel = viewModel, Column( modifier = Modifier.verticalNestedScrollToScene( topBehavior = NestedScrollBehavior.EdgeWithPreview, isExternalOverscrollGesture = { isCurrentGestureOverscroll.value } isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }, ) .thenIf(shadeMode == ShadeMode.Single) { Modifier.nestedScroll(scrimNestedScrollConnection) Loading @@ -473,18 +502,31 @@ fun SceneScope.NotificationScrollingStack( .verticalScroll(scrollState) .padding(top = topPadding) .fillMaxWidth() .notificationStackHeight( ) { NotificationPlaceholder( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.notificationStackHeight( view = stackScrollView, totalVerticalPadding = topPadding + bottomPadding, ) .onSizeChanged { size -> stackHeight.intValue = size.height }, ) Spacer( modifier = Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget) .onGloballyPositioned { coordinates: LayoutCoordinates -> imeTop.floatValue = screenHeight - coordinates.size.height } ) } } if (shouldIncludeHeadsUpSpace) { HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.padding(top = topPadding) modifier = Modifier.padding(top = topPadding), ) } } Loading Loading @@ -572,6 +614,42 @@ private fun SceneScope.NotificationPlaceholder( ) } private suspend fun scrollNotificationStack( scope: CoroutineScope, delta: Float, animate: Boolean, scrimOffset: Animatable<Float, AnimationVector1D>, minScrimOffset: () -> Float, scrollState: ScrollState, ) { val minOffset = minScrimOffset() if (scrimOffset.value > minOffset) { val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt() if (remainingDelta > 0) { if (animate) { // launch a new coroutine for the remainder animation so that it doesn't suspend the // scrim animation, allowing both to play simultaneously. scope.launch { scrollState.animateScrollTo(remainingDelta) } } else { scrollState.scrollTo(remainingDelta) } } val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset) if (animate) { scrimOffset.animateTo(newScrimOffset) } else { scrimOffset.snapTo(newScrimOffset) } } else { if (animate) { scrollState.animateScrollBy(delta) } else { scrollState.scrollBy(delta) } } } private fun calculateCornerRadius( scrimCornerRadius: Dp, screenCornerRadius: Dp, Loading Loading @@ -618,7 +696,7 @@ private fun consumeDeltaWithinRange( setCurrent: (Float) -> Unit, min: Float, max: Float, delta: Float delta: Float, ): Float { return if (delta < 0 && current > min) { val remainder = (current + delta - min).coerceAtMost(0f) Loading @@ -631,10 +709,7 @@ private fun consumeDeltaWithinRange( } else 0f } private inline fun debugLog( viewModel: NotificationsPlaceholderViewModel, msg: () -> Any, ) { private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) { if (viewModel.isDebugLoggingEnabled) { Log.d(TAG, msg().toString()) } Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +3 −5 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource Loading @@ -79,7 +80,6 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.ExclusiveActivatable Loading Loading @@ -229,17 +229,16 @@ private fun SceneScope.QuickSettingsScene( } .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() } ) { val density = LocalDensity.current val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val screenHeight = LocalRawScreenHeight.current val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() } val collapsedHeaderHeight = with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } val lifecycleOwner = LocalLifecycleOwner.current val footerActionsViewModel = remember(lifecycleOwner, viewModel) { Loading Loading @@ -268,7 +267,6 @@ private fun SceneScope.QuickSettingsScene( val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() val density = LocalDensity.current val bottomPadding by animateDpAsState( targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, Loading packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt +17 −3 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow /** * Repository used for tracking the state of notification remote input (e.g. when the user presses Loading @@ -33,14 +34,21 @@ import kotlinx.coroutines.flow.Flow interface RemoteInputRepository { /** Whether remote input is currently active for any notification. */ val isRemoteInputActive: Flow<Boolean> /** * The bottom bound of the currently focused remote input notification row, or null if there * isn't one. */ val remoteInputRowBottomBound: Flow<Float?> fun setRemoteInputRowBottomBound(bottom: Float?) } @SysUISingleton class RemoteInputRepositoryImpl @Inject constructor( private val notificationRemoteInputManager: NotificationRemoteInputManager, ) : RemoteInputRepository { constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) : RemoteInputRepository { override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow { trySend(false) // initial value is false val callback = Loading @@ -52,6 +60,12 @@ constructor( notificationRemoteInputManager.addControllerCallback(callback) awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) } } override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null) override fun setRemoteInputRowBottomBound(bottom: Float?) { remoteInputRowBottomBound.value = bottom } } @Module Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt +0 −21 Original line number Diff line number Diff line Loading @@ -16,15 +16,10 @@ package com.android.systemui.common.ui.compose.windowinsets import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle Loading @@ -36,9 +31,6 @@ val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() } /** The corner radius in px of the current display. */ val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp } /** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */ val LocalRawScreenHeight = staticCompositionLocalOf { 0f } @Composable fun ScreenDecorProvider( displayCutout: StateFlow<DisplayCutout>, Loading @@ -48,22 +40,9 @@ fun ScreenDecorProvider( val cutout by displayCutout.collectAsStateWithLifecycle() val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() } val density = LocalDensity.current val navBarHeight = with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding() val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding() val screenHeight = with(density) { (LocalConfiguration.current.screenHeightDp.dp + maxOf(statusBarHeight, displayCutoutHeight)) .toPx() } + navBarHeight CompositionLocalProvider( LocalScreenCornerRadius provides screenCornerRadiusDp, LocalDisplayCutout provides cutout, LocalRawScreenHeight provides screenHeight, ) { content() } Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +8 −5 Original line number Diff line number Diff line Loading @@ -24,9 +24,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityNestedScrollConnection import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import kotlin.math.max import kotlin.math.roundToInt import kotlin.math.tanh Loading @@ -36,9 +38,10 @@ import kotlinx.coroutines.launch @Composable fun Modifier.stackVerticalOverscroll( coroutineScope: CoroutineScope, canScrollForward: () -> Boolean canScrollForward: () -> Boolean, ): Modifier { val screenHeight = LocalRawScreenHeight.current val screenHeight = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } val overscrollOffset = remember { Animatable(0f) } val stackNestedScrollConnection = remember { NotificationStackNestedScrollConnection( Loading @@ -60,10 +63,10 @@ fun Modifier.stackVerticalOverscroll( overscrollOffset.animateTo( targetValue = 0f, initialVelocity = velocityAvailable, animationSpec = tween() animationSpec = tween(), ) } } }, ) } Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +112 −37 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background Loading @@ -29,6 +30,8 @@ import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollBy 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.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.absoluteOffset Loading @@ -36,9 +39,11 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme Loading Loading @@ -68,6 +73,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp Loading @@ -81,7 +87,6 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession Loading @@ -96,6 +101,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch object Notifications { Loading Loading @@ -171,7 +177,7 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( setCurrent = { scrollOffset = it }, min = minScrollOffset, max = maxScrollOffset, delta delta, ) } Loading Loading @@ -209,8 +215,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( calculateHeadsUpPlaceholderYOffset( scrollOffset.roundToInt(), minScrollOffset.roundToInt(), stackScrollView.topHeadsUpHeight ) stackScrollView.topHeadsUpHeight, ), ) } .thenIf(isHeadsUp) { Loading @@ -218,11 +224,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace( bottomBehavior = NestedScrollBehavior.EdgeAlways ) .nestedScroll(nestedScrollConnection) .scrollable( orientation = Orientation.Vertical, state = scrollableState, ) } .scrollable(orientation = Orientation.Vertical, state = scrollableState) }, ) } Loading Loading @@ -259,6 +262,7 @@ fun SceneScope.ConstrainedNotificationStack( * Adds the space where notification stack should appear in the scene, with a scrim and nested * scrolling. */ @OptIn(ExperimentalLayoutApi::class) @Composable fun SceneScope.NotificationScrollingStack( shadeSession: SaveableSession, Loading Loading @@ -291,7 +295,7 @@ fun SceneScope.NotificationScrollingStack( val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp val screenHeight = LocalRawScreenHeight.current val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } /** * The height in px of the contents of notification stack. Depending on the number of Loading Loading @@ -325,6 +329,14 @@ fun SceneScope.NotificationScrollingStack( screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() } } val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false) // The bottom Y bound of the currently focused remote input notification. val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f) // The top y bound of the IME. val imeTop = remember { mutableFloatStateOf(0f) } // we are not scrolled to the top unless the scrim is at its maximum offset. LaunchedEffect(viewModel, scrimOffset) { snapshotFlow { scrimOffset.value >= 0f } Loading @@ -342,15 +354,34 @@ fun SceneScope.NotificationScrollingStack( LaunchedEffect(syntheticScroll, scrimOffset, scrollState) { snapshotFlow { syntheticScroll.value } .collect { delta -> val minOffset = minScrimOffset() if (scrimOffset.value > minOffset) { val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f) scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset)) if (remainingDelta > 0f) { scrollState.scrollBy(remainingDelta) scrollNotificationStack( scope = coroutineScope, delta = delta, animate = false, scrimOffset = scrimOffset, minScrimOffset = minScrimOffset, scrollState = scrollState, ) } } else { scrollState.scrollTo(delta.roundToInt()) } // if remote input state changes, compare the row and IME's overlap and offset the scrim and // placeholder accordingly. LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) { imeTop.floatValue = 0f snapshotFlow { imeTop.floatValue } .collect { imeTopValue -> // only scroll the stack if ime value has been populated (ime placeholder has been // composed at least once), and our remote input row overlaps with the ime bounds. if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) { scrollNotificationStack( scope = coroutineScope, delta = remoteInputRowBottom - imeTopValue, animate = true, scrimOffset = scrimOffset, minScrimOffset = minScrimOffset, scrollState = scrollState, ) } } } Loading Loading @@ -394,12 +425,12 @@ fun SceneScope.NotificationScrollingStack( scrimOffset.value < 0 && layoutState.isTransitioning( from = Scenes.Shade, to = Scenes.QuickSettings to = Scenes.QuickSettings, ) ) { IntOffset( x = 0, y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt() y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(), ) } else { IntOffset(x = 0, y = scrimOffset.value.roundToInt()) Loading Loading @@ -458,13 +489,11 @@ fun SceneScope.NotificationScrollingStack( .thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() } .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { NotificationPlaceholder( stackScrollView = stackScrollView, viewModel = viewModel, Column( modifier = Modifier.verticalNestedScrollToScene( topBehavior = NestedScrollBehavior.EdgeWithPreview, isExternalOverscrollGesture = { isCurrentGestureOverscroll.value } isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }, ) .thenIf(shadeMode == ShadeMode.Single) { Modifier.nestedScroll(scrimNestedScrollConnection) Loading @@ -473,18 +502,31 @@ fun SceneScope.NotificationScrollingStack( .verticalScroll(scrollState) .padding(top = topPadding) .fillMaxWidth() .notificationStackHeight( ) { NotificationPlaceholder( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.notificationStackHeight( view = stackScrollView, totalVerticalPadding = topPadding + bottomPadding, ) .onSizeChanged { size -> stackHeight.intValue = size.height }, ) Spacer( modifier = Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget) .onGloballyPositioned { coordinates: LayoutCoordinates -> imeTop.floatValue = screenHeight - coordinates.size.height } ) } } if (shouldIncludeHeadsUpSpace) { HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.padding(top = topPadding) modifier = Modifier.padding(top = topPadding), ) } } Loading Loading @@ -572,6 +614,42 @@ private fun SceneScope.NotificationPlaceholder( ) } private suspend fun scrollNotificationStack( scope: CoroutineScope, delta: Float, animate: Boolean, scrimOffset: Animatable<Float, AnimationVector1D>, minScrimOffset: () -> Float, scrollState: ScrollState, ) { val minOffset = minScrimOffset() if (scrimOffset.value > minOffset) { val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt() if (remainingDelta > 0) { if (animate) { // launch a new coroutine for the remainder animation so that it doesn't suspend the // scrim animation, allowing both to play simultaneously. scope.launch { scrollState.animateScrollTo(remainingDelta) } } else { scrollState.scrollTo(remainingDelta) } } val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset) if (animate) { scrimOffset.animateTo(newScrimOffset) } else { scrimOffset.snapTo(newScrimOffset) } } else { if (animate) { scrollState.animateScrollBy(delta) } else { scrollState.scrollBy(delta) } } } private fun calculateCornerRadius( scrimCornerRadius: Dp, screenCornerRadius: Dp, Loading Loading @@ -618,7 +696,7 @@ private fun consumeDeltaWithinRange( setCurrent: (Float) -> Unit, min: Float, max: Float, delta: Float delta: Float, ): Float { return if (delta < 0 && current > min) { val remainder = (current + delta - min).coerceAtMost(0f) Loading @@ -631,10 +709,7 @@ private fun consumeDeltaWithinRange( } else 0f } private inline fun debugLog( viewModel: NotificationsPlaceholderViewModel, msg: () -> Any, ) { private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) { if (viewModel.isDebugLoggingEnabled) { Log.d(TAG, msg().toString()) } Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +3 −5 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource Loading @@ -79,7 +80,6 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.ExclusiveActivatable Loading Loading @@ -229,17 +229,16 @@ private fun SceneScope.QuickSettingsScene( } .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() } ) { val density = LocalDensity.current val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val screenHeight = LocalRawScreenHeight.current val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() } val collapsedHeaderHeight = with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } val lifecycleOwner = LocalLifecycleOwner.current val footerActionsViewModel = remember(lifecycleOwner, viewModel) { Loading Loading @@ -268,7 +267,6 @@ private fun SceneScope.QuickSettingsScene( val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() val density = LocalDensity.current val bottomPadding by animateDpAsState( targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, Loading
packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt +17 −3 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow /** * Repository used for tracking the state of notification remote input (e.g. when the user presses Loading @@ -33,14 +34,21 @@ import kotlinx.coroutines.flow.Flow interface RemoteInputRepository { /** Whether remote input is currently active for any notification. */ val isRemoteInputActive: Flow<Boolean> /** * The bottom bound of the currently focused remote input notification row, or null if there * isn't one. */ val remoteInputRowBottomBound: Flow<Float?> fun setRemoteInputRowBottomBound(bottom: Float?) } @SysUISingleton class RemoteInputRepositoryImpl @Inject constructor( private val notificationRemoteInputManager: NotificationRemoteInputManager, ) : RemoteInputRepository { constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) : RemoteInputRepository { override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow { trySend(false) // initial value is false val callback = Loading @@ -52,6 +60,12 @@ constructor( notificationRemoteInputManager.addControllerCallback(callback) awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) } } override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null) override fun setRemoteInputRowBottomBound(bottom: Float?) { remoteInputRowBottomBound.value = bottom } } @Module Loading