• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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