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