Bind switch color (2/2)

Create SwitchColorBinder for binding switch color and use it to bind
switches across the picker.

Flag: com.android.systemui.shared.new_customization_picker_ui
Test: manually verified by applying new system color
Bug: 363018910
Change-Id: I2c577684dfb2e21be63d21c41df3cdaea676845f
diff --git a/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt b/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt
index ef9e662..9e9db7a 100644
--- a/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt
+++ b/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt
@@ -21,6 +21,8 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.customization.picker.mode.ui.viewmodel.DarkModeViewModel
+import com.android.wallpaper.customization.ui.binder.SwitchColorBinder
+import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
 import com.google.android.material.materialswitch.MaterialSwitch
 import kotlinx.coroutines.launch
 
@@ -28,12 +30,28 @@
     fun bind(
         darkModeToggle: MaterialSwitch,
         viewModel: DarkModeViewModel,
+        colorUpdateViewModel: ColorUpdateViewModel,
+        shouldAnimateColor: () -> Boolean,
         lifecycleOwner: LifecycleOwner,
     ) {
         lifecycleOwner.lifecycleScope.launch {
             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch { viewModel.isEnabled.collect { darkModeToggle.isEnabled = it } }
-                launch { viewModel.previewingIsDarkMode.collect { darkModeToggle.isChecked = it } }
+                launch {
+                    var binding: SwitchColorBinder.Binding? = null
+                    viewModel.previewingIsDarkMode.collect {
+                        darkModeToggle.isChecked = it
+                        binding?.destroy()
+                        binding =
+                            SwitchColorBinder.bind(
+                                switch = darkModeToggle,
+                                isChecked = it,
+                                colorUpdateViewModel = colorUpdateViewModel,
+                                shouldAnimateColor = shouldAnimateColor,
+                                lifecycleOwner = lifecycleOwner,
+                            )
+                    }
+                }
                 launch {
                     viewModel.toggleDarkMode.collect {
                         darkModeToggle.setOnCheckedChangeListener { _, _ -> it.invoke() }
diff --git a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
index 3196257..480ae11 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
@@ -325,11 +325,25 @@
                 }
 
                 launch {
+                    var binding: SwitchColorBinder.Binding? = null
                     viewModel.previewingClockSize.collect { size ->
                         when (size) {
                             ClockSize.DYNAMIC -> clockSizeSwitch.isChecked = true
                             ClockSize.SMALL -> clockSizeSwitch.isChecked = false
                         }
+                        binding?.destroy()
+                        binding =
+                            SwitchColorBinder.bind(
+                                switch = clockSizeSwitch,
+                                isChecked =
+                                    when (size) {
+                                        ClockSize.DYNAMIC -> true
+                                        ClockSize.SMALL -> false
+                                    },
+                                colorUpdateViewModel = colorUpdateViewModel,
+                                shouldAnimateColor = isFloatingSheetActive,
+                                lifecycleOwner = lifecycleOwner,
+                            )
                     }
                 }
 
@@ -346,6 +360,8 @@
         bindClockFontContent(
             clockFontContent = clockFontContent,
             viewModel = viewModel,
+            colorUpdateViewModel = colorUpdateViewModel,
+            shouldAnimateColor = isFloatingSheetActive,
             lifecycleOwner = lifecycleOwner,
         )
     }
@@ -353,6 +369,8 @@
     private fun bindClockFontContent(
         clockFontContent: View,
         viewModel: ClockPickerViewModel,
+        colorUpdateViewModel: ColorUpdateViewModel,
+        shouldAnimateColor: () -> Boolean,
         lifecycleOwner: LifecycleOwner,
     ) {
         val sliderViewList =
@@ -409,9 +427,15 @@
                             viewHolder.setIsVisible(booleanAxis != null)
                             booleanAxis?.let {
                                 switchViewMap[it.key] = viewHolder
-                                viewHolder.initView(booleanAxis) { value ->
-                                    viewModel.updatePreviewFontAxis(booleanAxis.key, value)
-                                }
+                                viewHolder.initView(
+                                    clockFontAxis = booleanAxis,
+                                    onFontAxisValueUpdated = { value ->
+                                        viewModel.updatePreviewFontAxis(booleanAxis.key, value)
+                                    },
+                                    colorUpdateViewModel = colorUpdateViewModel,
+                                    shouldAnimateColor = shouldAnimateColor,
+                                    lifecycleOwner = lifecycleOwner,
+                                )
                             }
                         }
                     }
diff --git a/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
index a097828..4c6a3e9 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
@@ -110,6 +110,8 @@
         DarkModeBinder.bind(
             darkModeToggle = view.findViewById(R.id.dark_mode_toggle),
             viewModel = optionsViewModel.darkModeViewModel,
+            colorUpdateViewModel = colorUpdateViewModel,
+            shouldAnimateColor = isFloatingSheetActive,
             lifecycleOwner = lifecycleOwner,
         )
 
diff --git a/src/com/android/wallpaper/customization/ui/binder/SwitchColorBinder.kt b/src/com/android/wallpaper/customization/ui/binder/SwitchColorBinder.kt
new file mode 100644
index 0000000..2710916
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/binder/SwitchColorBinder.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2025 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.binder
+
+import android.content.res.ColorStateList
+import androidx.lifecycle.LifecycleOwner
+import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder
+import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
+import com.google.android.material.materialswitch.MaterialSwitch
+
+object SwitchColorBinder {
+
+    private const val COLOR_TRANSPARENT = 0
+
+    interface Binding {
+        /** Destroys the binding in spite of lifecycle state. */
+        fun destroy()
+    }
+
+    /**
+     * Binds the color of a [MaterialSwitch] using [ColorUpdateBinder] according to Material 3
+     * specs.
+     */
+    fun bind(
+        switch: MaterialSwitch,
+        isChecked: Boolean,
+        colorUpdateViewModel: ColorUpdateViewModel,
+        shouldAnimateColor: () -> Boolean,
+        lifecycleOwner: LifecycleOwner,
+    ): Binding {
+        val bindingThumb: ColorUpdateBinder.Binding
+        val bindingTrack: ColorUpdateBinder.Binding
+        if (isChecked) {
+            switch.trackDecorationTintList = ColorStateList.valueOf(COLOR_TRANSPARENT)
+            bindingThumb =
+                ColorUpdateBinder.bind(
+                    setColor = { color -> switch.thumbTintList = ColorStateList.valueOf(color) },
+                    color = colorUpdateViewModel.colorOnPrimary,
+                    shouldAnimate = shouldAnimateColor,
+                    lifecycleOwner = lifecycleOwner,
+                )
+            bindingTrack =
+                ColorUpdateBinder.bind(
+                    setColor = { color -> switch.trackTintList = ColorStateList.valueOf(color) },
+                    color = colorUpdateViewModel.colorPrimary,
+                    shouldAnimate = shouldAnimateColor,
+                    lifecycleOwner = lifecycleOwner,
+                )
+        } else {
+            bindingThumb =
+                ColorUpdateBinder.bind(
+                    setColor = { color ->
+                        switch.thumbTintList = ColorStateList.valueOf(color)
+                        switch.trackDecorationTintList = ColorStateList.valueOf(color)
+                    },
+                    color = colorUpdateViewModel.colorOutline,
+                    shouldAnimate = shouldAnimateColor,
+                    lifecycleOwner = lifecycleOwner,
+                )
+            bindingTrack =
+                ColorUpdateBinder.bind(
+                    setColor = { color -> switch.trackTintList = ColorStateList.valueOf(color) },
+                    color = colorUpdateViewModel.colorSurfaceContainerHighest,
+                    shouldAnimate = shouldAnimateColor,
+                    lifecycleOwner = lifecycleOwner,
+                )
+        }
+        return object : Binding {
+            override fun destroy() {
+                bindingThumb.destroy()
+                bindingTrack.destroy()
+            }
+        }
+    }
+}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index d8b14b2..cd65e0c 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -202,10 +202,10 @@
 
         val optionThemedIcons =
             homeScreenCustomizationOptionEntries
-                .find { it.first == ThemePickerHomeCustomizationOption.THEMED_ICONS }
-                ?.second
+                .first { it.first == ThemePickerHomeCustomizationOption.THEMED_ICONS }
+                .second
         val optionThemedIconsSwitch =
-            optionThemedIcons?.findViewById<MaterialSwitch>(R.id.option_entry_switch)
+            optionThemedIcons.requireViewById<MaterialSwitch>(R.id.option_entry_switch)
 
         lifecycleOwner.lifecycleScope.launch {
             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -315,24 +315,32 @@
                     }
                 }
 
