Clock floating sheet height animation

Clock floating sheet height should change with the content inside.

Test: Manually tested. See bug.
Bug: 350718184
Flag: com.android.wallpaper.new_picker_ui_flag
Change-Id: Ibe2832ef9471197a6804576b314772f351a83e2d
diff --git a/res/layout/floating_sheet_clock.xml b/res/layout/floating_sheet_clock.xml
index ff3a83a..4f114a6 100644
--- a/res/layout/floating_sheet_clock.xml
+++ b/res/layout/floating_sheet_clock.xml
@@ -21,6 +21,7 @@
     android:orientation="vertical">
 
     <FrameLayout
+        android:id="@+id/clock_floating_sheet_content_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingVertical="@dimen/floating_sheet_content_vertical_padding"
@@ -37,7 +38,7 @@
 
 
         <LinearLayout
-            android:id="@+id/clock_color_sheet_content"
+            android:id="@+id/clock_floating_sheet_color_content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
@@ -45,30 +46,36 @@
             android:clipChildren="false">
 
             <FrameLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-
-            <androidx.recyclerview.widget.RecyclerView
-                android:id="@+id/clock_color_list"
+                android:id="@+id/clock_color_list_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:clipToPadding="false"
                 android:clipChildren="false"
-                android:clipToPadding="false"/>
+                android:layout_marginBottom="12dp">
 
                 <!--
-                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.
+               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.
-                -->
+               It's critical for any TextViews inside the included layout to have text.
+               -->
                 <include
                     layout="@layout/clock_color_option_noop_item"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:visibility="invisible" />
+
+                <androidx.recyclerview.widget.RecyclerView
+                    android:id="@+id/clock_color_list"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:clipChildren="false"
+                    android:clipToPadding="false" />
+
+
             </FrameLayout>
 
 
@@ -87,7 +94,7 @@
         </LinearLayout>
 
         <LinearLayout
-            android:id="@+id/clock_size_container"
+            android:id="@+id/clock_floating_sheet_size_content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
diff --git a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
index 4cb9bb5..335d4fe 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
@@ -16,13 +16,16 @@
 
 package com.android.wallpaper.customization.ui.binder
 
+import android.animation.ValueAnimator
 import android.annotation.DrawableRes
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.drawable.Drawable
 import android.view.View
+import android.view.ViewGroup
 import android.widget.SeekBar
 import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.doOnLayout
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
@@ -36,6 +39,7 @@
 import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
 import com.android.customization.picker.common.ui.view.DoubleRowListItemSpacing
 import com.android.themepicker.R
+import com.android.wallpaper.customization.ui.viewmodel.ClockFloatingSheetHeightsViewModel
 import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel
 import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.Tab.COLOR
 import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.Tab.SIZE
@@ -45,11 +49,21 @@
 import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar.Tab.SECONDARY
 import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar.Tab.TERTIARY
 import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 object ClockFloatingSheetBinder {
     private const val SLIDER_ENABLED_ALPHA = 1f
     private const val SLIDER_DISABLED_ALPHA = .3f
+    private const val ANIMATION_DURATION = 200L
+
+    private val _clockFloatingSheetHeights: MutableStateFlow<ClockFloatingSheetHeightsViewModel?> =
+        MutableStateFlow(null)
+    private val clockFloatingSheetHeights: Flow<ClockFloatingSheetHeightsViewModel?> =
+        _clockFloatingSheetHeights.asStateFlow()
 
     fun bind(
         view: View,
@@ -78,11 +92,15 @@
                 setOnTabClick(TERTIARY) { viewModel.setTab(SIZE) }
             }
 
+        val container = view.requireViewById<ViewGroup>(R.id.clock_floating_sheet_content_container)
+
         // Clock style
         val clockList = view.requireViewById<View>(R.id.clock_list)
 
         // Cloc color
-        val clockColorSheetContent = view.requireViewById<View>(R.id.clock_color_sheet_content)
+        val clockColorSheetContent =
+            view.requireViewById<View>(R.id.clock_floating_sheet_color_content)
+        val clockColorListContainer = view.requireViewById<View>(R.id.clock_color_list_container)
         val clockColorAdapter =
             createOptionItemAdapter(
                 view.resources.configuration.uiMode,
@@ -112,22 +130,67 @@
         )
 
         // Clock size
-        val clockSizeContainer = view.requireViewById<View>(R.id.clock_size_container)
+        val clockSizeContainer = view.requireViewById<View>(R.id.clock_floating_sheet_size_content)
+
+        view.doOnLayout {
+            if (_clockFloatingSheetHeights.value == null) {
+                _clockFloatingSheetHeights.value =
+                    ClockFloatingSheetHeightsViewModel(
+                        clockStyleContentHeight = clockList.height,
+                        clockColorContentHeight = clockColorSheetContent.height,
+                        clockColorListHeight = clockColorListContainer.height,
+                        clockSizeContentHeight = clockSizeContainer.height,
+                    )
+            }
+        }
 
         lifecycleOwner.lifecycleScope.launch {
             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
-                    viewModel.selectedTab.collect {
-                        when (it) {
-                            STYLE -> tabs.setTabSelected(PRIMARY)
-                            COLOR -> tabs.setTabSelected(SECONDARY)
-                            SIZE -> tabs.setTabSelected(TERTIARY)
+                    combine(clockFloatingSheetHeights, viewModel.selectedTab) { heights, selectedTab
+                            ->
+                            heights to selectedTab
                         }
+                        .collect { (heights, selectedTab) ->
+                            heights ?: return@collect
+                            when (selectedTab) {
+                                STYLE -> tabs.setTabSelected(PRIMARY)
+                                COLOR -> tabs.setTabSelected(SECONDARY)
+                                SIZE -> tabs.setTabSelected(TERTIARY)
+                            }
+                            val targetHeight =
+                                when (selectedTab) {
+                                    STYLE -> heights.clockStyleContentHeight
+                                    COLOR -> heights.clockColorContentHeight
+                                    SIZE -> heights.clockSizeContentHeight
+                                } +
+                                    view.resources.getDimensionPixelSize(
+                                        R.dimen.floating_sheet_content_vertical_padding
+                                    ) * 2
 
-                        clockList.isVisible = it == STYLE
-                        clockColorSheetContent.isVisible = it == COLOR
-                        clockSizeContainer.isVisible = it == SIZE
-                    }
+                            val animationFloatingSheet =
+                                ValueAnimator.ofInt(container.height, targetHeight)
+                            animationFloatingSheet.addUpdateListener { valueAnimator ->
+                                val value = valueAnimator.animatedValue as Int
+                                container.layoutParams =
+                                    container.layoutParams.apply { height = value }
+                            }
+                            animationFloatingSheet.setDuration(ANIMATION_DURATION)
+                            animationFloatingSheet.start()
+
+                            // For some reason the color list layout collapses when we animate the
+                            // parent's height. This is to make sure the height stays as it is
+                            // firstly
+                            // inflated.
+                            clockColorList.layoutParams =
+                                clockColorList.layoutParams.apply {
+                                    height = heights.clockColorListHeight
+                                }
+
+                            clockList.isVisible = selectedTab == STYLE
+                            clockColorSheetContent.isVisible = selectedTab == COLOR
+                            clockSizeContainer.isVisible = selectedTab == SIZE
+                        }
                 }
 
                 launch {
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
new file mode 100644
index 0000000..37845cd
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+data class ClockFloatingSheetHeightsViewModel(
+    val clockStyleContentHeight: Int,
+    val clockColorContentHeight: Int,
+    val clockColorListHeight: Int,
+    val clockSizeContentHeight: Int,
+)