diff options
| author | 2023-09-19 21:08:13 +0000 | |
|---|---|---|
| committer | 2023-11-16 17:39:11 +0000 | |
| commit | cdd70211b3594163bf88b21dd62272ea5370f79d (patch) | |
| tree | 3976639900848bea93b47bbc61f7d750a62be480 | |
| parent | 45d0bd1e7af185452b49ad81d45db45756f3edcc (diff) | |
Start FlexiNotifs
* Adds a basic composable that draws the notification placeholder
* Moves the SharedNotificationContainer to be a sibling of the SceneContainer (under SceneWindowRootView)
* Demonstrates the composable setting data on a view model
* Demonstrates the other view model setting data on the NSSLController + AmbientState
Bug: 296118689
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Test: flexiglass on + enable flexi_notifs
Change-Id: If04403534a481b0420596aa6e38d8a9c10f2fc26
35 files changed, 690 insertions, 114 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index ee310ab41373..cc95a4b72731 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible import com.android.compose.animation.scene.SceneScope @@ -41,6 +42,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.qualifiers.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel +import com.android.systemui.notifications.ui.composable.NotificationStack import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.Edge @@ -88,7 +90,7 @@ constructor( ) { LockscreenScene( viewProvider = viewProvider, - longPressViewModel = viewModel.longPress, + viewModel = viewModel, modifier = modifier, ) } @@ -108,9 +110,9 @@ constructor( } @Composable -private fun LockscreenScene( +private fun SceneScope.LockscreenScene( viewProvider: () -> View, - longPressViewModel: KeyguardLongPressViewModel, + viewModel: LockscreenSceneViewModel, modifier: Modifier = Modifier, ) { fun findSettingsMenu(): View { @@ -121,7 +123,7 @@ private fun LockscreenScene( modifier = modifier, ) { LongPressSurface( - viewModel = longPressViewModel, + viewModel = viewModel.longPress, isSettingsMenuVisible = { findSettingsMenu().isVisible }, settingsMenuBounds = { val bounds = android.graphics.Rect() @@ -141,6 +143,28 @@ private fun LockscreenScene( }, modifier = Modifier.fillMaxSize(), ) + + val notificationStackPosition by + viewModel.keyguardRoot.notificationPositionOnLockscreen.collectAsState() + + Layout( + modifier = Modifier.fillMaxSize(), + content = { + NotificationStack( + viewModel = viewModel.notifications, + isScrimVisible = false, + ) + } + ) { measurables, constraints -> + check(measurables.size == 1) + val height = notificationStackPosition.height.toInt() + val childConstraints = constraints.copy(minHeight = height, maxHeight = height) + val placeable = measurables[0].measure(childConstraints) + layout(constraints.maxWidth, constraints.maxHeight) { + val start = (constraints.maxWidth - placeable.measuredWidth) / 2 + placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt()) + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index dd71dfa0b008..c9d31fdcb8e5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -17,56 +17,197 @@ package com.android.systemui.notifications.ui.composable +import android.util.Log import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ValueKey +import com.android.compose.animation.scene.animateSharedFloatAsState +import com.android.systemui.notifications.ui.composable.Notifications.Form +import com.android.systemui.notifications.ui.composable.Notifications.SharedValues.SharedExpansionValue +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel object Notifications { object Elements { - val Notifications = ElementKey("Notifications") + val NotificationScrim = ElementKey("NotificationScrim") + val NotificationPlaceholder = ElementKey("NotificationPlaceholder") + val ShelfSpace = ElementKey("ShelfSpace") + } + + object SharedValues { + val SharedExpansionValue = ValueKey("SharedExpansionValue") + } + + enum class Form { + HunFromTop, + Stack, + HunFromBottom, } } +/** + * Adds the space where heads up notifications can appear in the scene. This should generally be the + * entire size of the scene. + */ @Composable -fun SceneScope.Notifications( +fun SceneScope.HeadsUpNotificationSpace( + viewModel: NotificationsPlaceholderViewModel, + isPeekFromBottom: Boolean = false, modifier: Modifier = Modifier, ) { - // TODO(b/272779828): implement. - Column( + NotificationPlaceholder( + viewModel = viewModel, + form = if (isPeekFromBottom) Form.HunFromBottom else Form.HunFromTop, + modifier = modifier, + ) +} + +/** Adds the space where notification stack will appear in the scene. */ +@Composable +fun SceneScope.NotificationStack( + viewModel: NotificationsPlaceholderViewModel, + isScrimVisible: Boolean, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier) { + if (isScrimVisible) { + Box( + modifier = + Modifier.element(Notifications.Elements.NotificationScrim) + .fillMaxSize() + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.surface) + ) + } + NotificationPlaceholder( + viewModel = viewModel, + form = Form.Stack, + modifier = Modifier.fillMaxSize(), + ) + } +} + +/** + * This may be added to the lockscreen to provide a space to the start of the lock icon where the + * short shelf has room to flow vertically below the lock icon, but to its start, allowing more + * notifications to fit in the stack itself. (see: b/213934746) + * + * NOTE: this is totally unused for now; it is here to clarify the future plan + */ +@Composable +fun SceneScope.NotificationShelfSpace( + viewModel: NotificationsPlaceholderViewModel, + modifier: Modifier = Modifier, +) { + Text( + text = "Shelf Space", + 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()}") + } + } + .clip(RoundedCornerShape(24.dp)) + .background(MaterialTheme.colorScheme.primaryContainer) + .padding(16.dp), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onPrimaryContainer, + ) +} + +@Composable +private fun SceneScope.NotificationPlaceholder( + viewModel: NotificationsPlaceholderViewModel, + form: Form, + modifier: Modifier = Modifier, +) { + val key = Notifications.Elements.NotificationPlaceholder + Box( modifier = modifier - .element(key = Notifications.Elements.Notifications) - .fillMaxWidth() - .defaultMinSize(minHeight = 300.dp) - .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.surface) - .padding(16.dp), + .element(key) + .debugBackground(viewModel) + .onSizeChanged { size: IntSize -> + debugLog(viewModel) { "STACK onSizeChanged: size=$size" } + } + .onPlaced { coordinates: LayoutCoordinates -> + debugLog(viewModel) { + "STACK onPlaced:" + + " size=${coordinates.size}" + + " position=${coordinates.positionInWindow()}" + + " bounds=${coordinates.boundsInWindow()}" + } + val boundsInWindow = coordinates.boundsInWindow() + viewModel.setPlaceholderPositionInWindow( + top = boundsInWindow.top, + bottom = boundsInWindow.bottom, + ) + } ) { - Text( - text = "Notifications", - modifier = Modifier.align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "Shelf", - modifier = Modifier.align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onSurface, - ) + val animatedExpansion by + animateSharedFloatAsState( + value = if (form == Form.HunFromTop) 0f else 1f, + key = SharedExpansionValue, + element = key + ) + debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" } + if (viewModel.isPlaceholderTextVisible) { + Text( + text = "Notifications", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.align(Alignment.Center), + ) + } + } +} + +private inline fun debugLog( + viewModel: NotificationsPlaceholderViewModel, + msg: () -> Any, +) { + if (viewModel.isDebugLoggingEnabled) { + Log.d(TAG, msg().toString()) } } + +private fun Modifier.debugBackground( + viewModel: NotificationsPlaceholderViewModel, + color: Color = DEBUG_COLOR, +): Modifier = + if (viewModel.isVisualDebuggingEnabled) { + background(color) + } else { + this + } + +private const val TAG = "FlexiNotifs" +private val DEBUG_COLOR = Color(1f, 0f, 0f, 0.2f) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index b9451d1c1585..9dd7bfaf3549 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -24,6 +24,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight @@ -43,6 +44,7 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.SceneKey @@ -101,53 +103,59 @@ private fun SceneScope.QuickSettingsScene( modifier: Modifier = Modifier, ) { // TODO(b/280887232): implement the real UI. - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val collapsedHeaderHeight = - with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - modifier - .fillMaxSize() - .clickable(onClick = { viewModel.onContentClicked() }) - .padding(start = 16.dp, end = 16.dp, bottom = 48.dp) - ) { - when (LocalWindowSizeClass.current.widthSizeClass) { - WindowWidthSizeClass.Compact -> - AnimatedVisibility( - visible = !isCustomizing, - enter = - expandVertically( - animationSpec = tween(1000), - initialHeight = { collapsedHeaderHeight }, - ) + fadeIn(tween(1000)), - exit = - shrinkVertically( - animationSpec = tween(1000), - targetHeight = { collapsedHeaderHeight }, - shrinkTowards = Alignment.Top, - ) + fadeOut(tween(1000)), - ) { - ExpandedShadeHeader( + Box(modifier = modifier.fillMaxSize()) { + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val collapsedHeaderHeight = + with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier.fillMaxSize() + .clickable(onClick = { viewModel.onContentClicked() }) + .padding(start = 16.dp, end = 16.dp, bottom = 48.dp) + ) { + when (LocalWindowSizeClass.current.widthSizeClass) { + WindowWidthSizeClass.Compact -> + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(1000), + initialHeight = { collapsedHeaderHeight }, + ) + fadeIn(tween(1000)), + exit = + shrinkVertically( + animationSpec = tween(1000), + targetHeight = { collapsedHeaderHeight }, + shrinkTowards = Alignment.Top, + ) + fadeOut(tween(1000)), + ) { + ExpandedShadeHeader( + viewModel = viewModel.shadeHeaderViewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + ) + } + else -> + CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, ) - } - else -> - CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - ) + } + Spacer(modifier = Modifier.height(16.dp)) + QuickSettings( + modifier = Modifier.fillMaxHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QS + ) } - Spacer(modifier = Modifier.height(16.dp)) - QuickSettings( - modifier = Modifier.fillMaxHeight(), - viewModel.qsSceneAdapter, - QSSceneAdapter.State.QS + HeadsUpNotificationSpace( + viewModel = viewModel.notifications, + isPeekFromBottom = true, + modifier = Modifier.padding(16.dp).fillMaxSize(), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index f35ea8373db7..bded98d52481 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -17,15 +17,20 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.Edge import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -36,7 +41,11 @@ import kotlinx.coroutines.flow.asStateFlow * content from the scene framework. */ @SysUISingleton -class GoneScene @Inject constructor() : ComposableScene { +class GoneScene +@Inject +constructor( + private val notificationsViewModel: NotificationsPlaceholderViewModel, +) : ComposableScene { override val key = SceneKey.Gone override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = @@ -56,6 +65,11 @@ class GoneScene @Inject constructor() : ComposableScene { override fun SceneScope.Content( modifier: Modifier, ) { - Box(modifier = modifier) + Box(modifier = modifier) { + HeadsUpNotificationSpace( + viewModel = notificationsViewModel, + modifier = Modifier.padding(16.dp).fillMaxSize(), + ) + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt index 45df2b1bb20c..6bb525aa00fb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt @@ -3,10 +3,12 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.ui.composable.Shade fun TransitionBuilder.goneToShadeTransition() { spec = tween(durationMillis = 500) translate(Shade.rootElementKey, Edge.Top, true) + fade(Notifications.Elements.NotificationScrim) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt index fadbdce80cbf..b7f12f3b40bd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt @@ -20,5 +20,5 @@ fun TransitionBuilder.lockscreenToShadeTransition() { startsOutsideLayoutBounds = false, ) } - fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) } + fractionRange(start = 0.5f) { fade(Notifications.Elements.NotificationScrim) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt index 5616175ed11c..d5c2a03b3f9f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt @@ -10,7 +10,7 @@ import com.android.systemui.shade.ui.composable.ShadeHeader fun TransitionBuilder.shadeToQuickSettingsTransition() { spec = tween(durationMillis = 500) - translate(Notifications.Elements.Notifications, Edge.Bottom) + translate(Notifications.Elements.NotificationScrim, Edge.Bottom) timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) } translate(ShadeHeader.Elements.CollapsedContent, y = ShadeHeader.Dimensions.CollapsedHeight) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index a02f046a18f5..2df151bc2d8e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -37,7 +37,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.notifications.ui.composable.Notifications +import com.android.systemui.notifications.ui.composable.NotificationStack import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Direction @@ -160,7 +160,11 @@ private fun SceneScope.ShadeScene( QSSceneAdapter.State.QQS ) Spacer(modifier = Modifier.height(16.dp)) - Notifications(modifier = Modifier.weight(1f)) + NotificationStack( + viewModel = viewModel.notifications, + isScrimVisible = true, + modifier = Modifier.weight(1f), + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt index 48d374207388..48d3fe000ff8 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt @@ -23,4 +23,6 @@ data class SharedNotificationContainerPosition( /** Whether any modifications to top/bottom are smoothly animated */ val animate: Boolean = false, -) +) { + val height: Float = bottom - top +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index e256b49be8f8..949c940bdebc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -56,6 +56,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -83,10 +84,18 @@ constructor( shadeRepository: ShadeRepository, sceneInteractorProvider: Provider<SceneInteractor>, ) { - /** Position information for the shared notification container. */ - val sharedNotificationContainerPosition = + // TODO(b/296118689): move to a repository + private val _sharedNotificationContainerPosition = MutableStateFlow(SharedNotificationContainerPosition()) + /** Position information for the shared notification container. */ + val sharedNotificationContainerPosition: StateFlow<SharedNotificationContainerPosition> = + _sharedNotificationContainerPosition.asStateFlow() + + fun setSharedNotificationContainerPosition(position: SharedNotificationContainerPosition) { + _sharedNotificationContainerPosition.value = position + } + /** * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at * all. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 165ee364c2c8..5673f997d23d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -29,10 +29,13 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject @@ -42,18 +45,24 @@ class DefaultNotificationStackScrollLayoutSection constructor( context: Context, private val featureFlags: FeatureFlags, + sceneContainerFlags: SceneContainerFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, + ambientState: AmbientState, controller: NotificationStackScrollLayoutController, notificationStackSizeCalculator: NotificationStackSizeCalculator, private val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : NotificationStackScrollLayoutSection( context, + sceneContainerFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, + notificationStackAppearanceViewModel, + ambientState, controller, notificationStackSizeCalculator, ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index 441f59d6df1d..a9e766e5f98d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -24,20 +24,28 @@ import androidx.constraintlayout.widget.ConstraintLayout import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import kotlinx.coroutines.DisposableHandle abstract class NotificationStackScrollLayoutSection constructor( protected val context: Context, + private val sceneContainerFlags: SceneContainerFlags, private val notificationPanelView: NotificationPanelView, private val sharedNotificationContainer: SharedNotificationContainer, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, + private val ambientState: AmbientState, private val controller: NotificationStackScrollLayoutController, private val notificationStackSizeCalculator: NotificationStackSizeCalculator, ) : KeyguardSection() { @@ -68,9 +76,19 @@ constructor( SharedNotificationContainerBinder.bind( sharedNotificationContainer, sharedNotificationContainerViewModel, + sceneContainerFlags, controller, notificationStackSizeCalculator, ) + if (sceneContainerFlags.flexiNotifsEnabled()) { + NotificationStackAppearanceViewBinder.bind( + sharedNotificationContainer, + notificationStackAppearanceViewModel, + sceneContainerFlags, + ambientState, + controller, + ) + } } override fun removeViews(constraintLayout: ConstraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index 2c45da63edb4..ab68ecd920ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -29,10 +29,13 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject @@ -42,18 +45,24 @@ class SplitShadeNotificationStackScrollLayoutSection constructor( context: Context, private val featureFlags: FeatureFlags, + sceneContainerFlags: SceneContainerFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, + ambientState: AmbientState, controller: NotificationStackScrollLayoutController, notificationStackSizeCalculator: NotificationStackSizeCalculator, private val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : NotificationStackScrollLayoutSection( context, + sceneContainerFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, + notificationStackAppearanceViewModel, + ambientState, controller, notificationStackSizeCalculator, ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 60f75f03609c..56cbd36f8cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -49,6 +49,7 @@ import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -99,6 +100,10 @@ constructor( val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition + /** the shared notification container position *on the lockscreen* */ + val notificationPositionOnLockscreen: StateFlow<SharedNotificationContainerPosition> + get() = keyguardInteractor.sharedNotificationContainerPosition + /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = previewMode.flatMapLatest { @@ -249,8 +254,9 @@ constructor( if (previewMode.value.isInPreviewMode) { return } - keyguardInteractor.sharedNotificationContainerPosition.value = + keyguardInteractor.setSharedNotificationContainerPosition( SharedNotificationContainerPosition(top, bottom) + ) } /** Is there an expanded pulse, are we animating in response? */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index c03e4d950cae..539db7fb1ae3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -37,6 +38,8 @@ constructor( deviceEntryInteractor: DeviceEntryInteractor, communalInteractor: CommunalInteractor, val longPress: KeyguardLongPressViewModel, + val keyguardRoot: KeyguardRootViewModel, + val notifications: NotificationsPlaceholderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 3941fd75ebaa..346d5c30a63e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.flow.map @@ -35,6 +36,7 @@ constructor( private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, val qsSceneAdapter: QSSceneAdapter, + val notifications: NotificationsPlaceholderViewModel, ) { /** Notifies that some content in quick settings was clicked. */ fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 8def457423e4..f3f9c916d705 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -97,6 +97,7 @@ constructor( /** Updates the visibility of the scene container. */ private fun hydrateVisibility() { applicationScope.launch { + // TODO(b/296114544): Combine with some global hun state to make it visible! sceneInteractor.transitionState .mapNotNull { state -> when (state) { diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 7fc409445f50..c88a04c24044 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -4,9 +4,11 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.view.WindowInsets +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import kotlinx.coroutines.flow.MutableStateFlow /** A root view of the main SysUI window that supports scenes. */ @@ -27,6 +29,8 @@ class SceneWindowRootView( fun init( viewModel: SceneContainerViewModel, containerConfig: SceneContainerConfig, + sharedNotificationContainer: SharedNotificationContainer, + flags: SceneContainerFlags, scenes: Set<Scene>, layoutInsetController: LayoutInsetsController, ) { @@ -37,6 +41,8 @@ class SceneWindowRootView( viewModel = viewModel, windowInsets = windowInsets, containerConfig = containerConfig, + sharedNotificationContainer = sharedNotificationContainer, + flags = flags, scenes = scenes, onVisibilityChangedInternal = { isVisible -> super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 17d6c9d319e8..4a839b8df9ba 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -31,10 +31,13 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.compose.ComposeFacade import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import java.time.Instant import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -47,6 +50,8 @@ object SceneWindowRootViewBinder { viewModel: SceneContainerViewModel, windowInsets: StateFlow<WindowInsets?>, containerConfig: SceneContainerConfig, + sharedNotificationContainer: SharedNotificationContainer, + flags: SceneContainerFlags, scenes: Set<Scene>, onVisibilityChangedInternal: (isVisible: Boolean) -> Unit, ) { @@ -91,6 +96,13 @@ object SceneWindowRootViewBinder { val legacyView = view.requireViewById<View>(R.id.legacy_window_root) view.addView(createVisibilityToggleView(legacyView)) + if (flags.flexiNotifsEnabled()) { + (sharedNotificationContainer.parent as? ViewGroup)?.removeView( + sharedNotificationContainer + ) + view.addView(sharedNotificationContainer) + } + launch { viewModel.isVisible.collect { isVisible -> onVisibilityChangedInternal(isVisible) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 37073a6b5a50..e5657d334ff6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -68,6 +68,7 @@ abstract class ShadeViewProviderModule { sceneContainerFlags: SceneContainerFlags, viewModelProvider: Provider<SceneContainerViewModel>, containerConfigProvider: Provider<SceneContainerConfig>, + flagsProvider: Provider<SceneContainerFlags>, scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, ): WindowRootView { @@ -77,6 +78,9 @@ abstract class ShadeViewProviderModule { sceneWindowRootView.init( viewModel = viewModelProvider.get(), containerConfig = containerConfigProvider.get(), + sharedNotificationContainer = + sceneWindowRootView.requireViewById(R.id.shared_notification_container), + flags = flagsProvider.get(), scenes = scenesProvider.get(), layoutInsetController = layoutInsetController, ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index af880816914f..2a071def083a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -21,12 +21,13 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn -import javax.inject.Inject /** Models UI state and handles user input for the shade scene. */ @SysUISingleton @@ -37,6 +38,7 @@ constructor( private val deviceEntryInteractor: DeviceEntryInteractor, val qsSceneAdapter: QSSceneAdapter, val shadeHeaderViewModel: ShadeHeaderViewModel, + val notifications: NotificationsPlaceholderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt new file mode 100644 index 000000000000..7c10663bbb50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.stack.data.repository + +import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** A repository which holds state about and controlling the appearance of the NSSL */ +@SysUISingleton +class NotificationStackAppearanceRepository @Inject constructor() { + /** The position of the notification stack in the current scene */ + val stackPosition = MutableStateFlow(SharedNotificationContainerPosition(0f, 0f)) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt new file mode 100644 index 000000000000..820fe0b1f11e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.stack.domain.interactor + +import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** An interactor which controls the appearance of the NSSL */ +@SysUISingleton +class NotificationStackAppearanceInteractor +@Inject +constructor( + private val repository: NotificationStackAppearanceRepository, +) { + /** The position of the notification stack in the current scene */ + val stackPosition: StateFlow<SharedNotificationContainerPosition> + get() = repository.stackPosition.asStateFlow() + + /** Sets the position of the notification stack in the current scene */ + fun setStackPosition(position: SharedNotificationContainerPosition) { + check(position.top <= position.bottom) { "Invalid position: $position" } + repository.stackPosition.value = position + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt new file mode 100644 index 000000000000..5a71bd6fa116 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.shared + +import com.android.systemui.scene.shared.flag.SceneContainerFlags + +private const val FLEXI_NOTIFS = false + +/** + * Returns whether flexiglass is displaying notifications, which is currently an optional piece of + * flexiglass + */ +fun SceneContainerFlags.flexiNotifsEnabled() = FLEXI_NOTIFS && isEnabled() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt new file mode 100644 index 000000000000..4d6a6ee98b7d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewbinder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel +import kotlinx.coroutines.launch + +/** Binds the shared notification container to its view-model. */ +object NotificationStackAppearanceViewBinder { + + @JvmStatic + fun bind( + view: SharedNotificationContainer, + viewModel: NotificationStackAppearanceViewModel, + sceneContainerFlags: SceneContainerFlags, + ambientState: AmbientState, + controller: NotificationStackScrollLayoutController, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + viewModel.stackPosition.collect { + controller.updateTopPadding( + it.top, + controller.isAddOrRemoveAnimationPending + ) + } + } + launch { + viewModel.expandFraction.collect { + ambientState.expansionFraction = it + controller.expandedHeight = it * controller.view.height + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 0ff1bec84d16..44006fc3fd4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -19,8 +19,10 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import kotlinx.coroutines.DisposableHandle @@ -33,6 +35,7 @@ object SharedNotificationContainerBinder { fun bind( view: SharedNotificationContainer, viewModel: SharedNotificationContainerViewModel, + sceneContainerFlags: SceneContainerFlags, controller: NotificationStackScrollLayoutController, notificationStackSizeCalculator: NotificationStackSizeCalculator, ): DisposableHandle { @@ -68,10 +71,12 @@ object SharedNotificationContainerBinder { .collect { controller.setMaxDisplayedNotifications(it) } } - launch { - viewModel.position.collect { - val animate = it.animate || controller.isAddOrRemoveAnimationPending() - controller.updateTopPadding(it.top, animate) + if (!sceneContainerFlags.flexiNotifsEnabled()) { + launch { + viewModel.position.collect { + val animate = it.animate || controller.isAddOrRemoveAnimationPending + controller.updateTopPadding(it.top, animate) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt new file mode 100644 index 000000000000..b86993486097 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ +@SysUISingleton +class NotificationStackAppearanceViewModel +@Inject +constructor( + stackAppearanceInteractor: NotificationStackAppearanceInteractor, + shadeInteractor: ShadeInteractor, +) { + /** The expansion fraction from the top of the notification shade */ + val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion + + /** The position of the notification stack in the current scene */ + val stackPosition: Flow<SharedNotificationContainerPosition> = + stackAppearanceInteractor.stackPosition +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt new file mode 100644 index 000000000000..7def6feedb41 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled +import javax.inject.Inject + +/** + * ViewModel used by the Notification placeholders inside the scene container to update the + * [NotificationStackAppearanceInteractor], and by extension control the NSSL. + */ +@SysUISingleton +class NotificationsPlaceholderViewModel +@Inject +constructor( + private val interactor: NotificationStackAppearanceInteractor, + flags: SceneContainerFlags, + featureFlags: FeatureFlagsClassic, +) { + /** DEBUG: whether the placeholder "Notifications" text should be shown. */ + val isPlaceholderTextVisible: Boolean = !flags.flexiNotifsEnabled() + + /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */ + val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES) + + /** DEBUG: whether the debug logging should be output. */ + val isDebugLoggingEnabled: Boolean = flags.flexiNotifsEnabled() + + /** Sets the position of the notification stack in the current scene */ + fun setPlaceholderPositionInWindow(top: Float, bottom: Float) { + interactor.setStackPosition(SharedNotificationContainerPosition(top, bottom)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index d6b6f75b3186..296ea884e5c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -25,7 +26,10 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged @@ -33,12 +37,14 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn /** View-model for the shared notification container, used by both the shade and keyguard spaces */ class SharedNotificationContainerViewModel @Inject constructor( private val interactor: SharedNotificationContainerInteractor, + @Application applicationScope: CoroutineScope, keyguardInteractor: KeyguardInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, @@ -103,32 +109,38 @@ constructor( * * When the shade is expanding, the position is controlled by... the shade. */ - val position: Flow<SharedNotificationContainerPosition> = - isOnLockscreenWithoutShade.flatMapLatest { onLockscreen -> - if (onLockscreen) { - combine( - keyguardInteractor.sharedNotificationContainerPosition, - configurationBasedDimensions - ) { position, config -> - if (config.useSplitShade) { - position.copy(top = 0f) - } else { - position + val position: StateFlow<SharedNotificationContainerPosition> = + isOnLockscreenWithoutShade + .flatMapLatest { onLockscreen -> + if (onLockscreen) { + combine( + keyguardInteractor.sharedNotificationContainerPosition, + configurationBasedDimensions + ) { position, config -> + if (config.useSplitShade) { + position.copy(top = 0f) + } else { + position + } + } + } else { + interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map { + (top, qsExpansion) -> + // When QS expansion > 0, it should directly set the top padding so do not + // animate it + val animate = qsExpansion == 0f + keyguardInteractor.sharedNotificationContainerPosition.value.copy( + top = top, + animate = animate + ) } - } - } else { - interactor.topPosition.sample(shadeInteractor.qsExpansion, ::Pair).map { - (top, qsExpansion) -> - // When QS expansion > 0, it should directly set the top padding so do not - // animate it - val animate = qsExpansion == 0f - keyguardInteractor.sharedNotificationContainerPosition.value.copy( - top = top, - animate = animate - ) } } - } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = SharedNotificationContainerPosition(0f, 0f), + ) /** * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 0b3bc9daa8b7..d07836d3abce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -94,6 +94,8 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { KeyguardLongPressViewModel( interactor = mock(), ), + keyguardRoot = utils.keyguardRootViewModel(), + notifications = utils.notificationsPlaceholderViewModel(), ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index ef129c85ed37..5c325ae67369 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -101,6 +101,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { ), shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, + notifications = utils.notificationsPlaceholderViewModel(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 6a054cd9aff7..c3294ff2e26c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -152,6 +152,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { KeyguardLongPressViewModel( interactor = mock(), ), + keyguardRoot = utils.keyguardRootViewModel(), + notifications = utils.notificationsPlaceholderViewModel(), ) private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) @@ -237,6 +239,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, + notifications = utils.notificationsPlaceholderViewModel(), ) utils.deviceEntryRepository.setUnlocked(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 0d3b2b372610..e2640af136a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -101,6 +101,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, + notifications = utils.notificationsPlaceholderViewModel(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index db8f21714964..3d54fdf9e364 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -253,8 +253,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Start on lockscreen showLockscreen() - keyguardInteractor.sharedNotificationContainerPosition.value = + keyguardInteractor.setSharedNotificationContainerPosition( SharedNotificationContainerPosition(top = 1f, bottom = 2f) + ) assertThat(position) .isEqualTo(SharedNotificationContainerPosition(top = 1f, bottom = 2f)) @@ -273,8 +274,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Start on lockscreen showLockscreen() - keyguardInteractor.sharedNotificationContainerPosition.value = + keyguardInteractor.setSharedNotificationContainerPosition( SharedNotificationContainerPosition(top = 1f, bottom = 2f) + ) runCurrent() // Top should be overridden to 0f @@ -327,8 +329,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.sharedNotificationContainerPosition.value = + keyguardInteractor.setSharedNotificationContainerPosition( SharedNotificationContainerPosition(top = 1f, bottom = 2f) + ) assertThat(maxNotifications).isEqualTo(10) @@ -349,8 +352,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.sharedNotificationContainerPosition.value = + keyguardInteractor.setSharedNotificationContainerPosition( SharedNotificationContainerPosition(top = 1f, bottom = 2f) + ) assertThat(maxNotifications).isEqualTo(10) @@ -383,8 +387,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.sharedNotificationContainerPosition.value = + keyguardInteractor.setSharedNotificationContainerPosition( SharedNotificationContainerPosition(top = 1f, bottom = 2f) + ) // -1 means No Limit assertThat(maxNotifications).isEqualTo(-1) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 29e73b548b0b..2decb3e67c53 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -64,6 +64,7 @@ import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -78,6 +79,9 @@ import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository @@ -117,6 +121,7 @@ class SceneTestUtils( FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, false) set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.NSSL_DEBUG_LINES, false) } val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true } val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() } @@ -271,6 +276,19 @@ class SceneTestUtils( ) } + fun keyguardRootViewModel(): KeyguardRootViewModel = mock() + + fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel { + return NotificationsPlaceholderViewModel( + interactor = + NotificationStackAppearanceInteractor( + repository = NotificationStackAppearanceRepository(), + ), + flags = sceneContainerFlags, + featureFlags = featureFlags, + ) + } + fun bouncerViewModel( bouncerInteractor: BouncerInteractor, authenticationInteractor: AuthenticationInteractor, |