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