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.res.dimensionResource 31 import androidx.compose.ui.viewinterop.AndroidView 32 import androidx.lifecycle.compose.collectAsStateWithLifecycle 33 import com.android.compose.animation.scene.ContentScope 34 import com.android.compose.modifiers.padding 35 import com.android.systemui.customization.R 36 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey 37 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey 38 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene 39 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene 40 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware 41 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged 42 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel 43 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters 44 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel 45 import javax.inject.Inject 46 47 @Composable 48 fun ClockView(view: View?, modifier: Modifier = Modifier) { 49 AndroidView( 50 factory = { 51 FrameLayout(it).apply { 52 // Clip nothing. The clock views at times render outside their bounds. Compose does 53 // not clip by default, so only this layer needs clipping to be explicitly disabled. 54 clipChildren = false 55 clipToPadding = false 56 } 57 }, 58 update = { parent -> 59 view?.let { 60 parent.removeAllViews() 61 (view.parent as? ViewGroup)?.removeView(view) 62 parent.addView(view) 63 } ?: run { parent.removeAllViews() } 64 }, 65 modifier = modifier, 66 ) 67 } 68 69 /** Provides small clock and large clock composables for the default clock face. */ 70 class DefaultClockSection 71 @Inject 72 constructor( 73 private val viewModel: KeyguardClockViewModel, 74 private val aodBurnInViewModel: AodBurnInViewModel, 75 ) { 76 @Composable SmallClocknull77 fun ContentScope.SmallClock( 78 burnInParams: BurnInParameters, 79 onTopChanged: (top: Float?) -> Unit, 80 modifier: Modifier = Modifier, 81 ) { 82 val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() 83 val smallTopMargin by 84 viewModel.smallClockTopMargin.collectAsStateWithLifecycle( 85 viewModel.getSmallClockTopMargin() 86 ) 87 if (currentClock?.smallClock?.view == null) { 88 return 89 } 90 91 ClockView( 92 checkNotNull(currentClock).smallClock.view, 93 modifier = 94 modifier 95 .height(dimensionResource(R.dimen.small_clock_height)) 96 .padding(horizontal = dimensionResource(R.dimen.clock_padding_start)) 97 .padding(top = { smallTopMargin }) 98 .onTopPlacementChanged(onTopChanged) 99 .burnInAware(viewModel = aodBurnInViewModel, params = burnInParams) 100 .element(smallClockElementKey), 101 ) 102 } 103 104 @Composable ContentScopenull105 fun ContentScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { 106 val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() 107 if (currentClock?.largeClock?.view == null) { 108 return 109 } 110 111 // Centering animation for clocks that have custom position animations. 112 LaunchedEffect(layoutState.currentTransition?.progress) { 113 val transition = layoutState.currentTransition ?: return@LaunchedEffect 114 if (currentClock?.largeClock?.config?.hasCustomPositionUpdatedAnimation != true) { 115 return@LaunchedEffect 116 } 117 118 // If we are not doing the centering animation, do not animate. 119 val progress = 120 if (transition.isTransitioningBetween(largeClockScene, splitShadeLargeClockScene)) { 121 transition.progress 122 } else { 123 1f 124 } 125 126 val dir = if (transition.toContent == splitShadeLargeClockScene) -1f else 1f 127 val distance = dir * getClockCenteringDistance() 128 val largeClock = checkNotNull(currentClock).largeClock 129 largeClock.animations.onPositionUpdated(distance = distance, fraction = progress) 130 } 131 132 Element(key = largeClockElementKey, modifier = modifier) { 133 ClockView( 134 checkNotNull(currentClock).largeClock.view, 135 modifier = 136 Modifier.fillMaxSize() 137 .burnInAware( 138 viewModel = aodBurnInViewModel, 139 params = burnInParams, 140 isClock = true, 141 ), 142 ) 143 } 144 } 145 getClockCenteringDistancenull146 fun getClockCenteringDistance(): Float { 147 return Resources.getSystem().displayMetrics.widthPixels / 4f 148 } 149 } 150