Merge "Update ClockChangeListener to match new interface type" into tm-qpr-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f4d525e..0d446df 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -33,7 +33,7 @@
<!-- The content description of clock entry. [CHAR LIMIT=NONE] -->
<string name="clock_picker_entry_content_description">Change a custom clock</string>
- <!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=15] -->
+ <!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=19] -->
<string name="clock_settings_title">Clock Settings</string>
<!-- Title of a tab to change the clock color. [CHAR LIMIT=15] -->
@@ -420,4 +420,22 @@
[CHAR LIMIT=128].
-->
<string name="more_colors">More Colors</string>
+
+ <!--
+ Accessibility string for a button that allows the user to select the default color for their
+ lock screen clock on the device. This is shown next to other buttons that allow the user to
+ select from a set of custom colors.
+
+ [CHAR LIMIT=NONE].
+ -->
+ <string name="content_description_default_color_option">Default color option</string>
+
+ <!--
+ Accessibility string for a button that allows the user to select a custom color for their lock
+ screen clock on the device. This is shown next to other such buttons that allow the user to
+ select from a set of custom colors.
+
+ [CHAR LIMIT=NONE].
+ -->
+ <string name="content_description_color_option">Color option <xliff:g name="color_number" example="1">%1$d</xliff:g></string>
</resources>
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index 900431e..e5c1424 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -25,6 +25,7 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
@@ -77,4 +78,9 @@
context: Context,
registry: ClockRegistry,
): ClockViewFactory
+
+ fun getClockSettingsViewModelFactory(
+ context: Context,
+ registry: ClockRegistry,
+ ): ClockSettingsViewModel.Factory
}
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 2924135..aed5a8a 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -38,6 +38,7 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
@@ -96,6 +97,7 @@
private var darkModeSnapshotRestorer: DarkModeSnapshotRestorer? = null
private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null
private var themedIconInteractor: ThemedIconInteractor? = null
+ private var clockSettingsViewModelFactory: ClockSettingsViewModel.Factory? = null
override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
return customizationSections
@@ -409,6 +411,18 @@
.also { themedIconInteractor = it }
}
+ override fun getClockSettingsViewModelFactory(
+ context: Context,
+ registry: ClockRegistry,
+ ): ClockSettingsViewModel.Factory {
+ return clockSettingsViewModelFactory
+ ?: ClockSettingsViewModel.Factory(
+ context,
+ getClockPickerInteractor(context, registry),
+ )
+ .also { clockSettingsViewModelFactory = it }
+ }
+
companion object {
@JvmStatic
private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER =
diff --git a/src/com/android/customization/picker/HorizontalTouchMovementAwareNestedScrollView.kt b/src/com/android/customization/picker/HorizontalTouchMovementAwareNestedScrollView.kt
new file mode 100644
index 0000000..06cf753
--- /dev/null
+++ b/src/com/android/customization/picker/HorizontalTouchMovementAwareNestedScrollView.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.customization.picker
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.core.widget.NestedScrollView
+import kotlin.math.abs
+
+/**
+ * This nested scroll view will detect horizontal touch movements and stop vertical scrolls when a
+ * horizontal touch movement is detected.
+ */
+class HorizontalTouchMovementAwareNestedScrollView(context: Context, attrs: AttributeSet?) :
+ NestedScrollView(context, attrs) {
+
+ private var startXPosition = 0f
+ private var startYPosition = 0f
+ private var isHorizontalTouchMovement = false
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ startXPosition = event.x
+ startYPosition = event.y
+ isHorizontalTouchMovement = false
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val xMoveDistance = abs(event.x - startXPosition)
+ val yMoveDistance = abs(event.y - startYPosition)
+ if (
+ !isHorizontalTouchMovement &&
+ xMoveDistance > yMoveDistance &&
+ xMoveDistance > ViewConfiguration.get(context).scaledTouchSlop
+ ) {
+ isHorizontalTouchMovement = true
+ }
+ }
+ else -> {}
+ }
+ return if (isHorizontalTouchMovement) {
+ // We only want to intercept the touch event when the touch moves more vertically than
+ // horizontally. So we return false.
+ false
+ } else {
+ super.onInterceptTouchEvent(event)
+ }
+ }
+}
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 4e838aa..e785ebd 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -16,6 +16,7 @@
package com.android.customization.picker.clock.ui.binder
import android.view.View
+import android.widget.SeekBar
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -41,22 +42,11 @@
lifecycleOwner: LifecycleOwner,
) {
val tabView: RecyclerView = view.requireViewById(R.id.tabs)
- val colorOptionContainer = view.requireViewById<View>(R.id.color_picker_container)
- val sizeOptions =
- view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group)
-
val tabAdapter = ClockSettingsTabAdapter()
tabView.adapter = tabAdapter
tabView.layoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
tabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
- sizeOptions.onRadioButtonClickListener =
- object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener {
- override fun onClick(size: ClockSize) {
- viewModel.setClockSize(size)
- }
- }
-
val colorOptionContainerView: RecyclerView = view.requireViewById(R.id.color_options)
val colorOptionAdapter = ColorOptionAdapter()
colorOptionContainerView.adapter = colorOptionAdapter
@@ -64,6 +54,30 @@
LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
colorOptionContainerView.addItemDecoration(ItemSpacing(ItemSpacing.ITEM_SPACING_DP))
+ val slider: SeekBar = view.requireViewById(R.id.slider)
+ slider.setOnSeekBarChangeListener(
+ object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(p0: SeekBar?, progress: Int, fromUser: Boolean) {
+ if (fromUser) {
+ viewModel.onSliderProgressChanged(progress)
+ }
+ }
+
+ override fun onStartTrackingTouch(p0: SeekBar?) = Unit
+ override fun onStopTrackingTouch(p0: SeekBar?) = Unit
+ }
+ )
+
+ val sizeOptions =
+ view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group)
+ sizeOptions.onRadioButtonClickListener =
+ object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener {
+ override fun onClick(size: ClockSize) {
+ viewModel.setClockSize(size)
+ }
+ }
+
+ val colorOptionContainer = view.requireViewById<View>(R.id.color_picker_container)
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { viewModel.tabs.collect { tabAdapter.setItems(it) } }
@@ -103,6 +117,18 @@
}
}
}
+
+ launch {
+ viewModel.sliderProgress.collect { progress ->
+ progress?.let { slider.setProgress(progress, false) }
+ }
+ }
+
+ launch {
+ viewModel.isSliderEnabled.collect { isEnabled ->
+ slider.isInvisible = !isEnabled
+ }
+ }
}
}
}
diff --git a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
index d716102..ed14889 100644
--- a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
@@ -27,7 +27,6 @@
import com.android.customization.picker.clock.ui.binder.ClockCarouselViewBinder
import com.android.customization.picker.clock.ui.binder.ClockSettingsBinder
import com.android.customization.picker.clock.ui.view.ClockCarouselView
-import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
import com.android.wallpaper.R
import com.android.wallpaper.model.WallpaperColorsViewModel
@@ -131,10 +130,14 @@
.show()
ClockSettingsBinder.bind(
view,
- ClockSettingsViewModel(
- context,
- injector.getClockPickerInteractor(context, registry)
- ),
+ ViewModelProvider(
+ requireActivity(),
+ injector.getClockSettingsViewModelFactory(
+ context = context,
+ registry = registry,
+ ),
+ )
+ .get(),
this@ClockSettingsFragment,
)
}
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 11e0273..54aaec3 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -17,64 +17,174 @@
import android.content.Context
import android.graphics.Color
+import androidx.core.graphics.ColorUtils
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.color.ui.viewmodel.ColorOptionViewModel
import com.android.wallpaper.R
+import kotlin.math.abs
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
/** View model for the clock settings screen. */
-class ClockSettingsViewModel(val context: Context, val interactor: ClockPickerInteractor) {
+class ClockSettingsViewModel
+private constructor(context: Context, private val interactor: ClockPickerInteractor) : ViewModel() {
enum class Tab {
COLOR,
SIZE,
}
- val colorOptions: Flow<List<ColorOptionViewModel>> =
- interactor.selectedClockColor.map { selectedColor ->
- buildList {
- // TODO (b/241966062) Change design of the placeholder for default theme color
- add(
- ColorOptionViewModel(
- color0 = Color.TRANSPARENT,
- color1 = Color.TRANSPARENT,
- color2 = Color.TRANSPARENT,
- color3 = Color.TRANSPARENT,
- contentDescription = "description",
- isSelected = selectedColor == null,
- onClick =
- if (selectedColor == null) {
- null
- } else {
- { interactor.setClockColor(null) }
- },
- )
- )
- COLOR_LIST.forEach { color ->
+ private val helperColorHsl: FloatArray by lazy { FloatArray(3) }
+
+ /**
+ * Saturation level of the current selected color. Note that this can be null if the selected
+ * color is null, which means that the clock color respects the system theme color. In this
+ * case, the saturation level is no longer needed since we do not allow changing saturation
+ * level of the system theme color.
+ */
+ private val saturationLevel: Flow<Float?> =
+ interactor.selectedClockColor
+ .map { selectedColor ->
+ if (selectedColor == null) {
+ null
+ } else {
+ ColorUtils.colorToHSL(selectedColor, helperColorHsl)
+ helperColorHsl[1]
+ }
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+
+ /**
+ * When the selected clock color is null, it means that the clock will respect the system theme
+ * color. And we no longer need the slider, which determines the saturation level of the clock's
+ * overridden color.
+ */
+ val isSliderEnabled: Flow<Boolean> = saturationLevel.map { it != null }
+
+ /**
+ * Slide progress from 0 to 100. Note that this can be null if the selected color is null, which
+ * means that the clock color respects the system theme color. In this case, the saturation
+ * level is no longer needed since we do not allow changing saturation level of the system theme
+ * color.
+ */
+ val sliderProgress: Flow<Int?> =
+ saturationLevel.map { saturation -> saturation?.let { (it * 100).roundToInt() } }
+
+ fun onSliderProgressChanged(progress: Int) {
+ val saturation = progress / 100f
+ val selectedOption = colorOptions.value.find { option -> option.isSelected }
+ selectedOption?.let { option ->
+ ColorUtils.colorToHSL(option.color0, helperColorHsl)
+ helperColorHsl[1] = saturation
+ interactor.setClockColor(ColorUtils.HSLToColor(helperColorHsl))
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val colorOptions: StateFlow<List<ColorOptionViewModel>> =
+ interactor.selectedClockColor
+ .mapLatest { selectedColor ->
+ // 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 {
+ // TODO (b/241966062) Change design of the placeholder for default theme color
add(
ColorOptionViewModel(
- color0 = color,
- color1 = color,
- color2 = color,
- color3 = color,
- contentDescription = "description",
- isSelected = selectedColor == color,
+ color0 = Color.TRANSPARENT,
+ color1 = Color.TRANSPARENT,
+ color2 = Color.TRANSPARENT,
+ color3 = Color.TRANSPARENT,
+ contentDescription =
+ context.getString(
+ R.string.content_description_color_option,
+ ),
+ isSelected = selectedColor == null,
onClick =
- if (selectedColor == color) {
+ if (selectedColor == null) {
null
} else {
- { interactor.setClockColor(color) }
+ { interactor.setClockColor(null) }
},
)
)
+
+ if (selectedColor != null) {
+ ColorUtils.colorToHSL(selectedColor, helperColorHsl)
+ }
+
+ val selectedColorPosition =
+ if (selectedColor != null) {
+ getSelectedColorPosition(helperColorHsl)
+ } else {
+ -1
+ }
+
+ COLOR_LIST_HSL.forEachIndexed { index, colorHSL ->
+ val color = ColorUtils.HSLToColor(colorHSL)
+ val isSelected = selectedColorPosition == index
+ val colorToSet: Int by lazy {
+ val saturation =
+ if (selectedColor != null) {
+ helperColorHsl[1]
+ } else {
+ colorHSL[1]
+ }
+ ColorUtils.HSLToColor(
+ listOf(
+ colorHSL[0],
+ saturation,
+ colorHSL[2],
+ )
+ .toFloatArray()
+ )
+ }
+ add(
+ ColorOptionViewModel(
+ color0 = color,
+ color1 = color,
+ color2 = color,
+ color3 = color,
+ contentDescription =
+ context.getString(
+ R.string.content_description_color_option,
+ index,
+ ),
+ isSelected = isSelected,
+ onClick =
+ if (isSelected) {
+ null
+ } else {
+ { interactor.setClockColor(colorToSet) }
+ },
+ )
+ )
+ }
}
}
- }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = emptyList(),
+ )
val selectedClockSize: Flow<ClockSize> = interactor.selectedClockSize
@@ -113,6 +223,32 @@
companion object {
// TODO (b/241966062) The color integers here are temporary for dev purposes. We need to
// finalize the overridden colors.
- val COLOR_LIST = listOf(-2563329, -8775, -1777665, -5442872)
+ val COLOR_LIST_HSL =
+ listOf(
+ arrayOf(225f, 0.65f, 0.74f).toFloatArray(),
+ arrayOf(30f, 0.65f, 0.74f).toFloatArray(),
+ arrayOf(249f, 0.65f, 0.74f).toFloatArray(),
+ arrayOf(144f, 0.65f, 0.74f).toFloatArray(),
+ )
+
+ val COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
+
+ fun getSelectedColorPosition(selectedColorHsl: FloatArray): Int {
+ return COLOR_LIST_HSL.withIndex().minBy { abs(it.value[0] - selectedColorHsl[0]) }.index
+ }
+ }
+
+ class Factory(
+ private val context: Context,
+ private val interactor: ClockPickerInteractor,
+ ) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return ClockSettingsViewModel(
+ context = context,
+ interactor = interactor,
+ )
+ as T
+ }
}
}
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
index a484027..72f5055 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
@@ -7,10 +7,12 @@
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.shared.ClockSize
import com.android.wallpaper.testing.collectLastValue
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
@@ -35,7 +37,11 @@
Dispatchers.setMain(testDispatcher)
context = InstrumentationRegistry.getInstrumentation().targetContext
underTest =
- ClockSettingsViewModel(context, ClockPickerInteractor(FakeClockPickerRepository()))
+ ClockSettingsViewModel.Factory(
+ context = context,
+ interactor = ClockPickerInteractor(FakeClockPickerRepository()),
+ )
+ .create(ClockSettingsViewModel::class.java)
}
@After
@@ -46,15 +52,46 @@
@Test
fun setClockColor() = runTest {
val observedClockColorOptions = collectLastValue(underTest.colorOptions)
+ // 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()
observedClockColorOptions()!![1].onClick?.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()
}
@Test
+ fun setClockSaturation() = runTest {
+ val observedClockColorOptions = collectLastValue(underTest.colorOptions)
+ val observedIsSliderEnabled = collectLastValue(underTest.isSliderEnabled)
+ val observedSliderProgress = collectLastValue(underTest.sliderProgress)
+ // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
+ advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ assertThat(observedIsSliderEnabled()).isFalse()
+ assertThat(observedSliderProgress()).isNull()
+
+ observedClockColorOptions()!![1].onClick?.invoke()
+ // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
+ advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ assertThat(observedIsSliderEnabled()).isTrue()
+ val targetProgress = 99
+ underTest.onSliderProgressChanged(targetProgress)
+ advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ assertThat(observedClockColorOptions()!![1].isSelected).isTrue()
+ assertThat(observedSliderProgress())
+ .isIn(
+ Range.closed(
+ targetProgress - 1,
+ targetProgress + 1,
+ )
+ )
+ }
+
+ @Test
fun setClockSize() = runTest {
val observedClockSize = collectLastValue(underTest.selectedClockSize)
underTest.setClockSize(ClockSize.DYNAMIC)
diff --git a/tests/src/com/android/customization/testing/TestCustomizationInjector.kt b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
index 71b2028..2627f92 100644
--- a/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
+++ b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
@@ -14,6 +14,7 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
@@ -53,6 +54,7 @@
private var colorPickerInteractor: ColorPickerInteractor? = null
private var colorPickerViewModelFactory: ColorPickerViewModel.Factory? = null
private var clockCarouselViewModel: ClockCarouselViewModel? = null
+ private var clockSettingsViewModelFactory: ClockSettingsViewModel.Factory? = null
override fun getCustomizationPreferences(context: Context): CustomizationPreferences {
return customizationPreferences
@@ -202,6 +204,18 @@
?: ClockViewFactory(context, registry).also { clockViewFactory = it }
}
+ override fun getClockSettingsViewModelFactory(
+ context: Context,
+ registry: ClockRegistry
+ ): ClockSettingsViewModel.Factory {
+ return clockSettingsViewModelFactory
+ ?: ClockSettingsViewModel.Factory(
+ context,
+ getClockPickerInteractor(context, registry),
+ )
+ .also { clockSettingsViewModelFactory = it }
+ }
+
companion object {
private const val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER = 1
}