Add step slider for presets

Add a step slider for clock presets. This is only handling showing and
hiding the slider when choosing different clock styles.

Test: Maually tested. See bug.
Bug: 395647577
Flag: com.android.systemui.shared.new_customization_picker_ui
Change-Id: I47753873a13c1dddfaf4bd2d4b367a2f91dbd917
diff --git a/res/layout/floating_sheet_clock_style_content.xml b/res/layout/floating_sheet_clock_style_content.xml
index 2dc9b33..259f376 100644
--- a/res/layout/floating_sheet_clock_style_content.xml
+++ b/res/layout/floating_sheet_clock_style_content.xml
@@ -13,33 +13,56 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:orientation="vertical"
     android:paddingVertical="@dimen/floating_sheet_content_vertical_padding"
     android:clipToPadding="false"
     android:clipChildren="false">
 
-    <!--
-    This is an invisible placeholder put in place so that the parent keeps its height
-    stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows
-    the layout logic to keep the size of the preview container stable as well, which
-    bodes well for setting up the SurfaceView for remote rendering without changing its
-    size after the content is loaded into the RecyclerView.
-
-    It's critical for any TextViews inside the included layout to have text.
-    -->
-    <include
-        layout="@layout/clock_style_option"
-        android:layout_width="@dimen/floating_sheet_clock_style_option_size"
-        android:layout_height="@dimen/floating_sheet_clock_style_option_size"
-        android:visibility="invisible" />
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/clock_style_list"
+    <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:clipChildren="false"
-        android:clipToPadding="false"/>
-</FrameLayout>
+        android:clipToPadding="false"
+        android:clipChildren="false">
+
+        <!--
+        This is an invisible placeholder put in place so that the parent keeps its height
+        stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows
+        the layout logic to keep the size of the preview container stable as well, which
+        bodes well for setting up the SurfaceView for remote rendering without changing its
+        size after the content is loaded into the RecyclerView.
+
+        It's critical for any TextViews inside the included layout to have text.
+        -->
+        <include
+            layout="@layout/clock_style_option"
+            android:layout_width="@dimen/floating_sheet_clock_style_option_size"
+            android:layout_height="@dimen/floating_sheet_clock_style_option_size"
+            android:visibility="invisible" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/clock_style_list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"/>
+    </FrameLayout>
+
+    <com.google.android.material.slider.Slider
+        android:id="@+id/clock_axis_preset_slider"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/accessibility_min_height"
+        android:layout_marginTop="@dimen/clock_axis_control_slider_row_margin_vertical"
+        android:valueFrom="0.0"
+        android:valueTo="100.0"
+        android:stepSize="10.0"
+        app:trackHeight="@dimen/slider_track_height"
+        app:thumbHeight="@dimen/slider_thumb_height"
+        app:labelBehavior="gone"
+        android:theme="@style/Theme.Material3.DynamicColors.DayNight" />
+</LinearLayout>
+
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index 9342ab5..e810c8a 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.IntRange
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.systemui.plugins.clocks.AxisPresetConfig
 import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockFontAxis
 import com.android.systemui.plugins.clocks.ClockId
@@ -74,6 +75,7 @@
                                     thumbnail = clockConfig.thumbnail,
                                     isReactiveToTone = clockConfig.isReactiveToTone,
                                     fontAxes = clockConfig.axes,
