Clock customization screen (1/3)
Implemented the preview and apply experiences for the clock
customization screen.
Test: Manually tested. See bug.
Bug: 350718184
Flag: com.android.systemui.shared.new_customization_picker_ui
Change-Id: I5c132ca8d4e73df26b76b04f0a0a191d88624520
diff --git a/res/layout/clock_host_view.xml b/res/layout/clock_host_view.xml
new file mode 100644
index 0000000..3f768e2
--- /dev/null
+++ b/res/layout/clock_host_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<com.android.customization.picker.clock.ui.view.ClockHostView2
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/clock_host_view"
+ android:importantForAccessibility="noHideDescendants"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
\ No newline at end of file
diff --git a/res/layout/floating_sheet_clock.xml b/res/layout/floating_sheet_clock.xml
index 9c9a0ac..9ca8f1a 100644
--- a/res/layout/floating_sheet_clock.xml
+++ b/res/layout/floating_sheet_clock.xml
@@ -125,6 +125,7 @@
android:paddingHorizontal="@dimen/floating_sheet_content_horizontal_padding">
<LinearLayout
+ android:id="@+id/clock_size_option_dynamic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
@@ -150,6 +151,7 @@
</LinearLayout>
<LinearLayout
+ android:id="@+id/clock_size_option_small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 2790d1f..b634df0 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -41,7 +41,7 @@
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
import com.android.customization.picker.clock.ui.view.ClockViewFactory
-import com.android.customization.picker.clock.ui.view.ClockViewFactoryImpl
+import com.android.customization.picker.clock.ui.view.ThemePickerClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
@@ -72,7 +72,6 @@
import com.android.wallpaper.picker.di.modules.BackgroundDispatcher
import com.android.wallpaper.picker.di.modules.MainDispatcher
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
-import com.android.wallpaper.util.ScreenSizeCalculator
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Singleton
@@ -172,9 +171,7 @@
return fragmentFactory ?: ThemePickerFragmentFactory().also { fragmentFactory }
}
- override fun getSnapshotRestorers(
- context: Context,
- ): Map<Int, SnapshotRestorer> {
+ override fun getSnapshotRestorers(context: Context): Map<Int, SnapshotRestorer> {
return super<WallpaperPicker2Injector>.getSnapshotRestorers(context).toMutableMap().apply {
this[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
keyguardQuickAffordanceSnapshotRestorer.get()
@@ -220,7 +217,7 @@
}
fun getNotificationSectionViewModelFactory(
- context: Context,
+ context: Context
): NotificationSectionViewModel.Factory {
return notificationSectionViewModelFactory
?: NotificationSectionViewModel.Factory(
@@ -230,9 +227,7 @@
.also { notificationSectionViewModelFactory = it }
}
- private fun getNotificationsInteractor(
- context: Context,
- ): NotificationSettingsInteractor {
+ private fun getNotificationsInteractor(context: Context): NotificationSettingsInteractor {
return notificationSettingsInteractor
?: NotificationSettingsInteractor(
repository =
@@ -241,7 +236,7 @@
backgroundDispatcher = bgDispatcher,
secureSettingsRepository = secureSettingsRepository.get(),
systemSettingsRepository = systemSettingsRepository.get(),
- ),
+ )
)
.also { notificationSettingsInteractor = it }
}
@@ -249,10 +244,7 @@
private fun getNotificationsSnapshotRestorer(context: Context): NotificationsSnapshotRestorer {
return notificationsSnapshotRestorer
?: NotificationsSnapshotRestorer(
- interactor =
- getNotificationsInteractor(
- context = context,
- ),
+ interactor = getNotificationsInteractor(context = context),
backgroundScope = bgScope,
)
.also { notificationsSnapshotRestorer = it }
@@ -276,10 +268,8 @@
override fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory {
return clockViewFactory
- ?: ClockViewFactoryImpl(
- activity.applicationContext,
- ScreenSizeCalculator.getInstance()
- .getScreenSize(activity.windowManager.defaultDisplay),
+ ?: ThemePickerClockViewFactory(
+ activity,
WallpaperManager.getInstance(activity.applicationContext),
clockRegistry.get(),
)
@@ -299,7 +289,7 @@
override fun getWallpaperColorResources(
wallpaperColors: WallpaperColors,
- context: Context
+ context: Context,
): WallpaperColorResources {
return ThemedWallpaperColorResources(wallpaperColors, secureSettingsRepository.get())
}
@@ -321,9 +311,7 @@
}
}
- fun getDarkModeSnapshotRestorer(
- context: Context,
- ): DarkModeSnapshotRestorer {
+ fun getDarkModeSnapshotRestorer(context: Context): DarkModeSnapshotRestorer {
val appContext = context.applicationContext
return darkModeSnapshotRestorer
?: DarkModeSnapshotRestorer(
@@ -334,9 +322,7 @@
.also { darkModeSnapshotRestorer = it }
}
- protected fun getThemedIconSnapshotRestorer(
- context: Context,
- ): ThemedIconSnapshotRestorer {
+ protected fun getThemedIconSnapshotRestorer(context: Context): ThemedIconSnapshotRestorer {
val optionProvider = ThemedIconSwitchProvider.getInstance(context)
return themedIconSnapshotRestorer
?: ThemedIconSnapshotRestorer(
@@ -351,10 +337,9 @@
protected fun getThemedIconInteractor(): ThemedIconInteractor {
return themedIconInteractor
- ?: ThemedIconInteractor(
- repository = ThemeIconRepository(),
- )
- .also { themedIconInteractor = it }
+ ?: ThemedIconInteractor(repository = ThemeIconRepository()).also {
+ themedIconInteractor = it
+ }
}
override fun getClockSettingsViewModelFactory(
@@ -375,9 +360,7 @@
.also { clockSettingsViewModelFactory = it }
}
- fun getGridScreenViewModelFactory(
- context: Context,
- ): ViewModelProvider.Factory {
+ fun getGridScreenViewModelFactory(context: Context): ViewModelProvider.Factory {
return gridScreenViewModelFactory
?: GridScreenViewModel.Factory(
context = context,
@@ -404,14 +387,11 @@
.also { gridInteractor = it }
}
- private fun getGridSnapshotRestorer(
- context: Context,
- ): GridSnapshotRestorer {
+ private fun getGridSnapshotRestorer(context: Context): GridSnapshotRestorer {
return gridSnapshotRestorer
- ?: GridSnapshotRestorer(
- interactor = getGridInteractor(context),
- )
- .also { gridSnapshotRestorer = it }
+ ?: GridSnapshotRestorer(interactor = getGridInteractor(context)).also {
+ gridSnapshotRestorer = it
+ }
}
override fun isCurrentSelectedColorPreset(context: Context): Boolean {
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
index 49227ee..42eed34 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -84,6 +84,24 @@
setClockOption(ClockSnapshotModel(clockSize = size))
}
+ suspend fun applyClock(
+ clockId: String?,
+ size: ClockSize?,
+ selectedColorId: String?,
+ @IntRange(from = 0, to = 100) colorToneProgress: Int?,
+ @ColorInt seedColor: Int?,
+ ) {
+ setClockOption(
+ ClockSnapshotModel(
+ clockId = clockId,
+ clockSize = size,
+ selectedColorId = selectedColorId,
+ colorToneProgress = colorToneProgress,
+ seedColor = seedColor,
+ )
+ )
+ }
+
private suspend fun setClockOption(clockSnapshotModel: ClockSnapshotModel) {
// [ClockCarouselViewModel] is monitoring the [ClockPickerInteractor.setSelectedClock] job,
// so it needs to finish last.
@@ -94,7 +112,7 @@
repository.setClockColor(
selectedColorId = clockSnapshotModel.selectedColorId,
colorToneProgress = clockSnapshotModel.colorToneProgress,
- seedColor = clockSnapshotModel.seedColor
+ seedColor = clockSnapshotModel.seedColor,
)
}
clockSnapshotModel.clockId?.let { repository.setSelectedClock(it) }
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockHostView2.kt b/src/com/android/customization/picker/clock/ui/view/ClockHostView2.kt
new file mode 100644
index 0000000..d5b317e
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/view/ClockHostView2.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.clock.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
+import android.widget.FrameLayout
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.util.ScreenSizeCalculator
+
+/**
+ * Parent view for the clock view. We will calculate the current display size and the preview size
+ * and scale down the clock view to fit in the preview.
+ */
+class ClockHostView2(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
+
+ var clockSize: ClockSize = ClockSize.DYNAMIC
+ set(value) {
+ if (field != value) {
+ field = value
+ updatePivotAndScale()
+ invalidate()
+ }
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ updatePivotAndScale()
+ }
+
+ override fun measureChildWithMargins(
+ child: View?,
+ parentWidthMeasureSpec: Int,
+ widthUsed: Int,
+ parentHeightMeasureSpec: Int,
+ heightUsed: Int,
+ ) {
+ val screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display)
+ super.measureChildWithMargins(
+ child,
+ MeasureSpec.makeMeasureSpec(screenSize.x, EXACTLY),
+ widthUsed,
+ MeasureSpec.makeMeasureSpec(screenSize.y, EXACTLY),
+ heightUsed,
+ )
+ }
+
+ private fun updatePivotAndScale() {
+ when (clockSize) {
+ ClockSize.DYNAMIC -> {
+ pivotX = (width / 2).toFloat()
+ pivotY = (height / 2).toFloat()
+ }
+ ClockSize.SMALL -> {
+ pivotX = getCenteredHostViewPivotX(this)
+ pivotY = 0F
+ }
+ }
+ val screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display)
+ val ratio = measuredWidth / screenSize.x.toFloat()
+ scaleX = ratio
+ scaleY = ratio
+ }
+
+ companion object {
+ fun getCenteredHostViewPivotX(hostView: View): Float {
+ return if (hostView.isLayoutRtl) hostView.width.toFloat() else 0F
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
deleted file mode 100644
index 8e5992e..0000000
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.clock.ui.view
-
-import android.view.View
-import androidx.annotation.ColorInt
-import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.plugins.clocks.ClockController
-
-interface ClockViewFactory {
-
- fun getController(clockId: String): ClockController
-
- /**
- * Reset the large view to its initial state when getting the view. This is because some view
- * configs, e.g. animation state, might change during the reuse of the clock view in the app.
- */
- fun getLargeView(clockId: String): View
-
- /**
- * Reset the small view to its initial state when getting the view. This is because some view
- * configs, e.g. translation X, might change during the reuse of the clock view in the app.
- */
- fun getSmallView(clockId: String): View
-
- /** Enables or disables the reactive swipe interaction */
- fun setReactiveTouchInteractionEnabled(clockId: String, enable: Boolean)
-
- fun updateColorForAllClocks(@ColorInt seedColor: Int?)
-
- fun updateColor(clockId: String, @ColorInt seedColor: Int?)
-
- fun updateRegionDarkness()
-
- fun updateTimeFormat(clockId: String)
-
- fun registerTimeTicker(owner: LifecycleOwner)
-
- fun onDestroy()
-
- fun unregisterTimeTicker(owner: LifecycleOwner)
-}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
similarity index 94%
rename from src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt
rename to src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
index 669c273..1f73727 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -15,10 +15,10 @@
*/
package com.android.customization.picker.clock.ui.view
+import android.app.Activity
import android.app.WallpaperColors
import android.app.WallpaperManager
import android.content.Context
-import android.graphics.Point
import android.graphics.Rect
import android.view.View
import android.widget.FrameLayout
@@ -29,21 +29,27 @@
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.wallpaper.config.BaseFlags
+import com.android.wallpaper.util.ScreenSizeCalculator
import com.android.wallpaper.util.TimeUtils.TimeTicker
import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
/**
* Provide reusable clock view and related util functions.
*
* @property screenSize The Activity or Fragment's window size.
*/
-class ClockViewFactoryImpl(
- private val appContext: Context,
- val screenSize: Point,
+class ThemePickerClockViewFactory
+@Inject
+constructor(
+ activity: Activity,
private val wallpaperManager: WallpaperManager,
private val registry: ClockRegistry,
) : ClockViewFactory {
+ private val appContext = activity.applicationContext
private val resources = appContext.resources
+ private val screenSize =
+ ScreenSizeCalculator.getInstance().getScreenSize(activity.windowManager.defaultDisplay)
private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap()
private val clockControllers: ConcurrentHashMap<String, ClockController> = ConcurrentHashMap()
private val smallClockFrames: HashMap<String, FrameLayout> = HashMap()
@@ -95,7 +101,7 @@
FrameLayout.LayoutParams.WRAP_CONTENT,
resources.getDimensionPixelSize(
com.android.systemui.customization.R.dimen.small_clock_height
- )
+ ),
)
layoutParams.topMargin = getSmallClockTopMargin()
layoutParams.marginStart = getSmallClockStartPadding()
@@ -120,7 +126,7 @@
}
override fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
- clockControllers[clockId]?.events?.onSeedColorChanged(seedColor)
+ getController(clockId).events.onSeedColorChanged(seedColor)
}
override fun updateRegionDarkness() {
diff --git a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
index 12733c5..a8d06a5 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
@@ -34,6 +34,7 @@
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder
import com.android.customization.picker.color.ui.view.ColorOptionIconView
import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
@@ -95,13 +96,10 @@
initStyleList(appContext, clockStyleAdapter)
}
- // Cloc color
+ // Clock color
val clockColorContent = view.requireViewById<View>(R.id.clock_floating_sheet_color_content)
val clockColorAdapter =
- createClockColorOptionItemAdapter(
- view.resources.configuration.uiMode,
- lifecycleOwner,
- )
+ createClockColorOptionItemAdapter(view.resources.configuration.uiMode, lifecycleOwner)
val clockColorList =
view.requireViewById<RecyclerView>(R.id.clock_color_list).apply {
initColorList(appContext, clockColorAdapter)
@@ -117,16 +115,14 @@
override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
- override fun onStopTrackingTouch(seekBar: SeekBar?) {
- seekBar?.progress?.let {
- lifecycleOwner.lifecycleScope.launch { viewModel.onSliderProgressStop(it) }
- }
- }
+ override fun onStopTrackingTouch(seekBar: SeekBar?) = Unit
}
)
// Clock size
val clockSizeContent = view.requireViewById<View>(R.id.clock_floating_sheet_size_content)
+ val clockSizeOptionDynamic = view.requireViewById<View>(R.id.clock_size_option_dynamic)
+ val clockSizeOptionSmall = view.requireViewById<View>(R.id.clock_size_option_small)
view.doOnLayout {
if (_clockFloatingSheetHeights.value == null) {
@@ -199,7 +195,7 @@
}
launch {
- viewModel.sliderProgress.collect { progress ->
+ viewModel.previewingSliderProgress.collect { progress ->
clockColorSlider.setProgress(progress, true)
}
}
@@ -211,6 +207,31 @@
if (isEnabled) SLIDER_ENABLED_ALPHA else SLIDER_DISABLED_ALPHA
}
}
+
+ launch {
+ viewModel.sizeOptions.collect { sizeOptions ->
+ sizeOptions.forEach { option ->
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ option.onClicked.collect { onClicked ->
+ when (option.size) {
+ ClockSize.DYNAMIC ->
+ clockSizeOptionDynamic.setOnClickListener {
+ onClicked?.invoke()
+ }
+ ClockSize.SMALL ->
+ clockSizeOptionSmall.setOnClickListener {
+ onClicked?.invoke()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
}
}
@@ -223,22 +244,13 @@
lifecycleOwner = lifecycleOwner,
bindIcon = { foregroundView: View, drawable: Drawable ->
(foregroundView as ImageView).setImageDrawable(drawable)
- }
+ },
)
- private fun RecyclerView.initStyleList(
- context: Context,
- adapter: OptionItemAdapter<Drawable>,
- ) {
+ private fun RecyclerView.initStyleList(context: Context, adapter: OptionItemAdapter<Drawable>) {
apply {
this.adapter = adapter
- layoutManager =
- GridLayoutManager(
- context,
- 2,
- GridLayoutManager.HORIZONTAL,
- false,
- )
+ layoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
addItemDecoration(
DoubleRowListItemSpacing(
context.resources.getDimensionPixelSize(
@@ -257,7 +269,7 @@
private fun createClockColorOptionItemAdapter(
uiMode: Int,
- lifecycleOwner: LifecycleOwner
+ lifecycleOwner: LifecycleOwner,
): OptionItemAdapter<ColorOptionIconViewModel> =
OptionItemAdapter(
layoutResourceId = R.layout.color_option,
@@ -267,7 +279,7 @@
val night =
uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
colorOptionIconView?.let { ColorOptionIconBinder.bind(it, colorIcon, night) }
- }
+ },
)
private fun RecyclerView.initColorList(
@@ -276,13 +288,7 @@
) {
apply {
this.adapter = adapter
- layoutManager =
- GridLayoutManager(
- context,
- 2,
- GridLayoutManager.HORIZONTAL,
- false,
- )
+ layoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
addItemDecoration(
DoubleRowListItemSpacing(
context.resources.getDimensionPixelSize(
@@ -300,10 +306,6 @@
}
private fun getDrawable(context: Context, @DrawableRes res: Int): Drawable? {
- return ResourcesCompat.getDrawable(
- context.resources,
- res,
- null,
- )
+ return ResourcesCompat.getDrawable(context.resources, res, null)
}
}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index 7ddafe0..e223ebc 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -17,6 +17,7 @@
package com.android.wallpaper.customization.ui.binder
import android.view.View
+import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
@@ -25,8 +26,12 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.customization.picker.clock.ui.view.ClockHostView2
+import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.grid.ui.binder.GridIconViewBinder
import com.android.themepicker.R
+import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption
import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
@@ -40,6 +45,8 @@
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@Singleton
@@ -123,27 +130,18 @@
optionsViewModel.keyguardQuickAffordancePickerViewModel2.summary.collect {
summary ->
optionShortcutDescription?.let {
- TextViewBinder.bind(
- view = it,
- viewModel = summary.description,
- )
+ TextViewBinder.bind(view = it, viewModel = summary.description)
}
summary.icon1?.let { icon ->
optionShortcutIcon1?.let {
- IconViewBinder.bind(
- view = it,
- viewModel = icon,
- )
+ IconViewBinder.bind(view = it, viewModel = icon)
}
}
optionShortcutIcon1?.isVisible = summary.icon1 != null
summary.icon2?.let { icon ->
optionShortcutIcon2?.let {
- IconViewBinder.bind(
- view = it,
- viewModel = icon,
- )
+ IconViewBinder.bind(view = it, viewModel = icon)
}
}
optionShortcutIcon2?.isVisible = summary.icon2 != null
@@ -170,16 +168,13 @@
}
gridOption.payload?.let { gridIconViewModel ->
optionShapeAndGridIcon?.let {
- GridIconViewBinder.bind(
- view = it,
- viewModel = gridIconViewModel,
- )
+ GridIconViewBinder.bind(view = it, viewModel = gridIconViewModel)
}
// TODO(b/363018910): Use ColorUpdateBinder to update color
optionShapeAndGridIcon?.setColorFilter(
ContextCompat.getColor(
view.context,
- com.android.wallpaper.R.color.system_on_surface_variant
+ com.android.wallpaper.R.color.system_on_surface_variant,
)
)
}
@@ -232,4 +227,61 @@
)
}
}
+
+ override fun bindClockPreview(
+ clockHostView: View,
+ viewModel: CustomizationPickerViewModel2,
+ lifecycleOwner: LifecycleOwner,
+ clockViewFactory: ClockViewFactory,
+ ) {
+ clockHostView as ClockHostView2
+ val clockPickerViewModel =
+ (viewModel.customizationOptionsViewModel as ThemePickerCustomizationOptionsViewModel)
+ .clockPickerViewModel
+
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ combine(
+ clockPickerViewModel.previewingClock.filterNotNull(),
+ clockPickerViewModel.previewingClockSize,
+ ) { clock, size ->
+ clock to size
+ }
+ .collect { (clock, size) ->
+ clockHostView.removeAllViews()
+ if (BaseFlags.get().isClockReactiveVariantsEnabled()) {
+ clockViewFactory.setReactiveTouchInteractionEnabled(
+ clock.clockId,
+ true,
+ )
+ }
+ val clockView =
+ when (size) {
+ ClockSize.DYNAMIC ->
+ clockViewFactory.getLargeView(clock.clockId)
+ ClockSize.SMALL -> clockViewFactory.getSmallView(clock.clockId)
+ }
+ // The clock view might still be attached to an existing parent. Detach
+ // before adding to another parent.
+ (clockView.parent as? ViewGroup)?.removeView(clockView)
+ clockHostView.addView(clockView)
+ clockHostView.clockSize = size
+ }
+ }
+
+ launch {
+ combine(
+ clockPickerViewModel.previewingSeedColor,
+ clockPickerViewModel.previewingClock,
+ ) { color, clock ->
+ color to clock
+ }
+ .collect { (color, clock) ->
+ clockViewFactory.updateColor(clock.clockId, color)
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerToolbarBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerToolbarBinder.kt
index 088a741..91705dc 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerToolbarBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerToolbarBinder.kt
@@ -56,9 +56,7 @@
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.onApplyButtonClicked.collect { onApplyButtonClicked ->
- applyButton.setOnClickListener {
- onApplyButtonClicked?.invoke()?.let { viewModel.deselectOption() }
- }
+ applyButton.setOnClickListener { onApplyButtonClicked?.invoke() }
}
}
diff --git a/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt b/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
index 176c757..7a73b7d 100644
--- a/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
+++ b/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
@@ -18,6 +18,7 @@
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import com.android.themepicker.R
@@ -54,11 +55,7 @@
layoutInflater: LayoutInflater,
): List<Pair<CustomizationOptionUtil.CustomizationOption, View>> {
val defaultOptionEntries =
- defaultCustomizationOptionUtil.getOptionEntries(
- screen,
- optionContainer,
- layoutInflater,
- )
+ defaultCustomizationOptionUtil.getOptionEntries(screen, optionContainer, layoutInflater)
return when (screen) {
LOCK_SCREEN ->
buildList {
@@ -76,7 +73,7 @@
layoutInflater.inflate(
R.layout.customization_option_entry_keyguard_quick_affordance,
optionContainer,
- false
+ false,
)
)
add(
@@ -129,7 +126,7 @@
override fun initFloatingSheet(
bottomSheetContainer: FrameLayout,
- layoutInflater: LayoutInflater
+ layoutInflater: LayoutInflater,
): Map<CustomizationOptionUtil.CustomizationOption, View> {
val map =
defaultCustomizationOptionUtil.initFloatingSheet(bottomSheetContainer, layoutInflater)
@@ -142,7 +139,7 @@
bottomSheetContainer,
layoutInflater,
)
- .also { bottomSheetContainer.addView(it) }
+ .also { bottomSheetContainer.addView(it) },
)
put(
ThemePickerLockCustomizationOption.SHORTCUTS,
@@ -151,7 +148,7 @@
bottomSheetContainer,
layoutInflater,
)
- .also { bottomSheetContainer.addView(it) }
+ .also { bottomSheetContainer.addView(it) },
)
put(
ThemePickerHomeCustomizationOption.COLORS,
@@ -160,7 +157,7 @@
bottomSheetContainer,
layoutInflater,
)
- .also { bottomSheetContainer.addView(it) }
+ .also { bottomSheetContainer.addView(it) },
)
put(
ThemePickerHomeCustomizationOption.APP_SHAPE_AND_GRID,
@@ -169,11 +166,20 @@
bottomSheetContainer,
layoutInflater,
)
- .also { bottomSheetContainer.addView(it) }
+ .also { bottomSheetContainer.addView(it) },
)
}
}
+ override fun createClockPreviewAndAddToParent(
+ parentView: ViewGroup,
+ layoutInflater: LayoutInflater,
+ ): View? {
+ val clockHostView = layoutInflater.inflate(R.layout.clock_host_view, parentView, false)
+ parentView.addView(clockHostView)
+ return clockHostView
+ }
+
private fun inflateFloatingSheet(
option: CustomizationOptionUtil.CustomizationOption,
bottomSheetContainer: FrameLayout,
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
index 034465e..6740b3b 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
@@ -21,11 +21,9 @@
import androidx.core.graphics.ColorUtils
import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.module.logging.ThemesUserEventLogger
-import com.android.customization.module.logging.ThemesUserEventLogger.Companion.NULL_SEED_COLOR
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
-import com.android.customization.picker.clock.shared.toClockSizeForLogging
import com.android.customization.picker.clock.ui.viewmodel.ClockColorViewModel
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.shared.model.ColorOptionModel
@@ -45,7 +43,6 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,12 +51,11 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/** View model for the clock customization screen. */
class ClockPickerViewModel
@@ -82,6 +78,7 @@
private val colorMap = ClockColorViewModel.getPresetColorMap(context.resources)
+ // Tabs
private val _selectedTab = MutableStateFlow(Tab.STYLE)
val selectedTab: StateFlow<Tab> = _selectedTab.asStateFlow()
val tabs: Flow<List<FloatingToolbarTabViewModel>> =
@@ -93,7 +90,7 @@
contentDescription = Text.Resource(R.string.clock_style),
),
context.getString(R.string.clock_style),
- it == Tab.STYLE
+ it == Tab.STYLE,
) {
_selectedTab.value = Tab.STYLE
},
@@ -103,7 +100,7 @@
contentDescription = Text.Resource(R.string.clock_color),
),
context.getString(R.string.clock_color),
- it == Tab.COLOR
+ it == Tab.COLOR,
) {
_selectedTab.value = Tab.COLOR
},
@@ -113,13 +110,21 @@
contentDescription = Text.Resource(R.string.clock_size),
),
context.getString(R.string.clock_size),
- it == Tab.SIZE
+ it == Tab.SIZE,
) {
_selectedTab.value = Tab.SIZE
},
)
}
+ // Clock style
+ private val overridingClock = MutableStateFlow<ClockMetadataModel?>(null)
+ val previewingClock =
+ combine(overridingClock, clockPickerInteractor.selectedClock) {
+ overridingClock,
+ selectedClock ->
+ overridingClock ?: selectedClock
+ }
@OptIn(ExperimentalCoroutinesApi::class)
val clockStyleOptions: StateFlow<List<OptionItemViewModel<Drawable>>> =
clockPickerInteractor.allClocks
@@ -128,8 +133,8 @@
delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
allClocks.map { clockModel ->
val isSelectedFlow =
- clockPickerInteractor.selectedClock
- .map { clockModel.clockId == it.clockId }
+ previewingClock
+ .map { it.clockId == clockModel.clockId }
.stateIn(viewModelScope)
val contentDescription =
resources.getString(
@@ -147,13 +152,7 @@
if (isSelected) {
null
} else {
- {
- viewModelScope.launch {
- clockPickerInteractor.setSelectedClock(
- clockModel.clockId
- )
- }
- }
+ { overridingClock.value = clockModel }
}
},
)
@@ -165,70 +164,87 @@
.flowOn(backgroundDispatcher.limitedParallelism(1))
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
- private var setSelectedClockJob: Job? = null
-
- fun setSelectedClock(clockId: String) {
- setSelectedClockJob?.cancel()
- setSelectedClockJob =
- viewModelScope.launch(backgroundDispatcher) {
- clockPickerInteractor.setSelectedClock(clockId)
- logger.logClockApplied(clockId)
- }
+ // Clock size
+ private val overridingClockSize = MutableStateFlow<ClockSize?>(null)
+ val previewingClockSize =
+ combine(overridingClockSize, clockPickerInteractor.selectedClockSize) {
+ overridingClockSize,
+ selectedClockSize ->
+ overridingClockSize ?: selectedClockSize
+ }
+ val sizeOptions = flow {
+ emit(
+ listOf(
+ ClockSizeOptionViewModel(
+ ClockSize.DYNAMIC,
+ previewingClockSize.map { it == ClockSize.DYNAMIC }.stateIn(viewModelScope),
+ previewingClockSize
+ .map {
+ if (it == ClockSize.DYNAMIC) {
+ null
+ } else {
+ { overridingClockSize.value = ClockSize.DYNAMIC }
+ }
+ }
+ .stateIn(viewModelScope),
+ ),
+ ClockSizeOptionViewModel(
+ ClockSize.SMALL,
+ previewingClockSize.map { it == ClockSize.SMALL }.stateIn(viewModelScope),
+ previewingClockSize
+ .map {
+ if (it == ClockSize.SMALL) {
+ null
+ } else {
+ { overridingClockSize.value = ClockSize.SMALL }
+ }
+ }
+ .stateIn(viewModelScope),
+ ),
+ )
+ )
}
- private val selectedColorId: StateFlow<String?> =
- clockPickerInteractor.selectedColorId.stateIn(viewModelScope, SharingStarted.Eagerly, null)
+ // Clock color
+ // 0 - 100
+ private val overridingClockColorId = MutableStateFlow<String?>(null)
+ private val previewingClockColorId =
+ combine(overridingClockColorId, clockPickerInteractor.selectedColorId) {
+ overridingClockColorId,
+ selectedColorId ->
+ overridingClockColorId ?: selectedColorId
+ }
- private val sliderColorToneProgress =
- MutableStateFlow(ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS)
+ private val overridingSliderProgress = MutableStateFlow<Int?>(null)
+ val previewingSliderProgress: Flow<Int> =
+ combine(overridingSliderProgress, clockPickerInteractor.colorToneProgress) {
+ overridingSliderProgress,
+ colorToneProgress ->
+ overridingSliderProgress ?: colorToneProgress
+ }
val isSliderEnabled: Flow<Boolean> =
- combine(clockPickerInteractor.selectedClock, clockPickerInteractor.selectedColorId) {
- clock,
- colorId ->
- if (colorId == null) {
- false
- } else {
- clock.isReactiveToTone
- }
+ combine(previewingClock, previewingClockColorId) { clock, clockColorId ->
+ // clockColorId null means clock color is the system theme color, thus no slider
+ clock.isReactiveToTone && clockColorId != null
}
.distinctUntilChanged()
- val sliderProgress: Flow<Int> =
- merge(clockPickerInteractor.colorToneProgress, sliderColorToneProgress)
- private val _seedColor: MutableStateFlow<Int?> = MutableStateFlow(null)
- val seedColor: Flow<Int?> = merge(clockPickerInteractor.seedColor, _seedColor)
-
- /**
- * The slider color tone updates are quick. Do not set color tone and the blended color to the
- * settings until [onSliderProgressStop] is called. Update to a locally cached temporary
- * [sliderColorToneProgress] and [_seedColor] instead.
- */
fun onSliderProgressChanged(progress: Int) {
- sliderColorToneProgress.value = progress
- val selectedColorId = selectedColorId.value ?: return
- val clockColorViewModel = colorMap[selectedColorId] ?: return
- _seedColor.value =
- blendColorWithTone(
- color = clockColorViewModel.color,
- colorTone = clockColorViewModel.getColorTone(progress),
- )
+ overridingSliderProgress.value = progress
}
- suspend fun onSliderProgressStop(progress: Int) {
- val selectedColorId = selectedColorId.value ?: return
- val clockColorViewModel = colorMap[selectedColorId] ?: return
- val seedColor =
- blendColorWithTone(
- color = clockColorViewModel.color,
- colorTone = clockColorViewModel.getColorTone(progress),
- )
- clockPickerInteractor.setClockColor(
- selectedColorId = selectedColorId,
- colorToneProgress = progress,
- seedColor = seedColor,
- )
- logger.logClockColorApplied(seedColor)
- }
+ val previewingSeedColor: Flow<Int?> =
+ combine(previewingClockColorId, previewingSliderProgress) { clockColorId, sliderProgress ->
+ val clockColorViewModel = if (clockColorId == null) null else colorMap[clockColorId]
+ if (clockColorViewModel == null) {
+ null
+ } else {
+ blendColorWithTone(
+ color = clockColorViewModel.color,
+ colorTone = clockColorViewModel.getColorTone(sliderProgress),
+ )
+ }
+ }
val clockColorOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
colorPickerInteractor.colorOptions.map { colorOptions ->
@@ -247,10 +263,9 @@
colorMap.values.forEachIndexed { index, colorModel ->
val isSelectedFlow =
- selectedColorId
+ previewingClockColorId
.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>,
@@ -280,22 +295,9 @@
null
} else {
{
- viewModelScope.launch {
- val seedColor =
- blendColorWithTone(
- color = colorModel.color,
- colorTone =
- colorModel.getColorTone(
- colorToneProgress,
- ),
- )
- clockPickerInteractor.setClockColor(
- selectedColorId = colorModel.colorId,
- colorToneProgress = colorToneProgress,
- seedColor = seedColor,
- )
- logger.logClockColorApplied(seedColor)
- }
+ overridingClockColorId.value = colorModel.colorId
+ overridingSliderProgress.value =
+ ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS
}
}
},
@@ -320,7 +322,7 @@
/** darkTheme= */
true
)
- val isSelectedFlow = selectedColorId.map { it == null }.stateIn(viewModelScope)
+ val isSelectedFlow = previewingClockColorId.map { it == null }.stateIn(viewModelScope)
return OptionItemViewModel<ColorOptionIconViewModel>(
key = MutableStateFlow(key) as StateFlow<String>,
payload =
@@ -343,28 +345,49 @@
null
} else {
{
- viewModelScope.launch {
- clockPickerInteractor.setClockColor(
- selectedColorId = null,
- colorToneProgress =
- ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
- seedColor = null,
- )
- logger.logClockColorApplied(NULL_SEED_COLOR)
- }
+ overridingClockColorId.value = null
+ overridingSliderProgress.value =
+ ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS
}
}
},
)
}
- val selectedClockSize: Flow<ClockSize> = clockPickerInteractor.selectedClockSize
-
- fun setClockSize(size: ClockSize) {
- viewModelScope.launch {
- clockPickerInteractor.setClockSize(size)
- logger.logClockSizeApplied(size.toClockSizeForLogging())
+ val onApply: Flow<(suspend () -> Unit)?> =
+ combine(
+ previewingClock,
+ previewingClockSize,
+ previewingClockColorId,
+ previewingSliderProgress,
+ ) { clock, size, colorId, progress ->
+ {
+ val clockColorViewModel = colorMap[colorId]
+ val seedColor =
+ if (clockColorViewModel != null) {
+ blendColorWithTone(
+ color = clockColorViewModel.color,
+ colorTone = clockColorViewModel.getColorTone(progress),
+ )
+ } else {
+ null
+ }
+ clockPickerInteractor.applyClock(
+ clockId = clock.clockId,
+ size = size,
+ selectedColorId = colorId,
+ colorToneProgress = progress,
+ seedColor = seedColor,
+ )
+ }
}
+
+ fun resetPreview() {
+ overridingClock.value = null
+ overridingClockSize.value = null
+ overridingClockColorId.value = null
+ overridingSliderProgress.value = null
+ _selectedTab.value = Tab.STYLE
}
companion object {
@@ -372,11 +395,7 @@
fun blendColorWithTone(color: Int, colorTone: Double): Int {
ColorUtils.colorToLAB(color, helperColorLab)
- return ColorUtils.LABToColor(
- colorTone,
- helperColorLab[1],
- helperColorLab[2],
- )
+ return ColorUtils.LABToColor(colorTone, helperColorLab[1], helperColorLab[2])
}
const val COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockSizeOptionViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockSizeOptionViewModel.kt
new file mode 100644
index 0000000..de2c54a
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockSizeOptionViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.wallpaper.customization.ui.viewmodel
+
+import com.android.customization.picker.clock.shared.ClockSize
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+data class ClockSizeOptionViewModel(
+ val size: ClockSize,
+ val isSelected: StateFlow<Boolean>,
+ val onClicked: Flow<(() -> Unit)?>,
+)
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/KeyguardQuickAffordancePickerViewModel2.kt b/src/com/android/wallpaper/customization/ui/viewmodel/KeyguardQuickAffordancePickerViewModel2.kt
index 658e435..fd94b78 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/KeyguardQuickAffordancePickerViewModel2.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/KeyguardQuickAffordancePickerViewModel2.kt
@@ -52,7 +52,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
class KeyguardQuickAffordancePickerViewModel2
@AssistedInject
@@ -66,10 +65,7 @@
private val _selectedSlotId = MutableStateFlow<String?>(null)
/** The ID of the selected slot. */
val selectedSlotId: StateFlow<String> =
- combine(
- quickAffordanceInteractor.slots,
- _selectedSlotId,
- ) { slots, selectedSlotIdOrNull ->
+ combine(quickAffordanceInteractor.slots, _selectedSlotId) { slots, selectedSlotIdOrNull ->
if (selectedSlotIdOrNull != null) {
slots.first { slot -> slot.id == selectedSlotIdOrNull }
} else {
@@ -159,12 +155,7 @@
val tabs: Flow<List<FloatingToolbarTabViewModel>> =
slots.map { slotById ->
slotById.values.map {
- FloatingToolbarTabViewModel(
- it.getIcon(),
- it.name,
- it.isSelected,
- it.onClicked,
- )
+ FloatingToolbarTabViewModel(it.getIcon(), it.name, it.isSelected, it.onClicked)
}
}
@@ -173,30 +164,23 @@
* or different affordances in the currently-selected slot or when slot selection changes.
*/
private val selectedAffordanceIds: Flow<Set<String>> =
- combine(
- quickAffordanceInteractor.selections,
- selectedSlotId,
- ) { selections, selectedSlotId ->
+ combine(quickAffordanceInteractor.selections, selectedSlotId) { selections, selectedSlotId
+ ->
selections
.filter { selection -> selection.slotId == selectedSlotId }
.map { selection -> selection.affordanceId }
.toSet()
}
- .shareIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- replay = 1,
- )
+ .shareIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(), replay = 1)
/** The list of all available quick affordances for the selected slot. */
val quickAffordances: Flow<List<OptionItemViewModel<Icon>>> =
quickAffordanceInteractor.affordances.map { affordances ->
val isNoneSelected =
- combine(
+ combine(selectedSlotId, previewingQuickAffordances, selectedAffordanceIds) {
selectedSlotId,
- previewingQuickAffordances,
- selectedAffordanceIds,
- ) { selectedSlotId, selectedQuickAffordances, selectedAffordanceIds ->
+ selectedQuickAffordances,
+ selectedAffordanceIds ->
selectedQuickAffordances[selectedSlotId]?.let {
it == KEYGUARD_QUICK_AFFORDANCE_ID_NONE
} ?: selectedAffordanceIds.isEmpty()
@@ -207,10 +191,7 @@
slotId = selectedSlotId,
isSelected = isNoneSelected,
onSelected =
- combine(
- isNoneSelected,
- selectedSlotId,
- ) { isSelected, selectedSlotId ->
+ combine(isNoneSelected, selectedSlotId) { isSelected, selectedSlotId ->
if (!isSelected) {
{
val newMap =
@@ -222,7 +203,7 @@
} else {
null
}
- }
+ },
)
) +
affordances.map { affordance ->
@@ -248,10 +229,8 @@
isSelected = isSelectedFlow,
onClicked =
if (affordance.isEnabled) {
- combine(
- isSelectedFlow,
- selectedSlotId,
- ) { isSelected, selectedSlotId ->
+ combine(isSelectedFlow, selectedSlotId) { isSelected, selectedSlotId
+ ->
if (!isSelected) {
{
val newMap =
@@ -286,7 +265,7 @@
}
}
- val onApply: Flow<(() -> Unit)?> =
+ val onApply: Flow<(suspend () -> Unit)?> =
previewingQuickAffordances.map {
if (it.isEmpty()) {
null
@@ -295,20 +274,15 @@
it.forEach { entry ->
val slotId = entry.key
val affordanceId = entry.value
- viewModelScope.launch {
- if (slotId == KEYGUARD_QUICK_AFFORDANCE_ID_NONE) {
- quickAffordanceInteractor.unselectAllFromSlot(slotId)
- } else {
- quickAffordanceInteractor.select(
- slotId = slotId,
- affordanceId = affordanceId
- )
- }
- logger.logShortcutApplied(
- shortcut = affordanceId,
- shortcutSlotId = slotId,
+ if (slotId == KEYGUARD_QUICK_AFFORDANCE_ID_NONE) {
+ quickAffordanceInteractor.unselectAllFromSlot(slotId)
+ } else {
+ quickAffordanceInteractor.select(
+ slotId = slotId,
+ affordanceId = affordanceId,
)
}
+ logger.logShortcutApplied(shortcut = affordanceId, shortcutSlotId = slotId)
}
}
}
@@ -344,9 +318,7 @@
_activityStartRequests.value = null
}
- private fun requestActivityStart(
- intent: Intent,
- ) {
+ private fun requestActivityStart(intent: Intent) {
_activityStartRequests.value = intent
}
@@ -359,11 +331,7 @@
) {
_dialog.value =
DialogViewModel(
- icon =
- Icon.Loaded(
- drawable = icon,
- contentDescription = null,
- ),
+ icon = Icon.Loaded(drawable = icon, contentDescription = null),
headline = Text.Resource(R.string.keyguard_affordance_enablement_dialog_headline),
message = Text.Loaded(explanation),
buttons =
@@ -382,7 +350,7 @@
}
),
style = ButtonStyle.Secondary,
- ),
+ )
)
if (actionText != null) {
@@ -392,8 +360,8 @@
style = ButtonStyle.Primary,
onClicked = {
actionIntent?.let { intent -> requestActivityStart(intent) }
- }
- ),
+ },
+ )
)
}
},
@@ -454,10 +422,7 @@
icon1 =
icon1
?: if (icon2 == null) {
- Icon.Resource(
- res = R.drawable.link_off,
- contentDescription = null,
- )
+ Icon.Resource(res = R.drawable.link_off, contentDescription = null)
} else {
null
},
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ShapeAndGridPickerViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ShapeAndGridPickerViewModel.kt
index 2ce4747..a13a652 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ShapeAndGridPickerViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ShapeAndGridPickerViewModel.kt
@@ -37,7 +37,6 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
class ShapeAndGridPickerViewModel
@AssistedInject
@@ -67,7 +66,7 @@
gridOptions.map { toOptionItemViewModel(it) }
}
- val onApply: Flow<(() -> Unit)?> =
+ val onApply: Flow<(suspend () -> Unit)?> =
combine(selectedGridOption, _previewingGridOptionKey) {
selectedGridOption,
previewingGridOptionKey ->
@@ -77,11 +76,7 @@
) {
null
} else {
- {
- viewModelScope.launch {
- interactor.applySelectedOption(previewingGridOptionKey)
- }
- }
+ { interactor.applySelectedOption(previewingGridOptionKey) }
}
}
@@ -115,11 +110,7 @@
return OptionItemViewModel(
key = MutableStateFlow(option.key),
payload =
- GridIconViewModel(
- columns = option.cols,
- rows = option.rows,
- path = iconShapePath,
- ),
+ GridIconViewModel(columns = option.cols, rows = option.rows, path = iconShapePath),
text = Text.Loaded(option.title),
isSelected = isSelected,
onClicked =
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
index 0adff1b..03831bd 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
@@ -32,6 +32,7 @@
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
class ThemePickerCustomizationOptionsViewModel
@AssistedInject
@@ -59,7 +60,7 @@
override fun deselectOption(): Boolean {
keyguardQuickAffordancePickerViewModel2.resetPreview()
shapeAndGridPickerViewModel.resetPreview()
-
+ clockPickerViewModel.resetPreview()
return defaultCustomizationOptionsViewModel.deselectOption()
}
@@ -122,6 +123,8 @@
selectedOption
.flatMapLatest {
when (it) {
+ ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption.CLOCK ->
+ clockPickerViewModel.onApply
ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption
.SHORTCUTS -> keyguardQuickAffordancePickerViewModel2.onApply
ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
@@ -129,6 +132,19 @@
else -> flow { emit(null) }
}
}
+ .map { onApply ->
+ {
+ if (onApply != null) {
+ viewModelScope.launch {
+ onApply()
+ // We only wait until onApply() is done to execute deselectOption()
+ deselectOption()
+ }
+ } else {
+ null
+ }
+ }
+ }
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
val isOnApplyEnabled: Flow<Boolean> = onApplyButtonClicked.map { it != null }
diff --git a/src_override/com/android/wallpaper/modules/ThemePickerActivityModule.kt b/src_override/com/android/wallpaper/modules/ThemePickerActivityModule.kt
index 90a0e3b..31213e5 100644
--- a/src_override/com/android/wallpaper/modules/ThemePickerActivityModule.kt
+++ b/src_override/com/android/wallpaper/modules/ThemePickerActivityModule.kt
@@ -16,6 +16,8 @@
package com.android.wallpaper.modules
+import com.android.customization.picker.clock.ui.view.ClockViewFactory
+import com.android.customization.picker.clock.ui.view.ThemePickerClockViewFactory
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil
import com.android.wallpaper.picker.customization.ui.util.CustomizationOptionUtil
import dagger.Binds
@@ -30,6 +32,10 @@
@Binds
@ActivityScoped
+ abstract fun bindClockViewFactory(impl: ThemePickerClockViewFactory): ClockViewFactory
+
+ @Binds
+ @ActivityScoped
abstract fun bindCustomizationOptionUtil(
impl: ThemePickerCustomizationOptionUtil
): CustomizationOptionUtil
diff --git a/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index 0b66357..f97feef 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -37,12 +37,11 @@
private val colorTone = MutableStateFlow(ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS)
@ColorInt private val seedColor = MutableStateFlow<Int?>(null)
override val selectedClock: Flow<ClockMetadataModel> =
- combine(
+ combine(selectedClockId, selectedColorId, colorTone, seedColor) {
selectedClockId,
- selectedColorId,
+ selectedColor,
colorTone,
- seedColor,
- ) { selectedClockId, selectedColor, colorTone, seedColor ->
+ seedColor ->
val selectedClock = fakeClocks.find { clock -> clock.clockId == selectedClockId }
checkNotNull(selectedClock)
ClockMetadataModel(
@@ -57,7 +56,7 @@
)
}
- private val _selectedClockSize = MutableStateFlow(ClockSize.SMALL)
+ private val _selectedClockSize = MutableStateFlow(ClockSize.DYNAMIC)
override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
override suspend fun setSelectedClock(clockId: String) {
@@ -93,7 +92,7 @@
true,
null,
50,
- null
+ null,
),
ClockMetadataModel(
CLOCK_ID_1,
@@ -103,7 +102,7 @@
true,
null,
50,
- null
+ null,
),
ClockMetadataModel(
CLOCK_ID_2,
@@ -113,7 +112,7 @@
true,
null,
50,
- null
+ null,
),
ClockMetadataModel(
CLOCK_ID_3,
@@ -123,7 +122,7 @@
false,
null,
50,
- null
+ null,
),
)
const val CLOCK_COLOR_ID = "RED"
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
index 41192e7..3249024 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
@@ -4,24 +4,22 @@
import android.view.View
import androidx.lifecycle.LifecycleOwner
import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
-import com.android.customization.picker.clock.ui.FakeClockViewFactory.Companion.fakeClocks
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceController
import java.io.PrintWriter
+import javax.inject.Inject
/**
* This is a fake [ClockViewFactory]. Only implement the function if it's actually called in a test.
*/
-class FakeClockViewFactory(
- val clockControllers: MutableMap<String, ClockController> = fakeClocks.toMutableMap(),
-) : ClockViewFactory {
+class FakeClockViewFactory @Inject constructor() : ClockViewFactory {
- class FakeClockController(
- override var config: ClockConfig,
- ) : ClockController {
+ private val clockControllers: MutableMap<String, ClockController> = fakeClocks.toMutableMap()
+
+ class FakeClockController(override var config: ClockConfig) : ClockController {
override val smallClock: ClockFaceController
get() = TODO("Not yet implemented")
@@ -37,7 +35,7 @@
override fun dump(pw: PrintWriter) = TODO("Not yet implemented")
}
- override fun getController(clockId: String): ClockController = clockControllers.get(clockId)!!
+ override fun getController(clockId: String): ClockController = clockControllers[clockId]!!
override fun setReactiveTouchInteractionEnabled(clockId: String, enable: Boolean) {
TODO("Not yet implemented")
@@ -81,17 +79,15 @@
companion object {
val fakeClocks =
- FakeClockPickerRepository.fakeClocks
- .map { clock ->
- clock.clockId to
- FakeClockController(
- ClockConfig(
- id = clock.clockId,
- name = "Name: ${clock.clockId}",
- description = "Desc: ${clock.clockId}"
- )
+ FakeClockPickerRepository.fakeClocks.associate { clock ->
+ clock.clockId to
+ FakeClockController(
+ ClockConfig(
+ id = clock.clockId,
+ name = "Name: ${clock.clockId}",
+ description = "Desc: ${clock.clockId}",
)
- }
- .toMap()
+ )
+ }
}
}
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt
index 73c9bd9..72f3f6b 100644
--- a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt
@@ -17,7 +17,6 @@
package com.android.wallpaper.customization.ui.viewmodel
import android.content.Context
-import android.stats.style.StyleEnums
import androidx.test.filters.SmallTest
import com.android.customization.module.logging.TestThemesUserEventLogger
import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
@@ -30,6 +29,7 @@
import com.android.customization.picker.color.data.repository.FakeColorPickerRepository
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
+import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.Tab
import com.android.wallpaper.testing.FakeSnapshotStore
import com.android.wallpaper.testing.collectLastValue
import com.google.common.truth.Truth.assertThat
@@ -115,135 +115,223 @@
}
@Test
- fun setTab() = runTest {
+ fun selectedTab_whenClickOnTabs() = runTest {
val tabs = collectLastValue(underTest.tabs)
- assertThat(tabs()?.get(0)?.isSelected).isTrue()
+ val selectedTab = collectLastValue(underTest.selectedTab)
+
+ assertThat(selectedTab()).isEqualTo(Tab.STYLE)
+
tabs()?.get(1)?.onClick?.invoke()
- assertThat(tabs()?.get(1)?.isSelected).isTrue()
+
+ assertThat(selectedTab()).isEqualTo(Tab.COLOR)
+
tabs()?.get(2)?.onClick?.invoke()
+
+ assertThat(selectedTab()).isEqualTo(Tab.SIZE)
+ }
+
+ @Test
+ fun tabs_whenClickOnTabs() = runTest {
+ val tabs = collectLastValue(underTest.tabs)
+
+ assertThat(tabs()?.get(0)?.isSelected).isTrue()
+
+ tabs()?.get(1)?.onClick?.invoke()
+
+ assertThat(tabs()?.get(1)?.isSelected).isTrue()
+
+ tabs()?.get(2)?.onClick?.invoke()
+
assertThat(tabs()?.get(2)?.isSelected).isTrue()
}
@Test
- fun setClockStyle() = runTest {
+ fun previewingClock_whenClickOnStyleOptions() = runTest {
+ val previewingClock = collectLastValue(underTest.previewingClock)
+ val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+ assertThat(previewingClock()?.clockId).isEqualTo(FakeClockPickerRepository.CLOCK_ID_0)
+
+ val option1OnClicked = collectLastValue(clockStyleOptions()!![1].onClicked)
+ option1OnClicked()?.invoke()
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockColorOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+ assertThat(previewingClock()?.clockId).isEqualTo(FakeClockPickerRepository.CLOCK_ID_1)
+ }
+
+ @Test
+ fun clockStyleOptions_whenClickOnStyleOptions() = runTest {
val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
// Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
val option0IsSelected = collectLastValue(clockStyleOptions()!![0].isSelected)
val option0OnClicked = collectLastValue(clockStyleOptions()!![0].onClicked)
+ val option1IsSelected = collectLastValue(clockStyleOptions()!![1].isSelected)
+ val option1OnClicked = collectLastValue(clockStyleOptions()!![1].onClicked)
+
assertThat(option0IsSelected()).isTrue()
assertThat(option0OnClicked()).isNull()
- val option1OnClickedBefore = collectLastValue(clockStyleOptions()!![1].onClicked)
- option1OnClickedBefore()?.invoke()
+ option1OnClicked()?.invoke()
// Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockColorOptions
advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
- val option1IsSelected = collectLastValue(clockStyleOptions()!![1].isSelected)
- val option1OnClickedAfter = collectLastValue(clockStyleOptions()!![1].onClicked)
+
assertThat(option0IsSelected()).isFalse()
assertThat(option1IsSelected()).isTrue()
- assertThat(option1OnClickedAfter()).isNull()
+ assertThat(option1OnClicked()).isNull()
}
@Test
- fun setSelectedColor() = runTest {
+ fun previewingClockSize_whenClickOnSizeOptions() = runTest {
+ val previewingClockSize = collectLastValue(underTest.previewingClockSize)
+ val sizeOptions = collectLastValue(underTest.sizeOptions)
+
+ assertThat(previewingClockSize()).isEqualTo(ClockSize.DYNAMIC)
+
+ val option1OnClicked = collectLastValue(sizeOptions()!![1].onClicked)
+ option1OnClicked()?.invoke()
+
+ assertThat(previewingClockSize()).isEqualTo(ClockSize.SMALL)
+ }
+
+ @Test
+ fun sizeOptions_whenClickOnSizeOptions() = runTest {
+ val sizeOptions = collectLastValue(underTest.sizeOptions)
+ val option0IsSelected = collectLastValue(sizeOptions()!![0].isSelected)
+ val option0OnClicked = collectLastValue(sizeOptions()!![0].onClicked)
+ val option1IsSelected = collectLastValue(sizeOptions()!![1].isSelected)
+ val option1OnClicked = collectLastValue(sizeOptions()!![1].onClicked)
+
+ assertThat(sizeOptions()!![0].size).isEqualTo(ClockSize.DYNAMIC)
+ assertThat(sizeOptions()!![1].size).isEqualTo(ClockSize.SMALL)
+ assertThat(option0IsSelected()).isTrue()
+ assertThat(option0OnClicked()).isNull()
+
+ option1OnClicked()?.invoke()
+
+ assertThat(option0IsSelected()).isFalse()
+ assertThat(option1IsSelected()).isTrue()
+ assertThat(option1OnClicked()).isNull()
+ }
+
+ @Test
+ fun sliderProgress_whenOnSliderProgressChanged() = runTest {
+ val sliderProgress = collectLastValue(underTest.previewingSliderProgress)
+
+ assertThat(sliderProgress()).isEqualTo(ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS)
+
+ underTest.onSliderProgressChanged(87)
+
+ assertThat(sliderProgress()).isEqualTo(87)
+ }
+
+ @Test
+ fun isSliderEnabledShouldBeTrue_whenTheClockIsReactiveToToneAndSolidColor() = runTest {
+ val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+ val styleOption0OnClicked = collectLastValue(clockStyleOptions()!![0].onClicked)
val clockColorOptions = collectLastValue(underTest.clockColorOptions)
- val observedSliderProgress = collectLastValue(underTest.sliderProgress)
- val observedSeedColor = collectLastValue(underTest.seedColor)
+ // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
+ // clockColorOptions
+ advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ val colorOption1OnClicked = collectLastValue(clockColorOptions()!![1].onClicked)
+ val isSliderEnabled = collectLastValue(underTest.isSliderEnabled)
+
+ styleOption0OnClicked()?.invoke()
+ colorOption1OnClicked()?.invoke()
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+ assertThat(isSliderEnabled()).isTrue()
+ }
+
+ @Test
+ fun isSliderEnabledShouldBeFalse_whenTheClockIsReactiveToToneAndDefaultColor() = runTest {
+ val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+ val styleOption0OnClicked = collectLastValue(clockStyleOptions()!![0].onClicked)
+ val clockColorOptions = collectLastValue(underTest.clockColorOptions)
+ // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
+ // clockColorOptions
+ advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ val colorOption0OnClicked = collectLastValue(clockColorOptions()!![0].onClicked)
+ val isSliderEnabled = collectLastValue(underTest.isSliderEnabled)
+
+ styleOption0OnClicked()?.invoke()
+ colorOption0OnClicked()?.invoke()
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+ assertThat(isSliderEnabled()).isFalse()
+ }
+
+ @Test
+ fun isSliderEnabledShouldBeFalse_whenTheClockIsNotReactiveToTone() = runTest {
+ val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+ val styleOption3OnClicked = collectLastValue(clockStyleOptions()!![3].onClicked)
+ val isSliderEnabled = collectLastValue(underTest.isSliderEnabled)
+
+ styleOption3OnClicked()?.invoke()
+ // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+ advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+ assertThat(isSliderEnabled()).isFalse()
+ }
+
+ @Test
+ fun previewingSeedColor_whenChangeColorOptionAndToneProgress() = runTest {
+ val previewingSeedColor = collectLastValue(underTest.previewingSeedColor)
+ val clockColorOptions = collectLastValue(underTest.clockColorOptions)
+ // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
+ // clockColorOptions
+ advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ val option1OnClicked = collectLastValue(clockColorOptions()!![1].onClicked)
+
+ option1OnClicked()?.invoke()
+ // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
+ // clockColorOptions
+ advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+ val targetProgress = 55
+ underTest.onSliderProgressChanged(targetProgress)
+
+ val expectedSelectedColorModel = colorMap.values.first() // RED
+ assertThat(previewingSeedColor())
+ .isEqualTo(
+ ClockSettingsViewModel.blendColorWithTone(
+ expectedSelectedColorModel.color,
+ expectedSelectedColorModel.getColorTone(targetProgress),
+ )
+ )
+ }
+
+ @Test
+ fun clockColorOptions_whenClickOnColorOptions() = runTest {
+ val clockColorOptions = collectLastValue(underTest.clockColorOptions)
// Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
// clockColorOptions
advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
val option0IsSelected = collectLastValue(clockColorOptions()!![0].isSelected)
val option0OnClicked = collectLastValue(clockColorOptions()!![0].onClicked)
+ val option1IsSelected = collectLastValue(clockColorOptions()!![1].isSelected)
+ val option1OnClicked = collectLastValue(clockColorOptions()!![1].onClicked)
+
assertThat(option0IsSelected()).isTrue()
assertThat(option0OnClicked()).isNull()
- val option1OnClickedBefore = collectLastValue(clockColorOptions()!![1].onClicked)
- option1OnClickedBefore()?.invoke()
+ option1OnClicked()?.invoke()
// Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
// clockColorOptions
advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- val option1IsSelected = collectLastValue(clockColorOptions()!![1].isSelected)
- val option1OnClickedAfter = collectLastValue(clockColorOptions()!![1].onClicked)
+
assertThat(option0IsSelected()).isFalse()
assertThat(option1IsSelected()).isTrue()
- assertThat(option1OnClickedAfter()).isNull()
- assertThat(observedSliderProgress())
- .isEqualTo(ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS)
- val expectedSelectedColorModel = colorMap.values.first() // RED
- assertThat(observedSeedColor())
- .isEqualTo(
- ClockSettingsViewModel.blendColorWithTone(
- expectedSelectedColorModel.color,
- expectedSelectedColorModel.getColorTone(
- ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS
- ),
- )
- )
- }
-
- @Test
- fun setColorTone() = runTest {
- val clockColorOptions = collectLastValue(underTest.clockColorOptions)
- val observedIsSliderEnabled = collectLastValue(underTest.isSliderEnabled)
- val observedSliderProgress = collectLastValue(underTest.sliderProgress)
- val observedSeedColor = collectLastValue(underTest.seedColor)
- // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
- // clockColorOptions
- advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- val option0IsSelected = collectLastValue(clockColorOptions()!![0].isSelected)
- assertThat(option0IsSelected()).isTrue()
- assertThat(observedIsSliderEnabled()).isFalse()
-
- val option1OnClicked = collectLastValue(clockColorOptions()!![1].onClicked)
- option1OnClicked()?.invoke()
-
- // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
- // clockColorOptions
- advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- assertThat(observedIsSliderEnabled()).isTrue()
- val targetProgress1 = 99
- underTest.onSliderProgressChanged(targetProgress1)
- assertThat(observedSliderProgress()).isEqualTo(targetProgress1)
- val targetProgress2 = 55
- testScope.launch { underTest.onSliderProgressStop(targetProgress2) }
- assertThat(observedSliderProgress()).isEqualTo(targetProgress2)
- val expectedSelectedColorModel = colorMap.values.first() // RED
- assertThat(observedSeedColor())
- .isEqualTo(
- ClockSettingsViewModel.blendColorWithTone(
- expectedSelectedColorModel.color,
- expectedSelectedColorModel.getColorTone(targetProgress2),
- )
- )
- }
-
- @Test
- fun getIsReactiveToTone() = runTest {
- val clockColorOptions = collectLastValue(underTest.clockColorOptions)
- val isSliderEnabled = collectLastValue(underTest.isSliderEnabled)
- // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from
- // clockColorOptions
- advanceTimeBy(ClockPickerViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
- val option1OnClicked = collectLastValue(clockColorOptions()!![1].onClicked)
- option1OnClicked()?.invoke()
-
- underTest.setSelectedClock(FakeClockPickerRepository.CLOCK_ID_0)
- assertThat(isSliderEnabled()).isTrue()
-
- underTest.setSelectedClock(FakeClockPickerRepository.CLOCK_ID_3)
- assertThat(isSliderEnabled()).isFalse()
- }
-
- @Test
- fun setClockSize() = runTest {
- val selectedClockSize = collectLastValue(underTest.selectedClockSize)
- underTest.setClockSize(ClockSize.DYNAMIC)
- assertThat(selectedClockSize()).isEqualTo(ClockSize.DYNAMIC)
- assertThat(logger.getLoggedClockSize()).isEqualTo(StyleEnums.CLOCK_SIZE_DYNAMIC)
-
- underTest.setClockSize(ClockSize.SMALL)
- assertThat(selectedClockSize()).isEqualTo(ClockSize.SMALL)
- assertThat(logger.getLoggedClockSize()).isEqualTo(StyleEnums.CLOCK_SIZE_SMALL)
+ assertThat(option1OnClicked()).isNull()
}
}