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 
18 package com.android.customization.picker.clock.domain.interactor
19 
20 import androidx.annotation.ColorInt
21 import androidx.annotation.IntRange
22 import com.android.customization.picker.clock.data.repository.ClockPickerRepository
23 import com.android.customization.picker.clock.shared.ClockSize
24 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
25 import com.android.customization.picker.clock.shared.model.ClockSnapshotModel
26 import com.android.systemui.plugins.clocks.ClockFontAxisSetting
27 import javax.inject.Inject
28 import javax.inject.Singleton
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.distinctUntilChanged
31 import kotlinx.coroutines.flow.firstOrNull
32 import kotlinx.coroutines.flow.map
33 
34 /**
35  * Interactor for accessing application clock settings, as well as selecting and configuring custom
36  * clocks.
37  */
38 @Singleton
39 class ClockPickerInteractor
40 @Inject
41 constructor(
42     private val repository: ClockPickerRepository,
43     private val snapshotRestorer: ClockPickerSnapshotRestorer,
44 ) {
45 
46     val allClocks: Flow<List<ClockMetadataModel>> = repository.allClocks
47 
48     val selectedClockId: Flow<String> =
49         repository.selectedClock.map { clock -> clock.clockId }.distinctUntilChanged()
50 
51     val selectedClock: Flow<ClockMetadataModel> = repository.selectedClock
52 
53     val selectedColorId: Flow<String?> =
54         repository.selectedClock.map { clock -> clock.selectedColorId }.distinctUntilChanged()
55 
56     val colorToneProgress: Flow<Int> =
57         repository.selectedClock.map { clock -> clock.colorToneProgress }
58 
59     val seedColor: Flow<Int?> = repository.selectedClock.map { clock -> clock.seedColor }
60 
61     val axisSettings: Flow<List<ClockFontAxisSetting>?> =
62         repository.selectedClock.map { clock -> clock.fontAxes.map { it.toSetting() } }
63 
64     val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
65 
66     suspend fun setSelectedClock(clockId: String) {
67         // Use the [clockId] to override saved clock id, since it might not be updated in time
68         setClockOption(ClockSnapshotModel(clockId = clockId))
69     }
70 
71     suspend fun setClockColor(
72         selectedColorId: String?,
73         @IntRange(from = 0, to = 100) colorToneProgress: Int,
74         @ColorInt seedColor: Int?,
75     ) {
76         // Use the color to override saved color, since it might not be updated in time
77         setClockOption(
78             ClockSnapshotModel(
79                 selectedColorId = selectedColorId,
80                 colorToneProgress = colorToneProgress,
81                 seedColor = seedColor,
82             )
83         )
84     }
85 
86     suspend fun setClockSize(size: ClockSize) {
87         // Use the [ClockSize] to override saved clock size, since it might not be updated in time
88         setClockOption(ClockSnapshotModel(clockSize = size))
89     }
90 
91     suspend fun setClockFontAxes(axisSettings: List<ClockFontAxisSetting>) {
92         setClockOption(ClockSnapshotModel(axisSettings = axisSettings))
93     }
94 
95     suspend fun applyClock(
96         clockId: String?,
97         size: ClockSize?,
98         selectedColorId: String?,
99         @IntRange(from = 0, to = 100) colorToneProgress: Int?,
100         @ColorInt seedColor: Int?,
101         axisSettings: List<ClockFontAxisSetting>,
102     ) {
103         setClockOption(
104             ClockSnapshotModel(
105                 clockId = clockId,
106                 clockSize = size,
107                 selectedColorId = selectedColorId,
108                 colorToneProgress = colorToneProgress,
109                 seedColor = seedColor,
110                 axisSettings = axisSettings,
111             )
112         )
113     }
114 
115     private suspend fun setClockOption(clockSnapshotModel: ClockSnapshotModel) {
116         // [ClockCarouselViewModel] is monitoring the [ClockPickerInteractor.setSelectedClock] job,
117         // so it needs to finish last.
118         storeCurrentClockOption(clockSnapshotModel)
119 
120         clockSnapshotModel.clockSize?.let { repository.setClockSize(it) }
121         clockSnapshotModel.colorToneProgress?.let {
122             repository.setClockColor(
123                 selectedColorId = clockSnapshotModel.selectedColorId,
124                 colorToneProgress = clockSnapshotModel.colorToneProgress,
125                 seedColor = clockSnapshotModel.seedColor,
126             )
127         }
128         clockSnapshotModel.clockId?.let { repository.setSelectedClock(it) }
129         clockSnapshotModel.axisSettings?.let { repository.setClockFontAxes(it) }
130     }
131 
132     private suspend fun storeCurrentClockOption(clockSnapshotModel: ClockSnapshotModel) {
133         val option = getCurrentClockToRestore(clockSnapshotModel)
134         snapshotRestorer.storeSnapshot(option)
135     }
136 
137     /**
138      * Gets the [ClockSnapshotModel] from the storage and override with [latestOption].
139      *
140      * The storage might be in the middle of a write, and not reflecting the user's options, always
141      * pass in a [ClockSnapshotModel] if we know it's the latest option from a user's point of view.
142      *
143      * [selectedColorId] and [seedColor] have null state collide with nullable type, but we know
144      * they are presented whenever there's a [colorToneProgress].
145      */
146     private suspend fun getCurrentClockToRestore(latestOption: ClockSnapshotModel) =
147         ClockSnapshotModel(
148             clockId = latestOption.clockId ?: selectedClockId.firstOrNull(),
149             clockSize = latestOption.clockSize ?: selectedClockSize.firstOrNull(),
150             colorToneProgress = latestOption.colorToneProgress ?: colorToneProgress.firstOrNull(),
151             selectedColorId =
152                 latestOption.colorToneProgress?.let { latestOption.selectedColorId }
153                     ?: selectedColorId.firstOrNull(),
154             seedColor =
155                 latestOption.colorToneProgress?.let { latestOption.seedColor }
156                     ?: seedColor.firstOrNull(),
157             axisSettings = latestOption.axisSettings ?: axisSettings.firstOrNull(),
158         )
159 }
160