+                                    axisPresetConfig = clockConfig.presetConfig,
                                 )
                             } else {
                                 null
@@ -120,6 +122,7 @@
                                     thumbnail = it.thumbnail,
                                     isReactiveToTone = it.isReactiveToTone,
                                     fontAxes = it.axes,
+                                    axisPresetConfig = it.presetConfig,
                                     selectedColorId = metadata?.getSelectedColorId(),
                                     colorTone =
                                         metadata?.getColorTone()
@@ -225,6 +228,7 @@
         thumbnail: Drawable,
         isReactiveToTone: Boolean,
         fontAxes: List<ClockFontAxis>,
+        axisPresetConfig: AxisPresetConfig?,
         selectedColorId: String? = null,
         @IntRange(from = 0, to = 100) colorTone: Int = 0,
         @ColorInt seedColor: Int? = null,
@@ -236,6 +240,7 @@
             thumbnail = thumbnail,
             isReactiveToTone = isReactiveToTone,
             fontAxes = fontAxes,
+            axisPresetConfig = axisPresetConfig,
             selectedColorId = selectedColorId,
             colorToneProgress = colorTone,
             seedColor = seedColor,
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
index 8414960..a839ffe 100644
--- a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
+++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable
 import androidx.annotation.ColorInt
 import androidx.annotation.IntRange
+import com.android.systemui.plugins.clocks.AxisPresetConfig
 import com.android.systemui.plugins.clocks.ClockFontAxis
 
 /** Model for clock metadata. */
@@ -30,6 +31,7 @@
     val thumbnail: Drawable,
     val isReactiveToTone: Boolean,
     val fontAxes: List<ClockFontAxis>,
+    val axisPresetConfig: AxisPresetConfig?, // Null indicates the preset list should be disabled.
     val selectedColorId: String?,
     @IntRange(from = 0, to = 100) val colorToneProgress: Int,
     @ColorInt val seedColor: Int?,
diff --git a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
index c08bafa..d141f30 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
@@ -85,7 +85,7 @@
         val appContext = view.context.applicationContext
         val isFloatingSheetActive = { optionsViewModel.selectedOption.value == CLOCK }
 
-        val tabs = view.requireViewById<FloatingToolbar>(R.id.floating_toolbar)
+        val tabs: FloatingToolbar = view.requireViewById(R.id.floating_toolbar)
         val tabContainer =
             tabs.findViewById<ViewGroup>(com.android.wallpaper.R.id.floating_toolbar_tab_container)
         ColorUpdateBinder.bind(
@@ -103,8 +103,8 @@
                 )
                 .also { tabs.setAdapter(it) }
 
-        val floatingSheetContainer =
-            view.requireViewById<ViewGroup>(R.id.floating_sheet_content_container)
+        val floatingSheetContainer: ViewGroup =
+            view.requireViewById(R.id.floating_sheet_content_container)
         ColorUpdateBinder.bind(
             setColor = { color ->
                 DrawableCompat.setTint(
@@ -118,7 +118,7 @@
         )
 
         // Clock style
-        val clockStyleContent = view.requireViewById<View>(R.id.clock_floating_sheet_style_content)
+        val clockStyleContent: View = view.requireViewById(R.id.clock_floating_sheet_style_content)
         val isClockStyleActive = {
             isFloatingSheetActive() && viewModel.selectedTab.value == Tab.STYLE
         }
@@ -128,13 +128,14 @@
                 shouldAnimateColor = isClockStyleActive,
                 lifecycleOwner = lifecycleOwner,
             )
-        val clockStyleList =
-            view.requireViewById<RecyclerView>(R.id.clock_style_list).apply {
-                initStyleList(appContext, clockStyleAdapter)
-            }
+        val clockStyleList: RecyclerView = view.requireViewById(R.id.clock_style_list)
+        clockStyleList.initStyleList(appContext, clockStyleAdapter)
+        val axisPresetSlider: Slider =
+            clockStyleContent.requireViewById(R.id.clock_axis_preset_slider)
 
         // Clock color
-        val clockColorContent = view.requireViewById<View>(R.id.clock_floating_sheet_color_content)
+        val clockColorContent: View = view.requireViewById(R.id.clock_floating_sheet_color_content)
+
         val clockColorAdapter =
             createClockColorOptionItemAdapter(
                 uiMode = view.resources.configuration.uiMode,
@@ -142,21 +143,19 @@
                 shouldAnimateColor = isFloatingSheetActive,
                 lifecycleOwner = lifecycleOwner,
             )
-        val clockColorList =
-            view.requireViewById<RecyclerView>(R.id.clock_color_list).apply {
-                adapter = clockColorAdapter
-                layoutManager =
-                    LinearLayoutManager(appContext, LinearLayoutManager.HORIZONTAL, false)
-            }
-        val clockColorSlider: Slider =
-            view.requireViewById<Slider>(R.id.clock_color_slider).also {
-                SliderColorBinder.bind(
-                    slider = it,
-                    colorUpdateViewModel = colorUpdateViewModel,
-                    shouldAnimateColor = isFloatingSheetActive,
-                    lifecycleOwner = lifecycleOwner,
-                )
-            }
+        val clockColorList: RecyclerView = view.requireViewById(R.id.clock_color_list)
+        clockColorList.adapter = clockColorAdapter
+        clockColorList.layoutManager =
+            LinearLayoutManager(appContext, LinearLayoutManager.HORIZONTAL, false)
+
+        val clockColorSlider: Slider = view.requireViewById(R.id.clock_color_slider)
+        SliderColorBinder.bind(
+            slider = clockColorSlider,
+            colorUpdateViewModel = colorUpdateViewModel,
+            shouldAnimateColor = isFloatingSheetActive,
+            lifecycleOwner = lifecycleOwner,
+        )
+
         clockColorSlider.apply {
             valueFrom = ClockMetadataModel.MIN_COLOR_TONE_PROGRESS.toFloat()
             valueTo = ClockMetadataModel.MAX_COLOR_TONE_PROGRESS.toFloat()
@@ -192,9 +191,9 @@
         )
 
         // Clock size
-        val clockSizeContent = view.requireViewById<View>(R.id.clock_floating_sheet_size_content)
-        val clockSizeSwitch =
-            clockSizeContent.requireViewById<MaterialSwitch>(R.id.clock_style_clock_size_switch)
+        val clockSizeContent: View = view.requireViewById(R.id.clock_floating_sheet_size_content)
+        val clockSizeSwitch: MaterialSwitch =
+            clockSizeContent.requireViewById(R.id.clock_style_clock_size_switch)
         ColorUpdateBinder.bind(
             setColor = { color ->
                 clockSizeContent
@@ -221,11 +220,14 @@
                 override fun onGlobalLayout() {
                     if (
                         clockStyleContent.height != 0 &&
+                            axisPresetSlider.height != 0 &&
+                            _clockFloatingSheetHeights.value.axisPresetSliderHeight == null &&
                             _clockFloatingSheetHeights.value.clockStyleContentHeight == null
                     ) {
                         _clockFloatingSheetHeights.value =
                             _clockFloatingSheetHeights.value.copy(
-                                clockStyleContentHeight = clockStyleContent.height
+                                clockStyleContentHeight = clockStyleContent.height,
+                                axisPresetSliderHeight = axisPresetSlider.height,
                             )
                         clockStyleContent.viewTreeObserver.removeOnGlobalLayoutListener(this)
                     }
@@ -268,60 +270,82 @@
         )
 
         lifecycleOwner.lifecycleScope.launch {
-            var currentContent: View = clockStyleContent
+            var currentTab: Tab = Tab.STYLE
             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch { viewModel.tabs.collect { tabAdapter.submitList(it) } }
 
                 launch {
-                    combine(clockFloatingSheetHeights, viewModel.selectedTab, ::Pair).collect {
-                        (heights, selectedTab) ->
-                        val (
-                            clockStyleContentHeight,
-                            clockColorContentHeight,
-                            clockSizeContentHeight) =
-                            heights
-                        clockStyleContentHeight ?: return@collect
-                        clockColorContentHeight ?: return@collect
-                        clockSizeContentHeight ?: return@collect
+                    combine(
+                            clockFloatingSheetHeights,
+                            viewModel.selectedTab,
+                            viewModel.shouldShowPresetSlider,
+                            ::Triple,
+                        )
+                        .collect { (heights, selectedTab, shouldShowPresetSlider) ->
+                            val (
+                                clockStyleContentHeight,
+                                clockColorContentHeight,
+                                clockSizeContentHeight,
+                                axisPresetSliderHeight) =
+                                heights
+                            clockStyleContentHeight ?: return@collect
+                            clockColorContentHeight ?: return@collect
+                            clockSizeContentHeight ?: return@collect
+                            axisPresetSliderHeight ?: return@collect
 
-                        val fromHeight = floatingSheetContainer.height
-                        val toHeight =
-                            when (selectedTab) {
-                                Tab.STYLE -> clockStyleContentHeight
-                                Tab.COLOR -> clockColorContentHeight
-                                Tab.SIZE -> clockSizeContentHeight
-                            }
-                        // Start to animate the content height
-                        ValueAnimator.ofInt(fromHeight, toHeight)
-                            .apply {
-                                addUpdateListener { valueAnimator ->
-                                    val value = valueAnimator.animatedValue as Int
-                                    floatingSheetContainer.layoutParams =
-                                        floatingSheetContainer.layoutParams.apply { height = value }
-                                    currentContent.alpha = getAlpha(fromHeight, toHeight, value)
+                            val fromHeight = floatingSheetContainer.height
+                            val toHeight =
+                                when (selectedTab) {
+                                    Tab.STYLE ->
+                                        if (shouldShowPresetSlider) clockStyleContentHeight
+                                        else clockStyleContentHeight - axisPresetSliderHeight
+                                    Tab.COLOR -> clockColorContentHeight
+                                    Tab.SIZE -> clockSizeContentHeight
                                 }
-                                duration = ANIMATION_DURATION
-                                addListener(
-                                    object : AnimatorListenerAdapter() {
-                                        override fun onAnimationEnd(animation: Animator) {
-                                            clockStyleContent.isVisible = selectedTab == Tab.STYLE
-                                            clockStyleContent.alpha = 1f
-                                            clockColorContent.isVisible = selectedTab == Tab.COLOR
-                                            clockColorContent.alpha = 1f
-                                            clockSizeContent.isVisible = selectedTab == Tab.SIZE
-                                            clockSizeContent.alpha = 1f
-                                            currentContent =
-                                                when (selectedTab) {
-                                                    Tab.STYLE -> clockStyleContent
-                                                    Tab.COLOR -> clockColorContent
-                                                    Tab.SIZE -> clockSizeContent
-                                                }
+                            val currentContent: View =
+                                when (currentTab) {
+                                    Tab.STYLE -> clockStyleContent
+                                    Tab.COLOR -> clockColorContent
+                                    Tab.SIZE -> clockSizeContent
+                                }
+                            val shouldCurrentContentFadeOut = currentTab != selectedTab
+                            // Start to animate the content height
+                            ValueAnimator.ofInt(fromHeight, toHeight)
+                                .apply {
+                                    addUpdateListener { valueAnimator ->
+                                        val value = valueAnimator.animatedValue as Int
+                                        floatingSheetContainer.layoutParams =
+                                            floatingSheetContainer.layoutParams.apply {
+                                                height = value
+                                            }
+                                        if (shouldCurrentContentFadeOut) {
+                                            currentContent.alpha =
+                                                getAlpha(fromHeight, toHeight, value)
                                         }
                                     }
-                                )
-                            }
-                            .start()
-                    }
+                                    duration = ANIMATION_DURATION
+                                    addListener(
+                                        object : AnimatorListenerAdapter() {
+                                            override fun onAnimationEnd(animation: Animator) {
+                                                clockStyleContent.isVisible =
+                                                    selectedTab == Tab.STYLE
+                                                clockStyleContent.alpha = 1f
+                                                clockColorContent.isVisible =
+                                                    selectedTab == Tab.COLOR
+                                                clockColorContent.alpha = 1f
+                                                clockSizeContent.isVisible = selectedTab == Tab.SIZE
+                                                clockSizeContent.alpha = 1f
+                                            }
+                                        }
+                                    )
+                                }
+                                .start()
+                            currentTab = selectedTab
+                        }
+                }
+
+                launch {
+                    viewModel.shouldShowPresetSlider.collect { axisPresetSlider.isVisible = it }
                 }
 
                 launch {
@@ -440,10 +464,8 @@
             layoutResourceId = R.layout.color_option2,
             lifecycleOwner = lifecycleOwner,
             bindPayload = { itemView: View, colorIcon: ColorOptionIconViewModel ->
-                val colorOptionIconView =
-                    itemView.requireViewById<ColorOptionIconView2>(
-                        com.android.wallpaper.R.id.background
-                    )
+                val colorOptionIconView: ColorOptionIconView2 =
+                    itemView.requireViewById(com.android.wallpaper.R.id.background)
                 val night = uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
                 val binding =
                     ColorOptionIconBinder2.bind(
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
index 42ed420..fcfec5f 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
@@ -20,4 +20,5 @@
     val clockStyleContentHeight: Int? = null,
     val clockColorContentHeight: Int? = null,
     val clockSizeContentHeight: Int? = null,
+    val axisPresetSliderHeight: Int? = null,
 )
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
index 5ab54fd..cd2183a 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
@@ -156,7 +156,7 @@
             .mapLatest { allClocks ->
                 // Delay to avoid the case that the full list of clocks is not initiated.
                 delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
-                val allClockMap = allClocks.groupBy { it.fontAxes.isNotEmpty() }
+                val allClockMap = allClocks.groupBy { it.axisPresetConfig != null }
                 buildList {
                     allClockMap[true]?.map { add(it.toOption(resources)) }
                     allClockMap[false]?.map { add(it.toOption(resources)) }
@@ -168,6 +168,8 @@
             .flowOn(backgroundDispatcher.limitedParallelism(1))
             .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
 
+    val shouldShowPresetSlider: Flow<Boolean> = previewingClock.map { it.axisPresetConfig != null }
+
     private suspend fun ClockMetadataModel.toOption(
         resources: Resources
     ): OptionItemViewModel2<ClockStyleModel> {
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 fd2c801..83f49d6 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
@@ -61,6 +61,7 @@
                 thumbnail = ColorDrawable(0),
                 isReactiveToTone = selectedClock.isReactiveToTone,
                 fontAxes = fontAxes,
+                axisPresetConfig = null,
                 selectedColorId = selectedColor,
                 colorToneProgress = colorTone,
                 seedColor = seedColor,
@@ -121,6 +122,7 @@
                     true,
                     listOf(buildFakeAxis(0)),
                     null,
+                    null,
                     50,
                     null,
                 ),
@@ -132,6 +134,7 @@
                     true,
                     listOf(buildFakeAxis(1)),
                     null,
+                    null,
                     50,
                     null,
                 ),
@@ -143,6 +146,7 @@
                     true,
                     listOf(buildFakeAxis(2)),
                     null,
+                    null,
                     50,
                     null,
                 ),
@@ -154,6 +158,7 @@
                     false,
                     listOf(buildFakeAxis(3)),
                     null,
+                    null,
                     50,
                     null,
                 ),
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
index 0386c85..f8e6a94 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
@@ -61,6 +61,7 @@
                     thumbnail = ColorDrawable(0),
                     isReactiveToTone = true,
                     fontAxes = listOf(),
+                    axisPresetConfig = null,
                     selectedColorId = null,
                     colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
                     seedColor = null,