Merge "Clock Settings selection animation (1/2)" into udc-dev am: 701f7067cc am: bb135f7e66
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/ThemePicker/+/22258943
Change-Id: I0a3d2429743692c05232c6b5da461f2fe2113951
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/layout/color_option_2.xml b/res/layout/color_option_2.xml
index 8c9c966..dcedaa3 100644
--- a/res/layout/color_option_2.xml
+++ b/res/layout/color_option_2.xml
@@ -16,7 +16,8 @@
<!-- Content description is set programmatically on the parent FrameLayout -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="@dimen/option_item_size"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipChildren="false">
@@ -86,5 +87,16 @@
android:importantForAccessibility="no" />
</FrameLayout>
</FrameLayout>
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/option_bottom_margin"
+ android:textColor="@color/text_color_primary"
+ android:visibility="gone"
+ android:gravity="center"
+ android:text="Placeholder for stable size calculation, please do not remove."
+ tools:ignore="HardcodedText" />
</LinearLayout>
diff --git a/res/layout/fragment_clock_settings.xml b/res/layout/fragment_clock_settings.xml
index 5208222..58232a7 100644
--- a/res/layout/fragment_clock_settings.xml
+++ b/res/layout/fragment_clock_settings.xml
@@ -57,11 +57,13 @@
android:layout_marginBottom="28dp"
android:background="@drawable/picker_fragment_background"
android:paddingTop="22dp"
- android:paddingBottom="62dp">
+ android:paddingBottom="36dp"
+ android:clipChildren="false">
<FrameLayout
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="22dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tabs"
@@ -90,25 +92,28 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="16dp">
+ android:clipChildren="false">
<LinearLayout
android:id="@+id/color_picker_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:clipChildren="false">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="16dp">
+ android:layout_marginBottom="16dp"
+ android:clipChildren="false">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/color_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
- android:paddingHorizontal="16dp" />
+ android:paddingHorizontal="16dp"
+ android:clipChildren="false" />
<!--
This is just an invisible placeholder put in place so that the parent keeps its
@@ -118,7 +123,7 @@
without changing its size after the content is loaded into the RecyclerView.
-->
<include
- layout="@layout/color_option_with_background"
+ layout="@layout/color_option_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible" />
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
index c0b7403..1bbb965 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -33,9 +33,11 @@
import com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.ui.adapter.ColorOptionAdapter
+import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder
+import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
import com.android.customization.picker.common.ui.view.ItemSpacing
import com.android.wallpaper.R
+import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -56,7 +58,15 @@
tabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
val colorOptionContainerView: RecyclerView = view.requireViewById(R.id.color_options)
- val colorOptionAdapter = ColorOptionAdapter()
+ val colorOptionAdapter =
+ OptionItemAdapter(
+ layoutResourceId = R.layout.color_option_2,
+ lifecycleOwner = lifecycleOwner,
+ bindIcon = { foregroundView: View, colorIcon: ColorOptionIconViewModel ->
+ val viewGroup = foregroundView as? ViewGroup
+ viewGroup?.let { ColorOptionIconBinder.bind(viewGroup, colorIcon) }
+ }
+ )
colorOptionContainerView.adapter = colorOptionAdapter
colorOptionContainerView.layoutManager =
LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
index c3cd217..1514f1b 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -26,9 +26,12 @@
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
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.ColorOptionViewModel
+import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
import com.android.wallpaper.R
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -38,8 +41,8 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -108,50 +111,48 @@
}
@OptIn(ExperimentalCoroutinesApi::class)
- val colorOptions: StateFlow<List<ColorOptionViewModel>> =
- combine(colorPickerInteractor.colorOptions, clockPickerInteractor.selectedColorId, ::Pair)
- .mapLatest { (colorOptions, selectedColorId) ->
- // Use mapLatest and delay(100) here to prevent too many selectedClockColor update
- // events from ClockRegistry upstream, caused by sliding the saturation level bar.
- delay(COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- buildList {
- val defaultThemeColorOptionViewModel =
- (colorOptions[ColorType.WALLPAPER_COLOR]
- ?.find { it.isSelected }
- ?.colorOption as? ColorSeedOption)
- ?.toColorOptionViewModel(
- context,
- selectedColorId,
- )
- ?: (colorOptions[ColorType.PRESET_COLOR]
- ?.find { it.isSelected }
- ?.colorOption as? ColorBundle)
- ?.toColorOptionViewModel(
- context,
- selectedColorId,
- )
- if (defaultThemeColorOptionViewModel != null) {
- add(defaultThemeColorOptionViewModel)
- }
+ val colorOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
+ colorPickerInteractor.colorOptions.map { colorOptions ->
+ // Use mapLatest and delay(100) here to prevent too many selectedClockColor update
+ // events from ClockRegistry upstream, caused by sliding the saturation level bar.
+ delay(COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ buildList {
+ val defaultThemeColorOptionViewModel =
+ (colorOptions[ColorType.WALLPAPER_COLOR]?.find { it.isSelected })
+ ?.toOptionItemViewModel(context)
+ ?: (colorOptions[ColorType.PRESET_COLOR]?.find { it.isSelected })
+ ?.toOptionItemViewModel(context)
+ if (defaultThemeColorOptionViewModel != null) {
+ add(defaultThemeColorOptionViewModel)
+ }
- val selectedColorPosition = colorMap.keys.indexOf(selectedColorId)
-
- colorMap.values.forEachIndexed { index, colorModel ->
- val isSelected = selectedColorPosition == index
- val colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS
- add(
- ColorOptionViewModel(
- color0 = colorModel.color,
- color1 = colorModel.color,
- color2 = colorModel.color,
- color3 = colorModel.color,
- contentDescription =
+ colorMap.values.forEachIndexed { index, colorModel ->
+ val isSelectedFlow =
+ selectedColorId
+ .map { colorMap.keys.indexOf(it) == index }
+ .stateIn(viewModelScope)
+ val colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS
+ add(
+ OptionItemViewModel<ColorOptionIconViewModel>(
+ key = MutableStateFlow(colorModel.colorId) as StateFlow<String>,
+ payload =
+ ColorOptionIconViewModel(
+ colorModel.color,
+ colorModel.color,
+ colorModel.color,
+ colorModel.color,
+ ),
+ text =
+ Text.Loaded(
context.getString(
R.string.content_description_color_option,
index,
- ),
- isSelected = isSelected,
- onClick =
+ )
+ ),
+ isTextUserVisible = false,
+ isSelected = isSelectedFlow,
+ onClicked =
+ isSelectedFlow.map { isSelected ->
if (isSelected) {
null
} else {
@@ -169,74 +170,64 @@
),
)
}
- },
- )
+ }
+ },
)
- }
+ )
}
}
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyList(),
- )
+ }
@OptIn(ExperimentalCoroutinesApi::class)
val selectedColorOptionPosition: Flow<Int> =
- colorOptions.mapLatest { it.indexOfFirst { colorOption -> colorOption.isSelected } }
+ colorOptions.flatMapLatest { colorOptions ->
+ combine(colorOptions.map { colorOption -> colorOption.isSelected }) { selectedFlags ->
+ selectedFlags.indexOfFirst { it }
+ }
+ }
- private fun ColorSeedOption.toColorOptionViewModel(
- context: Context,
- selectedColorId: String?,
- ): ColorOptionViewModel {
- val colors = previewInfo.resolveColors(context.resources)
- return ColorOptionViewModel(
- color0 = colors[0],
- color1 = colors[1],
- color2 = colors[2],
- color3 = colors[3],
- contentDescription = getContentDescription(context).toString(),
- title = context.getString(R.string.default_theme_title),
- isSelected = selectedColorId == null,
- onClick =
- if (selectedColorId == null) {
- null
- } else {
- {
- clockPickerInteractor.setClockColor(
- selectedColorId = null,
- colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
- seedColor = null,
- )
- }
- },
- )
- }
-
- private fun ColorBundle.toColorOptionViewModel(
- context: Context,
- selectedColorId: String?
- ): ColorOptionViewModel {
- val primaryColor = previewInfo.resolvePrimaryColor(context.resources)
- val secondaryColor = previewInfo.resolveSecondaryColor(context.resources)
- return ColorOptionViewModel(
- color0 = primaryColor,
- color1 = secondaryColor,
- color2 = primaryColor,
- color3 = secondaryColor,
- contentDescription = getContentDescription(context).toString(),
- title = context.getString(R.string.default_theme_title),
- isSelected = selectedColorId == null,
- onClick =
- if (selectedColorId == null) {
- null
- } else {
- {
- clockPickerInteractor.setClockColor(
- selectedColorId = null,
- colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
- seedColor = null,
- )
+ private suspend fun ColorOptionModel.toOptionItemViewModel(
+ context: Context
+ ): OptionItemViewModel<ColorOptionIconViewModel> {
+ val optionItemPayload =
+ when (colorOption) {
+ is ColorSeedOption -> {
+ val colors = colorOption.previewInfo.resolveColors(context.resources)
+ ColorOptionIconViewModel(colors[0], colors[1], colors[2], colors[3])
+ }
+ is ColorBundle -> {
+ val primaryColor =
+ colorOption.previewInfo.resolvePrimaryColor(context.resources)
+ val secondaryColor =
+ colorOption.previewInfo.resolveSecondaryColor(context.resources)
+ ColorOptionIconViewModel(
+ primaryColor,
+ secondaryColor,
+ primaryColor,
+ secondaryColor
+ )
+ }
+ else -> null
+ }
+ val isSelectedFlow = selectedColorId.map { it == null }.stateIn(viewModelScope)
+ return OptionItemViewModel<ColorOptionIconViewModel>(
+ key = MutableStateFlow(key) as StateFlow<String>,
+ payload = optionItemPayload,
+ text = Text.Loaded(context.getString(R.string.default_theme_title)),
+ isTextUserVisible = true,
+ isSelected = isSelectedFlow,
+ onClicked =
+ isSelectedFlow.map { isSelected ->
+ if (isSelected) {
+ null
+ } else {
+ {
+ clockPickerInteractor.setClockColor(
+ selectedColorId = null,
+ colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
+ seedColor = null,
+ )
+ }
}
},
)
diff --git a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
index 81a5810..73c1ac6 100644
--- a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
+++ b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
@@ -129,6 +129,7 @@
.getContentDescription(context)
.toString()
),
+ isTextUserVisible = false,
isSelected = isSelectedFlow,
onClicked =
isSelectedFlow.map { isSelected ->
@@ -183,6 +184,7 @@
.getContentDescription(context)
.toString()
),
+ isTextUserVisible = false,
isSelected = isSelectedFlow,
onClicked =
isSelectedFlow.map { isSelected ->
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
index d53288d..a959a46 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
@@ -88,15 +88,20 @@
val observedSeedColor = collectLastValue(underTest.seedColor)
// Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- assertThat(observedClockColorOptions()!![0].isSelected).isTrue()
- assertThat(observedClockColorOptions()!![0].onClick).isNull()
+ val option0IsSelected = collectLastValue(observedClockColorOptions()!![0].isSelected)
+ val option0OnClicked = collectLastValue(observedClockColorOptions()!![0].onClicked)
+ assertThat(option0IsSelected()).isTrue()
+ assertThat(option0OnClicked()).isNull()
assertThat(observedSelectedColorOptionPosition()).isEqualTo(0)
- observedClockColorOptions()!![1].onClick?.invoke()
+ val option1OnClickedBefore = collectLastValue(observedClockColorOptions()!![1].onClicked)
+ option1OnClickedBefore()?.invoke()
// Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- assertThat(observedClockColorOptions()!![1].isSelected).isTrue()
- assertThat(observedClockColorOptions()!![1].onClick).isNull()
+ val option1IsSelected = collectLastValue(observedClockColorOptions()!![1].isSelected)
+ val option1OnClickedAfter = collectLastValue(observedClockColorOptions()!![1].onClicked)
+ assertThat(option1IsSelected()).isTrue()
+ assertThat(option1OnClickedAfter()).isNull()
assertThat(observedSelectedColorOptionPosition()).isEqualTo(1)
assertThat(observedSliderProgress())
.isEqualTo(ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS)
@@ -120,10 +125,12 @@
val observedSeedColor = collectLastValue(underTest.seedColor)
// Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- assertThat(observedClockColorOptions()!![0].isSelected).isTrue()
+ val option0IsSelected = collectLastValue(observedClockColorOptions()!![0].isSelected)
+ assertThat(option0IsSelected()).isTrue()
assertThat(observedIsSliderEnabled()).isFalse()
- observedClockColorOptions()!![1].onClick?.invoke()
+ val option1OnClicked = collectLastValue(observedClockColorOptions()!![1].onClicked)
+ option1OnClicked()?.invoke()
// Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)