Color picker preview & apply pattern
Establish color picker preview & apply pattern. Also ensure
selected color option is updated when color is updated since the new
picker no longer restarts after color updates.
Flag: com.android.systemui.shared.new_customization_picker_ui
Test: ColorPickerViewModelTest
Bug: 350718581
Bug: 288312530
Change-Id: Ib992d0ed34eee8e6ec9bda820dd110c37d9a238a
diff --git a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
index f5b4ac5..f393880 100644
--- a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
@@ -24,6 +24,7 @@
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
import com.android.systemui.monet.Style
+import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
import javax.inject.Inject
@@ -46,6 +47,8 @@
private val colorManager: ColorCustomizationManager,
) : ColorPickerRepository {
+ private val isNewPickerUi = BaseFlags.get().isNewPickerUi()
+
private val homeWallpaperColors: StateFlow<WallpaperColorsModel?> =
wallpaperColorsRepository.homeWallpaperColors
private val lockWallpaperColors: StateFlow<WallpaperColorsModel?> =
@@ -56,8 +59,7 @@
private val _isApplyingSystemColor = MutableStateFlow(false)
override val isApplyingSystemColor = _isApplyingSystemColor.asStateFlow()
- // TODO (b/299510645): update color options on selected option change after restart is disabled
- override val colorOptions: Flow<Map<ColorType, List<ColorOptionModel>>> =
+ private val generatedColorOptions: Flow<Map<ColorType, List<ColorOptionImpl>>> =
combine(homeWallpaperColors, lockWallpaperColors) { homeColors, lockColors ->
homeColors to lockColors
}
@@ -71,7 +73,7 @@
Result.success(
mapOf(
ColorType.WALLPAPER_COLOR to listOf(),
- ColorType.PRESET_COLOR to listOf()
+ ColorType.PRESET_COLOR to listOf(),
)
)
)
@@ -81,28 +83,27 @@
val lockColorsLoaded = lockColors as WallpaperColorsModel.Loaded
colorManager.setWallpaperColors(
homeColorsLoaded.colors,
- lockColorsLoaded.colors
+ lockColorsLoaded.colors,
)
colorManager.fetchOptions(
object : CustomizationManager.OptionsFetchedListener<ColorOption?> {
override fun onOptionsLoaded(options: MutableList<ColorOption?>?) {
- val wallpaperColorOptions: MutableList<ColorOptionModel> =
+ val wallpaperColorOptions: MutableList<ColorOptionImpl> =
mutableListOf()
- val presetColorOptions: MutableList<ColorOptionModel> =
+ val presetColorOptions: MutableList<ColorOptionImpl> =
mutableListOf()
options?.forEach { option ->
when ((option as ColorOptionImpl).type) {
ColorType.WALLPAPER_COLOR ->
- wallpaperColorOptions.add(option.toModel())
- ColorType.PRESET_COLOR ->
- presetColorOptions.add(option.toModel())
+ wallpaperColorOptions.add(option)
+ ColorType.PRESET_COLOR -> presetColorOptions.add(option)
}
}
continuation.resumeWith(
Result.success(
mapOf(
ColorType.WALLPAPER_COLOR to wallpaperColorOptions,
- ColorType.PRESET_COLOR to presetColorOptions
+ ColorType.PRESET_COLOR to presetColorOptions,
)
)
)
@@ -117,11 +118,88 @@
)
}
},
- /* reload= */ false
+ /* reload= */ false,
)
}
}
+ override val colorOptions: Flow<Map<ColorType, List<ColorOptionModel>>> =
+ if (isNewPickerUi) {
+ // Convert to ColorOptionModel. When the selected color option changes, update each
+ // ColorOptionModel's isSelected by calling toModel again.
+ combine(generatedColorOptions, selectedColorOption) { generatedColorOptions, _ ->
+ generatedColorOptions
+ .map { entry ->
+ entry.key to entry.value.map { colorOption -> colorOption.toModel() }
+ }
+ .toMap()
+ }
+ } else {
+ combine(homeWallpaperColors, lockWallpaperColors) { homeColors, lockColors ->
+ homeColors to lockColors
+ }
+ .map { (homeColors, lockColors) ->
+ suspendCancellableCoroutine { continuation ->
+ if (
+ homeColors is WallpaperColorsModel.Loading ||
+ lockColors is WallpaperColorsModel.Loading
+ ) {
+ continuation.resumeWith(
+ Result.success(
+ mapOf(
+ ColorType.WALLPAPER_COLOR to listOf(),
+ ColorType.PRESET_COLOR to listOf(),
+ )
+ )
+ )
+ return@suspendCancellableCoroutine
+ }
+ val homeColorsLoaded = homeColors as WallpaperColorsModel.Loaded
+ val lockColorsLoaded = lockColors as WallpaperColorsModel.Loaded
+ colorManager.setWallpaperColors(
+ homeColorsLoaded.colors,
+ lockColorsLoaded.colors,
+ )
+ colorManager.fetchOptions(
+ object : CustomizationManager.OptionsFetchedListener<ColorOption?> {
+ override fun onOptionsLoaded(options: MutableList<ColorOption?>?) {
+ val wallpaperColorOptions: MutableList<ColorOptionModel> =
+ mutableListOf()
+ val presetColorOptions: MutableList<ColorOptionModel> =
+ mutableListOf()
+ options?.forEach { option ->
+ when ((option as ColorOptionImpl).type) {
+ ColorType.WALLPAPER_COLOR ->
+ wallpaperColorOptions.add(option.toModel())
+ ColorType.PRESET_COLOR ->
+ presetColorOptions.add(option.toModel())
+ }
+ }
+ continuation.resumeWith(
+ Result.success(
+ mapOf(
+ ColorType.WALLPAPER_COLOR to wallpaperColorOptions,
+ ColorType.PRESET_COLOR to presetColorOptions,
+ )
+ )
+ )
+ }
+
+ override fun onError(throwable: Throwable?) {
+ Log.e(TAG, "Error loading theme bundles", throwable)
+ continuation.resumeWith(
+ Result.failure(
+ throwable ?: Throwable("Error loading theme bundles")
+ )
+ )
+ }
+ },
+ /* reload= */ false,
+ )
+ }
+ }
+ }
+
override suspend fun select(colorOptionModel: ColorOptionModel) {
_isApplyingSystemColor.value = true
suspendCancellableCoroutine { continuation ->
@@ -141,7 +219,7 @@
Result.failure(throwable ?: Throwable("Error loading theme bundles"))
)
}
- }
+ },
)
}
}
@@ -158,11 +236,7 @@
colorOptionBuilder.addOverlayPackage(overlay.key, overlay.value)
}
val colorOption = colorOptionBuilder.build()
- return ColorOptionModel(
- key = "",
- colorOption = colorOption,
- isSelected = false,
- )
+ return ColorOptionModel(key = "", colorOption = colorOption, isSelected = false)
}
override fun getCurrentColorSource(): String? {
@@ -173,6 +247,8 @@
return ColorOptionModel(
key = "${this.type}::${this.style}::${this.serializedPackages}",
colorOption = this,
+ // Instead of using the selectedColorOption flow to determine isSelected, we check the
+ // source of truth, which is the settings, using ColorOption::isActive
isSelected = isActive(colorManager),
)
}
diff --git a/src/com/android/customization/picker/color/shared/model/ColorOptionModel.kt b/src/com/android/customization/picker/color/shared/model/ColorOptionModel.kt
index 5fde08e..ba477bc 100644
--- a/src/com/android/customization/picker/color/shared/model/ColorOptionModel.kt
+++ b/src/com/android/customization/picker/color/shared/model/ColorOptionModel.kt
@@ -27,5 +27,5 @@
val colorOption: ColorOption,
/** Whether this color option is selected. */
- var isSelected: Boolean,
+ val isSelected: Boolean,
)
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
index a039996..fc78ac7 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
@@ -20,6 +20,7 @@
import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
+import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
import com.android.themepicker.R
@@ -36,6 +37,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -51,14 +53,16 @@
@Assisted private val viewModelScope: CoroutineScope,
) {
+ private val overridingColorOption = MutableStateFlow<ColorOptionModel?>(null)
+ val previewingColorOption = overridingColorOption.asStateFlow()
+
private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null)
/** View-models for each color tab. */
val colorTypeTabs: Flow<List<FloatingToolbarTabViewModel>> =
- combine(
- interactor.colorOptions,
- selectedColorTypeTabId,
- ) { colorOptions, selectedColorTypeIdOrNull ->
+ combine(interactor.colorOptions, selectedColorTypeTabId) {
+ colorOptions,
+ selectedColorTypeIdOrNull ->
colorOptions.keys.mapIndexed { index, colorType ->
val isSelected =
(selectedColorTypeIdOrNull == null && index == 0) ||
@@ -118,7 +122,7 @@
val darkThemeColors =
colorOption.previewInfo.resolveColors(/* darkTheme= */ true)
val isSelectedFlow: StateFlow<Boolean> =
- interactor.selectingColorOption
+ previewingColorOption
.map {
it?.colorOption?.isEquivalent(colorOptionModel.colorOption)
?: colorOptionModel.isSelected
@@ -150,15 +154,7 @@
} else {
{
viewModelScope.launch {
- interactor.select(colorOptionModel)
- logger.logThemeColorApplied(
- colorOptionModel.colorOption
- .sourceForLogging,
- colorOptionModel.colorOption
- .styleForLogging,
- colorOptionModel.colorOption
- .seedColorForLogging,
- )
+ overridingColorOption.value = colorOptionModel
}
}
}
@@ -169,6 +165,28 @@
.toMap()
}
+ val onApply: Flow<(suspend () -> Unit)?> =
+ previewingColorOption.map { previewingColorOption ->
+ previewingColorOption?.let {
+ if (it.isSelected) {
+ null
+ } else {
+ {
+ interactor.select(it)
+ logger.logThemeColorApplied(
+ previewingColorOption.colorOption.sourceForLogging,
+ previewingColorOption.colorOption.styleForLogging,
+ previewingColorOption.colorOption.seedColorForLogging,
+ )
+ }
+ }
+ }
+ }
+
+ fun resetPreview() {
+ overridingColorOption.value = null
+ }
+
/** The list of all available color options for the selected Color Type. */
val colorOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
combine(allColorOptions, selectedColorTypeTabId) {
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
index 03831bd..d114ac8 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
@@ -61,6 +61,7 @@
keyguardQuickAffordancePickerViewModel2.resetPreview()
shapeAndGridPickerViewModel.resetPreview()
clockPickerViewModel.resetPreview()
+ colorPickerViewModel2.resetPreview()
return defaultCustomizationOptionsViewModel.deselectOption()
}
@@ -129,6 +130,8 @@
.SHORTCUTS -> keyguardQuickAffordancePickerViewModel2.onApply
ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
.APP_SHAPE_AND_GRID -> shapeAndGridPickerViewModel.onApply
+ ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption.COLORS ->
+ colorPickerViewModel2.onApply
else -> flow { emit(null) }
}
}
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
index d13d4b1..c153de6 100644
--- a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
@@ -97,19 +97,19 @@
}
@Test
- fun `Log selected wallpaper color`() =
+ fun onApply_wallpaperColor_shouldLogColor() =
testScope.runTest {
repository.setOptions(
listOf(
repository.buildWallpaperOption(
ColorOptionsProvider.COLOR_SOURCE_LOCK,
Style.EXPRESSIVE,
- "121212"
+ "121212",
)
),
listOf(repository.buildPresetOption(Style.FRUIT_SALAD, "#ABCDEF")),
ColorType.PRESET_COLOR,
- 0
+ 0,
)
val colorTypes = collectLastValue(underTest.colorTypeTabs)
@@ -117,8 +117,10 @@
// Select "Wallpaper colors" tab
colorTypes()?.get(0)?.onClick?.invoke()
- // Select a color option
+ // Select a color option to preview
selectColorOption(colorOptions, 0)
+ // Apply the selected color option
+ applySelectedColorOption()
assertThat(logger.themeColorSource)
.isEqualTo(StyleEnums.COLOR_SOURCE_LOCK_SCREEN_WALLPAPER)
@@ -127,19 +129,19 @@
}
@Test
- fun `Log selected preset color`() =
+ fun onApply_presetColor_shouldLogColor() =
testScope.runTest {
repository.setOptions(
listOf(
repository.buildWallpaperOption(
ColorOptionsProvider.COLOR_SOURCE_LOCK,
Style.EXPRESSIVE,
- "121212"
+ "121212",
)
),
listOf(repository.buildPresetOption(Style.FRUIT_SALAD, "#ABCDEF")),
ColorType.WALLPAPER_COLOR,
- 0
+ 0,
)
val colorTypes = collectLastValue(underTest.colorTypeTabs)
@@ -147,8 +149,10 @@
// Select "Wallpaper colors" tab
colorTypes()?.get(1)?.onClick?.invoke()
- // Select a color option
+ // Select a color option to preview
selectColorOption(colorOptions, 0)
+ // Apply the selected color option
+ applySelectedColorOption()
assertThat(logger.themeColorSource).isEqualTo(StyleEnums.COLOR_SOURCE_PRESET_COLOR)
assertThat(logger.themeColorStyle).isEqualTo(Style.FRUIT_SALAD.toString().hashCode())
@@ -156,7 +160,7 @@
}
@Test
- fun `Select a preset color`() =
+ fun selectColorOption() =
testScope.runTest {
val colorTypes = collectLastValue(underTest.colorTypeTabs)
val colorOptions = collectLastValue(underTest.colorOptions)
@@ -166,7 +170,7 @@
colorTypes = colorTypes(),
colorOptions = colorOptions(),
selectedColorTypeText = "Wallpaper colors",
- selectedColorOptionIndex = 0
+ selectedColorOptionIndex = 0,
)
// Select "Basic colors" tab
@@ -175,7 +179,7 @@
colorTypes = colorTypes(),
colorOptions = colorOptions(),
selectedColorTypeText = "Basic colors",
- selectedColorOptionIndex = -1
+ selectedColorOptionIndex = -1,
)
// Select a color option
@@ -187,7 +191,7 @@
colorTypes = colorTypes(),
colorOptions = colorOptions(),
selectedColorTypeText = "Wallpaper colors",
- selectedColorOptionIndex = -1
+ selectedColorOptionIndex = -1,
)
// Check new option is selected
@@ -196,7 +200,7 @@
colorTypes = colorTypes(),
colorOptions = colorOptions(),
selectedColorTypeText = "Basic colors",
- selectedColorOptionIndex = 2
+ selectedColorOptionIndex = 2,
)
}
@@ -210,10 +214,16 @@
onClickedFlow?.let { collectLastValue(it) }
onClickedLastValueOrNull?.let { onClickedLastValue ->
val onClickedOrNull: (() -> Unit)? = onClickedLastValue()
- onClickedOrNull?.let { onClicked -> onClicked() }
+ onClickedOrNull?.invoke()
}
}
+ /** Simulates a user selecting the affordance at the given index, if that is clickable. */
+ private suspend fun TestScope.applySelectedColorOption() {
+ val onApply = collectLastValue(underTest.onApply)()
+ onApply?.invoke()
+ }
+
/**
* Asserts the entire picker UI state is what is expected. This includes the color type tabs and
* the color options list.