-                if (optionThemedIconsSwitch != null) {
-                    launch {
-                        optionsViewModel.themedIconViewModel.isAvailable.collect { isAvailable ->
-                            optionThemedIconsSwitch.isEnabled = isAvailable
-                        }
+                launch {
+                    optionsViewModel.themedIconViewModel.isAvailable.collect { isAvailable ->
+                        optionThemedIconsSwitch.isEnabled = isAvailable
                     }
+                }
 
-                    launch {
-                        optionsViewModel.themedIconViewModel.isActivated.collect {
-                            optionThemedIconsSwitch.isChecked = it
-                        }
+                launch {
+                    var binding: SwitchColorBinder.Binding? = null
+                    optionsViewModel.themedIconViewModel.isActivated.collect {
+                        optionThemedIconsSwitch.isChecked = it
+                        binding?.destroy()
+                        binding =
+                            SwitchColorBinder.bind(
+                                switch = optionThemedIconsSwitch,
+                                isChecked = it,
+                                colorUpdateViewModel = colorUpdateViewModel,
+                                shouldAnimateColor = isOnMainScreen,
+                                lifecycleOwner = lifecycleOwner,
+                            )
                     }
+                }
 
-                    launch {
-                        optionsViewModel.themedIconViewModel.toggleThemedIcon.collect {
-                            optionThemedIconsSwitch.setOnCheckedChangeListener { _, _ ->
-                                launch { it.invoke() }
-                            }
+                launch {
+                    optionsViewModel.themedIconViewModel.toggleThemedIcon.collect {
+                        optionThemedIconsSwitch.setOnCheckedChangeListener { _, _ ->
+                            launch { it.invoke() }
                         }
                     }
                 }
diff --git a/src/com/android/wallpaper/customization/ui/view/ClockFontSwitchViewHolder.kt b/src/com/android/wallpaper/customization/ui/view/ClockFontSwitchViewHolder.kt
index 22e5dea..fcf70d3 100644
--- a/src/com/android/wallpaper/customization/ui/view/ClockFontSwitchViewHolder.kt
+++ b/src/com/android/wallpaper/customization/ui/view/ClockFontSwitchViewHolder.kt
@@ -18,7 +18,10 @@
 
 import android.widget.TextView
 import androidx.core.view.isVisible
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.plugins.clocks.ClockFontAxis
+import com.android.wallpaper.customization.ui.binder.SwitchColorBinder
+import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
 import com.google.android.material.materialswitch.MaterialSwitch
 import kotlin.math.abs
 
@@ -31,12 +34,35 @@
         switch.isVisible = isVisible
     }
 
-    fun initView(clockFontAxis: ClockFontAxis, onFontAxisValueUpdated: (value: Float) -> Unit) {
+    fun initView(
+        clockFontAxis: ClockFontAxis,
+        onFontAxisValueUpdated: (value: Float) -> Unit,
+        colorUpdateViewModel: ColorUpdateViewModel,
+        shouldAnimateColor: () -> Boolean,
+        lifecycleOwner: LifecycleOwner,
+    ) {
         switchMaxValue = clockFontAxis.maxValue
         name.text = clockFontAxis.name
         switch.apply {
             isChecked = abs(clockFontAxis.currentValue - clockFontAxis.maxValue) < 0.01f
+            var binding: SwitchColorBinder.Binding =
+                SwitchColorBinder.bind(
+                    switch = switch,
+                    isChecked = isChecked,
+                    colorUpdateViewModel = colorUpdateViewModel,
+                    shouldAnimateColor = shouldAnimateColor,
+                    lifecycleOwner = lifecycleOwner,
+                )
             setOnCheckedChangeListener { v, _ ->
+                binding.destroy()
+                binding =
+                    SwitchColorBinder.bind(
+                        switch = switch,
+                        isChecked = v.isChecked,
+                        colorUpdateViewModel = colorUpdateViewModel,
+                        shouldAnimateColor = shouldAnimateColor,
+                        lifecycleOwner = lifecycleOwner,
+                    )
                 val value = if (v.isChecked) clockFontAxis.maxValue else clockFontAxis.minValue
                 onFontAxisValueUpdated.invoke(value)
             }