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.keyguard.ui.composable.section 18 19 import android.content.Context 20 import androidx.compose.foundation.layout.Column 21 import androidx.compose.foundation.layout.fillMaxSize 22 import androidx.compose.foundation.layout.fillMaxWidth 23 import androidx.compose.foundation.layout.heightIn 24 import androidx.compose.foundation.layout.offset 25 import androidx.compose.foundation.layout.wrapContentSize 26 import androidx.compose.runtime.Composable 27 import androidx.compose.runtime.LaunchedEffect 28 import androidx.compose.runtime.getValue 29 import androidx.compose.ui.Modifier 30 import androidx.compose.ui.platform.LocalContext 31 import androidx.compose.ui.platform.LocalDensity 32 import androidx.compose.ui.unit.Density 33 import androidx.compose.ui.unit.Dp 34 import androidx.compose.ui.unit.IntOffset 35 import androidx.lifecycle.compose.collectAsStateWithLifecycle 36 import com.android.compose.animation.scene.SceneScope 37 import com.android.compose.animation.scene.SceneTransitionLayout 38 import com.android.compose.modifiers.thenIf 39 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor 40 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene 41 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene 42 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene 43 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeSmallClockScene 44 import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition 45 import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockScenes 46 import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn 47 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel 48 import javax.inject.Inject 49 50 class TopAreaSection 51 @Inject 52 constructor( 53 private val clockViewModel: KeyguardClockViewModel, 54 private val smartSpaceSection: SmartSpaceSection, 55 private val mediaCarouselSection: MediaCarouselSection, 56 private val clockSection: DefaultClockSection, 57 private val weatherClockSection: WeatherClockSection, 58 private val clockInteractor: KeyguardClockInteractor, 59 ) { 60 @Composable DefaultClockLayoutnull61 fun DefaultClockLayout( 62 modifier: Modifier = Modifier, 63 ) { 64 val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle() 65 val hasCustomPositionUpdatedAnimation by 66 clockViewModel.hasCustomPositionUpdatedAnimation.collectAsStateWithLifecycle() 67 val currentScene = 68 when (currentClockLayout) { 69 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK -> 70 splitShadeLargeClockScene 71 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK -> 72 splitShadeSmallClockScene 73 KeyguardClockViewModel.ClockLayout.LARGE_CLOCK -> largeClockScene 74 KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene 75 KeyguardClockViewModel.ClockLayout.WEATHER_LARGE_CLOCK -> 76 WeatherClockScenes.largeClockScene 77 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK -> 78 WeatherClockScenes.splitShadeLargeClockScene 79 } 80 81 SceneTransitionLayout( 82 modifier = modifier, 83 currentScene = currentScene, 84 onChangeScene = {}, 85 transitions = ClockTransition.defaultClockTransitions, 86 enableInterruptions = false, 87 ) { 88 scene(splitShadeLargeClockScene) { 89 LargeClockWithSmartSpace( 90 shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation 91 ) 92 } 93 94 scene(splitShadeSmallClockScene) { 95 SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f)) 96 } 97 98 scene(smallClockScene) { SmallClockWithSmartSpace() } 99 100 scene(largeClockScene) { LargeClockWithSmartSpace() } 101 102 scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() } 103 104 scene(WeatherClockScenes.splitShadeLargeClockScene) { 105 WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f)) 106 } 107 } 108 } 109 110 @Composable SmallClockWithSmartSpacenull111 private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) { 112 val burnIn = rememberBurnIn(clockInteractor) 113 114 Column(modifier = modifier) { 115 with(clockSection) { 116 SmallClock( 117 burnInParams = burnIn.parameters, 118 onTopChanged = burnIn.onSmallClockTopChanged, 119 modifier = Modifier.wrapContentSize() 120 ) 121 } 122 with(smartSpaceSection) { 123 SmartSpace( 124 burnInParams = burnIn.parameters, 125 onTopChanged = burnIn.onSmartspaceTopChanged, 126 ) 127 } 128 with(mediaCarouselSection) { KeyguardMediaCarousel() } 129 } 130 } 131 132 @Composable LargeClockWithSmartSpacenull133 private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) { 134 val burnIn = rememberBurnIn(clockInteractor) 135 val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() 136 137 LaunchedEffect(isLargeClockVisible) { 138 if (isLargeClockVisible) { 139 burnIn.onSmallClockTopChanged(null) 140 } 141 } 142 143 Column { 144 with(smartSpaceSection) { 145 SmartSpace( 146 burnInParams = burnIn.parameters, 147 onTopChanged = burnIn.onSmartspaceTopChanged, 148 ) 149 } 150 with(clockSection) { 151 LargeClock( 152 burnInParams = burnIn.parameters, 153 modifier = 154 Modifier.fillMaxSize().thenIf(shouldOffSetClockToOneHalf) { 155 // If we do not have a custom position animation, we want 156 // the clock to be on one half of the screen. 157 Modifier.offset { 158 IntOffset( 159 x = -clockSection.getClockCenteringDistance().toInt(), 160 y = 0, 161 ) 162 } 163 } 164 ) 165 } 166 } 167 } 168 169 @Composable WeatherLargeClockWithSmartSpacenull170 private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) { 171 val burnIn = rememberBurnIn(clockInteractor) 172 val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() 173 val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle() 174 175 LaunchedEffect(isLargeClockVisible) { 176 if (isLargeClockVisible) { 177 burnIn.onSmallClockTopChanged(null) 178 } 179 } 180 181 Column(modifier = modifier) { 182 val currentClock = currentClockState.value ?: return@Column 183 with(weatherClockSection) { 184 Time( 185 clock = currentClock, 186 burnInParams = burnIn.parameters, 187 ) 188 } 189 val density = LocalDensity.current 190 val context = LocalContext.current 191 192 with(smartSpaceSection) { 193 SmartSpace( 194 burnInParams = burnIn.parameters, 195 onTopChanged = burnIn.onSmartspaceTopChanged, 196 modifier = 197 Modifier.heightIn( 198 min = getDimen(context, "enhanced_smartspace_height", density) 199 ) 200 ) 201 } 202 with(weatherClockSection) { 203 LargeClockSectionBelowSmartspace( 204 burnInParams = burnIn.parameters, 205 clock = currentClock, 206 ) 207 } 208 } 209 } 210 211 /* 212 * Use this function to access dimen which cannot be access by R.dimen directly 213 * Currently use to access dimen from BcSmartspace 214 * @param name Name of resources 215 * @param density Density required to convert dimen from Int To Dp 216 */ getDimennull217 private fun getDimen(context: Context, name: String, density: Density): Dp { 218 val res = context.packageManager.getResourcesForApplication(context.packageName) 219 val id = res.getIdentifier(name, "dimen", context.packageName) 220 var dimen: Dp 221 with(density) { dimen = (if (id == 0) 0 else res.getDimensionPixelSize(id)).toDp() } 222 return dimen 223 } 224 } 225