1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.notifications.ui.composable 18 19 import androidx.compose.foundation.layout.Box 20 import androidx.compose.foundation.layout.Column 21 import androidx.compose.foundation.layout.fillMaxWidth 22 import androidx.compose.foundation.layout.padding 23 import androidx.compose.runtime.Composable 24 import androidx.compose.ui.Alignment 25 import androidx.compose.ui.Modifier 26 import androidx.compose.ui.platform.LocalResources 27 import androidx.compose.ui.res.dimensionResource 28 import com.android.compose.animation.scene.ContentScope 29 import com.android.compose.animation.scene.ElementKey 30 import com.android.compose.animation.scene.UserAction 31 import com.android.compose.animation.scene.UserActionResult 32 import com.android.internal.jank.InteractionJankMonitor 33 import com.android.systemui.dagger.SysUISingleton 34 import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn 35 import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection 36 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel 37 import com.android.systemui.lifecycle.rememberViewModel 38 import com.android.systemui.media.controls.ui.composable.MediaCarousel 39 import com.android.systemui.media.controls.ui.composable.isLandscape 40 import com.android.systemui.media.controls.ui.controller.MediaCarouselController 41 import com.android.systemui.media.controls.ui.view.MediaHost 42 import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED 43 import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED 44 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL 45 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel 46 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel 47 import com.android.systemui.res.R 48 import com.android.systemui.scene.session.ui.composable.SaveableSession 49 import com.android.systemui.scene.shared.model.Overlays 50 import com.android.systemui.scene.ui.composable.Overlay 51 import com.android.systemui.shade.ui.composable.OverlayShade 52 import com.android.systemui.shade.ui.composable.OverlayShadeHeader 53 import com.android.systemui.shade.ui.composable.isFullWidthShade 54 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView 55 import com.android.systemui.util.Utils 56 import dagger.Lazy 57 import javax.inject.Inject 58 import javax.inject.Named 59 import kotlinx.coroutines.flow.Flow 60 61 @SysUISingleton 62 class NotificationsShadeOverlay 63 @Inject 64 constructor( 65 private val actionsViewModelFactory: NotificationsShadeOverlayActionsViewModel.Factory, 66 private val contentViewModelFactory: NotificationsShadeOverlayContentViewModel.Factory, 67 private val shadeSession: SaveableSession, 68 private val stackScrollView: Lazy<NotificationScrollView>, 69 private val clockSection: DefaultClockSection, 70 private val keyguardClockViewModel: KeyguardClockViewModel, 71 private val mediaCarouselController: MediaCarouselController, 72 @Named(QUICK_QS_PANEL) private val mediaHost: Lazy<MediaHost>, 73 private val jankMonitor: InteractionJankMonitor, 74 ) : Overlay { 75 override val key = Overlays.NotificationsShade 76 <lambda>null77 private val actionsViewModel: NotificationsShadeOverlayActionsViewModel by lazy { 78 actionsViewModelFactory.create() 79 } 80 81 override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions 82 activatenull83 override suspend fun activate(): Nothing { 84 actionsViewModel.activate() 85 } 86 87 @Composable Contentnull88 override fun ContentScope.Content(modifier: Modifier) { 89 val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings) 90 91 val viewModel = 92 rememberViewModel("NotificationsShadeOverlay-viewModel") { 93 contentViewModelFactory.create() 94 } 95 val placeholderViewModel = 96 rememberViewModel("NotificationsShadeOverlay-notifPlaceholderViewModel") { 97 viewModel.notificationsPlaceholderViewModelFactory.create() 98 } 99 100 val usingCollapsedLandscapeMedia = 101 Utils.useCollapsedMediaInLandscape(LocalResources.current) 102 mediaHost.get().expansion = 103 if (usingCollapsedLandscapeMedia && isLandscape()) COLLAPSED else EXPANDED 104 105 OverlayShade( 106 panelElement = NotificationsShade.Elements.Panel, 107 alignmentOnWideScreens = Alignment.TopStart, 108 modifier = modifier, 109 onScrimClicked = viewModel::onScrimClicked, 110 header = { 111 val headerViewModel = 112 rememberViewModel("NotificationsShadeOverlayHeader") { 113 viewModel.shadeHeaderViewModelFactory.create() 114 } 115 OverlayShadeHeader( 116 viewModel = headerViewModel, 117 modifier = Modifier.element(NotificationsShade.Elements.StatusBar), 118 ) 119 }, 120 ) { 121 Box { 122 Column { 123 if (isFullWidthShade()) { 124 val burnIn = rememberBurnIn(keyguardClockViewModel) 125 126 with(clockSection) { 127 SmallClock( 128 burnInParams = burnIn.parameters, 129 onTopChanged = burnIn.onSmallClockTopChanged, 130 ) 131 } 132 } 133 134 MediaCarousel( 135 isVisible = viewModel.showMedia, 136 mediaHost = mediaHost.get(), 137 carouselController = mediaCarouselController, 138 usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia, 139 modifier = 140 Modifier.padding( 141 top = notificationStackPadding, 142 start = notificationStackPadding, 143 end = notificationStackPadding, 144 ), 145 ) 146 147 NotificationScrollingStack( 148 shadeSession = shadeSession, 149 stackScrollView = stackScrollView.get(), 150 viewModel = placeholderViewModel, 151 jankMonitor = jankMonitor, 152 maxScrimTop = { 0f }, 153 shouldPunchHoleBehindScrim = false, 154 stackTopPadding = notificationStackPadding, 155 stackBottomPadding = notificationStackPadding, 156 shouldFillMaxSize = false, 157 shouldShowScrim = false, 158 supportNestedScrolling = false, 159 modifier = Modifier.fillMaxWidth(), 160 ) 161 } 162 // Communicates the bottom position of the drawable area within the shade to NSSL. 163 NotificationStackCutoffGuideline( 164 stackScrollView = stackScrollView.get(), 165 viewModel = placeholderViewModel, 166 modifier = 167 Modifier.align(Alignment.BottomCenter) 168 .padding(bottom = notificationStackPadding), 169 ) 170 } 171 } 172 } 173 } 174 175 object NotificationsShade { 176 object Elements { 177 val Panel = ElementKey("NotificationsShadeOverlayPanel") 178 val StatusBar = ElementKey("NotificationsShadeOverlayStatusBar") 179 } 180 } 181