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()
     }
 }