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.viewmodel 18 19 import android.content.Context 20 import android.content.res.Resources 21 import androidx.constraintlayout.helper.widget.Layer 22 import com.android.keyguard.ClockEventController 23 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor 24 import com.android.systemui.customization.R as customR 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.dagger.qualifiers.Main 28 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor 29 import com.android.systemui.keyguard.shared.model.ClockSize 30 import com.android.systemui.plugins.clocks.ClockPreviewConfig 31 import com.android.systemui.scene.shared.flag.SceneContainerFlag 32 import com.android.systemui.shade.ShadeDisplayAware 33 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor 34 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel 35 import com.android.systemui.statusbar.ui.SystemBarUtilsProxy 36 import javax.inject.Inject 37 import kotlinx.coroutines.CoroutineScope 38 import kotlinx.coroutines.flow.Flow 39 import kotlinx.coroutines.flow.SharingStarted 40 import kotlinx.coroutines.flow.StateFlow 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.map 43 import kotlinx.coroutines.flow.stateIn 44 45 @SysUISingleton 46 class KeyguardClockViewModel 47 @Inject 48 constructor( 49 private val context: Context, 50 keyguardClockInteractor: KeyguardClockInteractor, 51 @Application private val applicationScope: CoroutineScope, 52 aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, 53 private val shadeModeInteractor: ShadeModeInteractor, 54 private val systemBarUtils: SystemBarUtilsProxy, 55 @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, 56 // TODO: b/374267505 - Use ShadeDisplayAware resources here. 57 @Main private val resources: Resources, 58 ) { 59 var burnInLayer: Layer? = null 60 61 val clockSize: StateFlow<ClockSize> = keyguardClockInteractor.clockSize 62 63 val isLargeClockVisible: StateFlow<Boolean> = 64 clockSize 65 .map { it == ClockSize.LARGE } 66 .stateIn( 67 scope = applicationScope, 68 started = SharingStarted.Eagerly, 69 initialValue = true, 70 ) 71 72 val clockEventController: ClockEventController = keyguardClockInteractor.clockEventController 73 val currentClock = keyguardClockInteractor.currentClock 74 75 val hasCustomWeatherDataDisplay = 76 combine(isLargeClockVisible, currentClock) { isLargeClock, currentClock -> 77 currentClock?.let { clock -> 78 val face = if (isLargeClock) clock.largeClock else clock.smallClock 79 face.config.hasCustomWeatherDataDisplay 80 } ?: false 81 } 82 .stateIn( 83 scope = applicationScope, 84 started = SharingStarted.WhileSubscribed(), 85 initialValue = 86 currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false, 87 ) 88 89 val clockShouldBeCentered: StateFlow<Boolean> = 90 keyguardClockInteractor.clockShouldBeCentered.stateIn( 91 scope = applicationScope, 92 started = SharingStarted.WhileSubscribed(), 93 initialValue = true, 94 ) 95 96 // To translate elements below smartspace in weather clock to avoid overlapping between date 97 // element in weather clock and aod icons 98 val hasAodIcons: StateFlow<Boolean> = 99 aodNotificationIconViewModel.icons 100 .map { it.visibleIcons.isNotEmpty() } 101 .stateIn( 102 scope = applicationScope, 103 started = SharingStarted.WhileSubscribed(), 104 initialValue = false, 105 ) 106 107 val currentClockLayout: StateFlow<ClockLayout> = 108 combine( 109 isLargeClockVisible, 110 clockShouldBeCentered, 111 shadeModeInteractor.isShadeLayoutWide, 112 currentClock, 113 ) { isLargeClockVisible, clockShouldBeCentered, isShadeLayoutWide, currentClock -> 114 if (currentClock?.config?.useCustomClockScene == true) { 115 when { 116 isShadeLayoutWide && clockShouldBeCentered -> 117 ClockLayout.WEATHER_LARGE_CLOCK 118 isShadeLayoutWide && isLargeClockVisible -> 119 ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK 120 isShadeLayoutWide -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK 121 isLargeClockVisible -> ClockLayout.WEATHER_LARGE_CLOCK 122 else -> ClockLayout.SMALL_CLOCK 123 } 124 } else { 125 when { 126 isShadeLayoutWide && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK 127 isShadeLayoutWide && isLargeClockVisible -> 128 ClockLayout.SPLIT_SHADE_LARGE_CLOCK 129 isShadeLayoutWide -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK 130 isLargeClockVisible -> ClockLayout.LARGE_CLOCK 131 else -> ClockLayout.SMALL_CLOCK 132 } 133 } 134 } 135 .stateIn( 136 scope = applicationScope, 137 started = SharingStarted.WhileSubscribed(), 138 initialValue = ClockLayout.SMALL_CLOCK, 139 ) 140 141 val hasCustomPositionUpdatedAnimation: StateFlow<Boolean> = 142 combine(currentClock, isLargeClockVisible) { currentClock, isLargeClockVisible -> 143 isLargeClockVisible && 144 currentClock?.largeClock?.config?.hasCustomPositionUpdatedAnimation == true 145 } 146 .stateIn( 147 scope = applicationScope, 148 started = SharingStarted.WhileSubscribed(), 149 initialValue = false, 150 ) 151 152 /** Calculates the top margin for the small clock. */ 153 fun getSmallClockTopMargin(): Int { 154 return ClockPreviewConfig( 155 context, 156 shadeModeInteractor.isShadeLayoutWide.value, 157 SceneContainerFlag.isEnabled, 158 ) 159 .getSmallClockTopPadding(systemBarUtils.getStatusBarHeaderHeightKeyguard()) 160 } 161 162 val smallClockTopMargin = 163 combine( 164 configurationInteractor.onAnyConfigurationChange, 165 shadeModeInteractor.isShadeLayoutWide, 166 ) { _, _ -> 167 getSmallClockTopMargin() 168 } 169 170 /** Calculates the top margin for the large clock. */ 171 fun getLargeClockTopMargin(): Int { 172 return systemBarUtils.getStatusBarHeight() + 173 resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) + 174 resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) 175 } 176 177 val largeClockTopMargin: Flow<Int> = 178 configurationInteractor.onAnyConfigurationChange.map { getLargeClockTopMargin() } 179 180 val largeClockTextSize: Flow<Int> = 181 configurationInteractor.dimensionPixelSize(customR.dimen.large_clock_text_size) 182 183 fun dateWeatherBelowSmallClock() = 184 KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(context.resources.configuration) 185 186 enum class ClockLayout { 187 LARGE_CLOCK, 188 SMALL_CLOCK, 189 SPLIT_SHADE_LARGE_CLOCK, 190 SPLIT_SHADE_SMALL_CLOCK, 191 WEATHER_LARGE_CLOCK, 192 SPLIT_SHADE_WEATHER_LARGE_CLOCK, 193 } 194 } 195