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 javax.inject.Provider
27 import kotlinx.coroutines.flow.Flow
28 import kotlinx.coroutines.flow.distinctUntilChanged
29 import kotlinx.coroutines.flow.firstOrNull
30 import kotlinx.coroutines.flow.map
31 
32 /**
33  * Interactor for accessing application clock settings, as well as selecting and configuring custom
34  * clocks.
35  */
36 class ClockPickerInteractor(
37     private val repository: ClockPickerRepository,
38     private val snapshotRestorer: Provider<ClockPickerSnapshotRestorer>,
39 ) {
40 
41     val allClocks: Flow<List<ClockMetadataModel>> = repository.allClocks
42 
43     val selectedClockId: Flow<String> =
44         repository.selectedClock.map { clock -> clock.clockId }.distinctUntilChanged()
45 
46     val selectedColorId: Flow<String?> =
47         repository.selectedClock.map { clock -> clock.selectedColorId }.distinctUntilChanged()
48 
49     val colorToneProgress: Flow<Int> =
50         repository.selectedClock.map { clock -> clock.colorToneProgress }
51 
52     val seedColor: Flow<Int?> = repository.selectedClock.map { clock -> clock.seedColor }
53 
54     val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
55 
56     suspend fun setSelectedClock(clockId: String) {
57         // Use the [clockId] to override saved clock id, since it might not be updated in time
58         setClockOption(ClockSnapshotModel(clockId = clockId))
59     }
60 
61     suspend fun setClockColor(
62         selectedColorId: String?,
63         @IntRange(from = 0, to = 100) colorToneProgress: Int,
64         @ColorInt seedColor: Int?,
65     ) {
66         // Use the color to override saved color, since it might not be updated in time
67         setClockOption(
68             ClockSnapshotModel(
69                 selectedColorId = selectedColorId,
70                 colorToneProgress = colorToneProgress,
71                 seedColor = seedColor,
72             )
73         )
74     }
75 
76     suspend fun setClockSize(size: ClockSize) {
77         // Use the [ClockSize] to override saved clock size, since it might not be updated in time
78         setClockOption(ClockSnapshotModel(clockSize = size))
79     }
80 
81     suspend fun setClockOption(clockSnapshotModel: ClockSnapshotModel) {
82         // [ClockCarouselViewModel] is monitoring the [ClockPickerInteractor.setSelectedClock] job,
83         // so it needs to finish last.
84         storeCurrentClockOption(clockSnapshotModel)
85 
86         clockSnapshotModel.clockSize?.let { repository.setClockSize(it) }
87         clockSnapshotModel.colorToneProgress?.let {
88             repository.setClockColor(
89                 selectedColorId = clockSnapshotModel.selectedColorId,
90                 colorToneProgress = clockSnapshotModel.colorToneProgress,
91                 seedColor = clockSnapshotModel.seedColor
92             )
93         }
94         clockSnapshotModel.clockId?.let { repository.setSelectedClock(it) }
95     }
96 
97     /**
98      * Gets the [ClockSnapshotModel] from the storage and override with [latestOption].
99      *
100      * The storage might be in the middle of a write, and not reflecting the user's options, always
101      * pass in a [ClockSnapshotModel] if we know it's the latest option from a user's point of view.
102      *
103      * [selectedColorId] and [seedColor] have null state collide with nullable type, but we know
104      * they are presented whenever there's a [colorToneProgress].
105      */
106     suspend fun getCurrentClockToRestore(latestOption: ClockSnapshotModel? = null) =
107         ClockSnapshotModel(
108             clockId = latestOption?.clockId ?: selectedClockId.firstOrNull(),
109             clockSize = latestOption?.clockSize ?: selectedClockSize.firstOrNull(),
110             colorToneProgress = latestOption?.colorToneProgress ?: colorToneProgress.firstOrNull(),
111             selectedColorId = latestOption?.colorToneProgress?.let { latestOption.selectedColorId }
112                     ?: selectedColorId.firstOrNull(),
113             seedColor = latestOption?.colorToneProgress?.let { latestOption.seedColor }
114                     ?: seedColor.firstOrNull(),
115         )
116 
117     private suspend fun storeCurrentClockOption(clockSnapshotModel: ClockSnapshotModel) {
118         val option = getCurrentClockToRestore(clockSnapshotModel)
119         snapshotRestorer.get().storeSnapshot(option)
120     }
121 }
122