• 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 package com.android.customization.picker.clock.ui.viewmodel
17 
18 import android.graphics.Color
19 import androidx.lifecycle.ViewModel
20 import androidx.lifecycle.ViewModelProvider
21 import androidx.lifecycle.viewModelScope
22 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
23 import com.android.customization.picker.clock.shared.ClockSize
24 import com.android.wallpaper.R
25 import kotlinx.coroutines.CoroutineDispatcher
26 import kotlinx.coroutines.ExperimentalCoroutinesApi
27 import kotlinx.coroutines.Job
28 import kotlinx.coroutines.delay
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.SharingStarted
31 import kotlinx.coroutines.flow.StateFlow
32 import kotlinx.coroutines.flow.flatMapLatest
33 import kotlinx.coroutines.flow.map
34 import kotlinx.coroutines.flow.mapLatest
35 import kotlinx.coroutines.flow.mapNotNull
36 import kotlinx.coroutines.flow.stateIn
37 import kotlinx.coroutines.launch
38 
39 /**
40  * Clock carousel view model that provides data for the carousel of clock previews. When there is
41  * only one item, we should show a single clock preview instead of a carousel.
42  */
43 class ClockCarouselViewModel(
44     private val interactor: ClockPickerInteractor,
45     private val backgroundDispatcher: CoroutineDispatcher,
46 ) : ViewModel() {
47     @OptIn(ExperimentalCoroutinesApi::class)
48     val allClocks: StateFlow<List<ClockCarouselItemViewModel>> =
49         interactor.allClocks
50             .mapLatest { allClocks ->
51                 // Delay to avoid the case that the full list of clocks is not initiated.
52                 delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
53                 allClocks.map { ClockCarouselItemViewModel(it.clockId, it.isSelected) }
54             }
55             .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
56 
57     val selectedClockSize: Flow<ClockSize> = interactor.selectedClockSize
58 
59     val seedColor: Flow<Int?> = interactor.seedColor
60 
61     fun getClockCardColorResId(isDarkThemeEnabled: Boolean): Flow<Int> {
62         return interactor.seedColor.map {
63             it.let { seedColor ->
64                 // if seedColor is null, default clock color is selected
65                 if (seedColor == null) {
66                     if (isDarkThemeEnabled) {
67                         // In dark mode, use darkest surface container color
68                         R.color.system_surface_container_high
69                     } else {
70                         // In light mode, use lightest surface container color
71                         R.color.system_surface_bright
72                     }
73                 } else {
74                     val luminance = Color.luminance(seedColor)
75                     if (isDarkThemeEnabled) {
76                         if (luminance <= CARD_COLOR_CHANGE_LUMINANCE_THRESHOLD_DARK_THEME) {
77                             R.color.system_surface_bright
78                         } else {
79                             R.color.system_surface_container_high
80                         }
81                     } else {
82                         if (luminance <= CARD_COLOR_CHANGE_LUMINANCE_THRESHOLD_LIGHT_THEME) {
83                             R.color.system_surface_bright
84                         } else {
85                             R.color.system_surface_container_highest
86                         }
87                     }
88                 }
89             }
90         }
91     }
92 
93     @OptIn(ExperimentalCoroutinesApi::class)
94     val selectedIndex: Flow<Int> =
95         allClocks
96             .flatMapLatest { allClockIds ->
97                 interactor.selectedClockId.map { selectedClockId ->
98                     val index = allClockIds.indexOfFirst { it.clockId == selectedClockId }
99                     /** Making sure there is no active [setSelectedClockJob] */
100                     val isSetClockIdJobActive = setSelectedClockJob?.isActive == true
101                     if (index >= 0 && !isSetClockIdJobActive) {
102                         index
103                     } else {
104                         null
105                     }
106                 }
107             }
108             .mapNotNull { it }
109 
110     private var setSelectedClockJob: Job? = null
111     fun setSelectedClock(clockId: String) {
112         setSelectedClockJob?.cancel()
113         setSelectedClockJob =
114             viewModelScope.launch(backgroundDispatcher) { interactor.setSelectedClock(clockId) }
115     }
116 
117     class Factory(
118         private val interactor: ClockPickerInteractor,
119         private val backgroundDispatcher: CoroutineDispatcher,
120     ) : ViewModelProvider.Factory {
121         override fun <T : ViewModel> create(modelClass: Class<T>): T {
122             @Suppress("UNCHECKED_CAST")
123             return ClockCarouselViewModel(
124                 interactor = interactor,
125                 backgroundDispatcher = backgroundDispatcher,
126             )
127                 as T
128         }
129     }
130 
131     companion object {
132         const val CLOCKS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
133         const val CARD_COLOR_CHANGE_LUMINANCE_THRESHOLD_LIGHT_THEME: Float = 0.85f
134         const val CARD_COLOR_CHANGE_LUMINANCE_THRESHOLD_DARK_THEME: Float = 0.03f
135     }
136 }
137