1 /* <lambda>null2 * Copyright (C) 2023 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.res.Resources 20 import android.view.View 21 import android.view.ViewGroup 22 import android.widget.FrameLayout 23 import androidx.compose.foundation.layout.fillMaxSize 24 import androidx.compose.foundation.layout.height 25 import androidx.compose.foundation.layout.padding 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.res.dimensionResource 32 import androidx.compose.ui.viewinterop.AndroidView 33 import androidx.core.view.contains 34 import androidx.lifecycle.compose.collectAsStateWithLifecycle 35 import com.android.compose.animation.scene.SceneScope 36 import com.android.compose.modifiers.padding 37 import com.android.systemui.customization.R 38 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey 39 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey 40 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene 41 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene 42 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware 43 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged 44 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel 45 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters 46 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel 47 import javax.inject.Inject 48 49 /** Provides small clock and large clock composables for the default clock face. */ 50 class DefaultClockSection 51 @Inject 52 constructor( 53 private val viewModel: KeyguardClockViewModel, 54 private val aodBurnInViewModel: AodBurnInViewModel, 55 ) { 56 @Composable 57 fun SceneScope.SmallClock( 58 burnInParams: BurnInParameters, 59 onTopChanged: (top: Float?) -> Unit, 60 modifier: Modifier = Modifier, 61 ) { 62 val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() 63 val smallTopMargin by 64 viewModel.smallClockTopMargin.collectAsStateWithLifecycle( 65 viewModel.getSmallClockTopMargin() 66 ) 67 if (currentClock?.smallClock?.view == null) { 68 return 69 } 70 val context = LocalContext.current 71 AndroidView( 72 factory = { context -> 73 FrameLayout(context).apply { 74 ensureClockViewExists(checkNotNull(currentClock).smallClock.view) 75 } 76 }, 77 update = { it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) }, 78 modifier = 79 modifier 80 .height(dimensionResource(R.dimen.small_clock_height)) 81 .padding(horizontal = dimensionResource(R.dimen.clock_padding_start)) 82 .padding(top = { smallTopMargin }) 83 .onTopPlacementChanged(onTopChanged) 84 .burnInAware( 85 viewModel = aodBurnInViewModel, 86 params = burnInParams, 87 ) 88 .element(smallClockElementKey), 89 ) 90 } 91 92 @Composable 93 fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { 94 val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() 95 if (currentClock?.largeClock?.view == null) { 96 return 97 } 98 99 // Centering animation for clocks that have custom position animations. 100 LaunchedEffect(layoutState.currentTransition?.progress) { 101 val transition = layoutState.currentTransition ?: return@LaunchedEffect 102 if (currentClock?.largeClock?.config?.hasCustomPositionUpdatedAnimation != true) { 103 return@LaunchedEffect 104 } 105 106 // If we are not doing the centering animation, do not animate. 107 val progress = 108 if (transition.isTransitioningBetween(largeClockScene, splitShadeLargeClockScene)) { 109 transition.progress 110 } else { 111 1f 112 } 113 114 val dir = if (transition.toScene == splitShadeLargeClockScene) -1f else 1f 115 val distance = dir * getClockCenteringDistance() 116 val largeClock = checkNotNull(currentClock).largeClock 117 largeClock.animations.onPositionUpdated( 118 distance = distance, 119 fraction = progress, 120 ) 121 } 122 123 MovableElement(key = largeClockElementKey, modifier = modifier) { 124 content { 125 AndroidView( 126 factory = { context -> 127 FrameLayout(context).apply { 128 ensureClockViewExists(checkNotNull(currentClock).largeClock.view) 129 } 130 }, 131 update = { 132 it.ensureClockViewExists(checkNotNull(currentClock).largeClock.view) 133 }, 134 modifier = 135 Modifier.fillMaxSize() 136 .burnInAware( 137 viewModel = aodBurnInViewModel, 138 params = burnInParams, 139 isClock = true 140 ) 141 ) 142 } 143 } 144 } 145 146 private fun FrameLayout.ensureClockViewExists(clockView: View) { 147 if (contains(clockView)) { 148 return 149 } 150 removeAllViews() 151 (clockView.parent as? ViewGroup)?.removeView(clockView) 152 addView(clockView) 153 } 154 155 fun getClockCenteringDistance(): Float { 156 return Resources.getSystem().displayMetrics.widthPixels / 4f 157 } 158 } 159