Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +136 −0 Original line number Diff line number Diff line Loading @@ -19,13 +19,19 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.animateScrollBy 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.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth Loading @@ -41,9 +47,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading @@ -52,6 +60,7 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow Loading @@ -59,10 +68,12 @@ 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.LocalContext 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.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import androidx.lifecycle.compose.collectAsStateWithLifecycle Loading @@ -71,6 +82,7 @@ import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.internal.policy.SystemBarUtils import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R Loading Loading @@ -137,6 +149,87 @@ fun SceneScope.HeadsUpNotificationSpace( ) } /** * A version of [HeadsUpNotificationSpace] that can be swiped up off the top edge of the screen by * the user. When swiped up, the heads up notification is snoozed. */ @Composable fun SceneScope.SnoozeableHeadsUpNotificationSpace( stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, ) { val context = LocalContext.current val density = LocalDensity.current val statusBarHeight = SystemBarUtils.getStatusBarHeight(context) val headsUpPadding = with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() } val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false) var scrollOffset by remember { mutableFloatStateOf(0f) } val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat()) val maxScrollOffset = 0f val scrollableState = rememberScrollableState { delta -> consumeDeltaWithinRange( current = scrollOffset, setCurrent = { scrollOffset = it }, min = minScrollOffset, max = maxScrollOffset, delta ) } val nestedScrollConnection = object : NestedScrollConnection { override suspend fun onPreFling(available: Velocity): Velocity { if ( velocityOrPositionalThresholdReached(scrollOffset, minScrollOffset, available.y) ) { scrollableState.animateScrollBy(minScrollOffset, tween()) } else { scrollableState.animateScrollBy(-minScrollOffset, tween()) } return available } } LaunchedEffect(isHeadsUp) { scrollOffset = 0f } LaunchedEffect(scrollableState.isScrollInProgress) { if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) { viewModel.snoozeHun() } } HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.absoluteOffset { IntOffset( x = 0, y = calculateHeadsUpPlaceholderYOffset( scrollOffset.roundToInt(), minScrollOffset.roundToInt(), stackScrollView.topHeadsUpHeight ) ) } .thenIf(isHeadsUp) { Modifier.verticalNestedScrollToScene( bottomBehavior = NestedScrollBehavior.EdgeAlways ) .nestedScroll(nestedScrollConnection) .scrollable( orientation = Orientation.Vertical, state = scrollableState, ) } ) } /** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.ConstrainedNotificationStack( Loading Loading @@ -480,6 +573,47 @@ private fun calculateCornerRadius( } } private fun calculateHeadsUpPlaceholderYOffset( scrollOffset: Int, minScrollOffset: Int, topHeadsUpHeight: Int, ): Int { return -minScrollOffset + (scrollOffset * (-minScrollOffset + topHeadsUpHeight) / -minScrollOffset) } private fun velocityOrPositionalThresholdReached( scrollOffset: Float, minScrollOffset: Float, availableVelocityY: Float, ): Boolean { return availableVelocityY < HUN_SNOOZE_VELOCITY_THRESHOLD || (availableVelocityY <= 0f && scrollOffset < minScrollOffset * HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION) } /** * Takes a range, current value, and delta, and updates the current value by the delta, coercing the * result within the given range. Returns how much of the delta was consumed. */ private fun consumeDeltaWithinRange( current: Float, setCurrent: (Float) -> Unit, min: Float, max: Float, delta: Float ): Float { return if (delta < 0 && current > min) { val remainder = (current + delta - min).coerceAtMost(0f) setCurrent((current + delta).coerceAtLeast(min)) delta - remainder } else if (delta > 0 && current < max) { val remainder = (current + delta).coerceAtLeast(0f) setCurrent((current + delta).coerceAtMost(max)) delta - remainder } else 0f } private inline fun debugLog( viewModel: NotificationsPlaceholderViewModel, msg: () -> Any, Loading Loading @@ -514,3 +648,5 @@ private const val TAG = "FlexiNotifs" 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) private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +2 −28 Original line number Diff line number Diff line Loading @@ -17,26 +17,19 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.internal.policy.SystemBarUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView Loading Loading @@ -72,28 +65,9 @@ constructor( ) animateSceneDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false) Spacer(modifier.fillMaxSize()) HeadsUpNotificationStack( SnoozeableHeadsUpNotificationSpace( stackScrollView = notificationStackScrolLView.get(), viewModel = notificationsPlaceholderViewModel ) } } @Composable private fun SceneScope.HeadsUpNotificationStack( stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, ) { val context = LocalContext.current val density = LocalDensity.current val statusBarHeight = SystemBarUtils.getStatusBarHeight(context) val headsUpPadding = with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() } HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) } ) } packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt +3 −0 Original line number Diff line number Diff line Loading @@ -41,4 +41,7 @@ interface HeadsUpRepository { val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> fun setHeadsUpAnimatingAway(animatingAway: Boolean) /** Snooze the currently pinned HUN. */ fun snooze() } packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +7 −0 Original line number Diff line number Diff line Loading @@ -101,10 +101,17 @@ constructor( fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor = HeadsUpRowInteractor(key as HeadsUpRowRepository) fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey fun setHeadsUpAnimatingAway(animatingAway: Boolean) { headsUpRepository.setHeadsUpAnimatingAway(animatingAway) } /** Snooze the currently pinned HUN. */ fun snooze() { headsUpRepository.snooze() } } class HeadsUpRowInteractor(repository: HeadsUpRowRepository) packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +7 −0 Original line number Diff line number Diff line Loading @@ -3499,6 +3499,13 @@ public class NotificationStackScrollLayout setIsBeingDragged(true); } // Only when scene container is enabled, mark that we are being dragged so that we start // dispatching the rest of the gesture to scene container. void startDraggingOnHun() { SceneContainerFlag.isUnexpectedlyInLegacyMode(); setIsBeingDragged(true); } @Override public boolean onGenericMotionEvent(MotionEvent event) { if (!isScrollingEnabled() Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +136 −0 Original line number Diff line number Diff line Loading @@ -19,13 +19,19 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.animateScrollBy 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.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth Loading @@ -41,9 +47,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading @@ -52,6 +60,7 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow Loading @@ -59,10 +68,12 @@ 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.LocalContext 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.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import androidx.lifecycle.compose.collectAsStateWithLifecycle Loading @@ -71,6 +82,7 @@ import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.internal.policy.SystemBarUtils import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R Loading Loading @@ -137,6 +149,87 @@ fun SceneScope.HeadsUpNotificationSpace( ) } /** * A version of [HeadsUpNotificationSpace] that can be swiped up off the top edge of the screen by * the user. When swiped up, the heads up notification is snoozed. */ @Composable fun SceneScope.SnoozeableHeadsUpNotificationSpace( stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, ) { val context = LocalContext.current val density = LocalDensity.current val statusBarHeight = SystemBarUtils.getStatusBarHeight(context) val headsUpPadding = with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() } val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false) var scrollOffset by remember { mutableFloatStateOf(0f) } val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat()) val maxScrollOffset = 0f val scrollableState = rememberScrollableState { delta -> consumeDeltaWithinRange( current = scrollOffset, setCurrent = { scrollOffset = it }, min = minScrollOffset, max = maxScrollOffset, delta ) } val nestedScrollConnection = object : NestedScrollConnection { override suspend fun onPreFling(available: Velocity): Velocity { if ( velocityOrPositionalThresholdReached(scrollOffset, minScrollOffset, available.y) ) { scrollableState.animateScrollBy(minScrollOffset, tween()) } else { scrollableState.animateScrollBy(-minScrollOffset, tween()) } return available } } LaunchedEffect(isHeadsUp) { scrollOffset = 0f } LaunchedEffect(scrollableState.isScrollInProgress) { if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) { viewModel.snoozeHun() } } HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.absoluteOffset { IntOffset( x = 0, y = calculateHeadsUpPlaceholderYOffset( scrollOffset.roundToInt(), minScrollOffset.roundToInt(), stackScrollView.topHeadsUpHeight ) ) } .thenIf(isHeadsUp) { Modifier.verticalNestedScrollToScene( bottomBehavior = NestedScrollBehavior.EdgeAlways ) .nestedScroll(nestedScrollConnection) .scrollable( orientation = Orientation.Vertical, state = scrollableState, ) } ) } /** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.ConstrainedNotificationStack( Loading Loading @@ -480,6 +573,47 @@ private fun calculateCornerRadius( } } private fun calculateHeadsUpPlaceholderYOffset( scrollOffset: Int, minScrollOffset: Int, topHeadsUpHeight: Int, ): Int { return -minScrollOffset + (scrollOffset * (-minScrollOffset + topHeadsUpHeight) / -minScrollOffset) } private fun velocityOrPositionalThresholdReached( scrollOffset: Float, minScrollOffset: Float, availableVelocityY: Float, ): Boolean { return availableVelocityY < HUN_SNOOZE_VELOCITY_THRESHOLD || (availableVelocityY <= 0f && scrollOffset < minScrollOffset * HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION) } /** * Takes a range, current value, and delta, and updates the current value by the delta, coercing the * result within the given range. Returns how much of the delta was consumed. */ private fun consumeDeltaWithinRange( current: Float, setCurrent: (Float) -> Unit, min: Float, max: Float, delta: Float ): Float { return if (delta < 0 && current > min) { val remainder = (current + delta - min).coerceAtMost(0f) setCurrent((current + delta).coerceAtLeast(min)) delta - remainder } else if (delta > 0 && current < max) { val remainder = (current + delta).coerceAtLeast(0f) setCurrent((current + delta).coerceAtMost(max)) delta - remainder } else 0f } private inline fun debugLog( viewModel: NotificationsPlaceholderViewModel, msg: () -> Any, Loading Loading @@ -514,3 +648,5 @@ private const val TAG = "FlexiNotifs" 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) private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +2 −28 Original line number Diff line number Diff line Loading @@ -17,26 +17,19 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.internal.policy.SystemBarUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView Loading Loading @@ -72,28 +65,9 @@ constructor( ) animateSceneDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false) Spacer(modifier.fillMaxSize()) HeadsUpNotificationStack( SnoozeableHeadsUpNotificationSpace( stackScrollView = notificationStackScrolLView.get(), viewModel = notificationsPlaceholderViewModel ) } } @Composable private fun SceneScope.HeadsUpNotificationStack( stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, ) { val context = LocalContext.current val density = LocalDensity.current val statusBarHeight = SystemBarUtils.getStatusBarHeight(context) val headsUpPadding = with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() } HeadsUpNotificationSpace( stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) } ) }
packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt +3 −0 Original line number Diff line number Diff line Loading @@ -41,4 +41,7 @@ interface HeadsUpRepository { val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> fun setHeadsUpAnimatingAway(animatingAway: Boolean) /** Snooze the currently pinned HUN. */ fun snooze() }
packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +7 −0 Original line number Diff line number Diff line Loading @@ -101,10 +101,17 @@ constructor( fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor = HeadsUpRowInteractor(key as HeadsUpRowRepository) fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey fun setHeadsUpAnimatingAway(animatingAway: Boolean) { headsUpRepository.setHeadsUpAnimatingAway(animatingAway) } /** Snooze the currently pinned HUN. */ fun snooze() { headsUpRepository.snooze() } } class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +7 −0 Original line number Diff line number Diff line Loading @@ -3499,6 +3499,13 @@ public class NotificationStackScrollLayout setIsBeingDragged(true); } // Only when scene container is enabled, mark that we are being dragged so that we start // dispatching the rest of the gesture to scene container. void startDraggingOnHun() { SceneContainerFlag.isUnexpectedlyInLegacyMode(); setIsBeingDragged(true); } @Override public boolean onGenericMotionEvent(MotionEvent event) { if (!isScrollingEnabled() Loading