Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2a10aeae authored by Catherine Liang's avatar Catherine Liang
Browse files

Bind customization option icon colors (2/2)

Bind all the customization option icons colors. Binding the color
contrast icon requires extra steps to separate the inner and outer
parts to bind their colors differently.

Flag: com.android.systemui.shared.new_customization_picker_ui
Test: manually verified
Bug: 363018910
Change-Id: Id12972b5f10e273241f91dd8e4094ffdf1b8d98c
parent 5e52d56b
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<!--
  ~ 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.
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/system_accent1_600"
      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
</vector>
 No newline at end of file
+25 −0
Original line number Diff line number Diff line
<!--
  ~ 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.
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/system_accent1_100"
      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
</vector>
 No newline at end of file
+12 −3
Original line number Diff line number Diff line
@@ -55,12 +55,21 @@
        android:background="@drawable/customization_option_entry_icon_background"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent">
        app:layout_constraintBottom_toBottomOf="parent"
        android:importantForAccessibility="noHideDescendants" >

        <ImageView
            android:id="@+id/option_entry_icon_inner_part"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/ic_contrast_inner_part"
            android:importantForAccessibility="no" />

        <ImageView
            android:id="@+id/option_entry_icon"
            android:id="@+id/option_entry_icon_outer_part"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="@string/grid_preview_card_content_description" />
            android:src="@drawable/ic_contrast_outer_part"
            android:importantForAccessibility="no" />
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
 No newline at end of file
+96 −0
Original line number Diff line number Diff line
/*
 * 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.customization.picker.settings.ui.binder

import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.android.themepicker.R
import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder
import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel

object ColorContrastSectionViewBinder2 {

    private const val TAG = "ColorContrastSectionViewBinder2"

    interface Binding {
        /** Destroys the binding in spite of lifecycle state. */
        fun destroy()
    }

    fun bind(
        view: View,
        contrast: Int,
        colorUpdateViewModel: ColorUpdateViewModel,
        shouldAnimateColor: () -> Boolean,
        lifecycleOwner: LifecycleOwner,
    ): Binding {

        val descriptionView: TextView = view.requireViewById(R.id.option_entry_description)
        val iconInner: ImageView = view.requireViewById(R.id.option_entry_icon_inner_part)
        val iconOuter: ImageView = view.requireViewById(R.id.option_entry_icon_outer_part)

        // Bind outer and inner parts of the contrast icon separately. Use the same material color
        // tokens despite contrast level because the tokens adjust according to contrast thanks to
        // dynamic color magic.
        val bindingOuter: ColorUpdateBinder.Binding =
            ColorUpdateBinder.bind(
                setColor = { color -> iconOuter.setColorFilter(color) },
                color = colorUpdateViewModel.colorPrimaryContainer,
                shouldAnimate = shouldAnimateColor,
                lifecycleOwner = lifecycleOwner,
            )
        val bindingInner: ColorUpdateBinder.Binding =
            ColorUpdateBinder.bind(
                setColor = { color -> iconInner.setColorFilter(color) },
                color = colorUpdateViewModel.colorPrimary,
                shouldAnimate = shouldAnimateColor,
                lifecycleOwner = lifecycleOwner,
            )

        TextViewBinder.bind(
            view = descriptionView,
            viewModel =
                when (contrast) {
                    CONTRAST_LEVEL_STANDARD -> Text.Resource(R.string.color_contrast_default_title)
                    CONTRAST_LEVEL_MEDIUM -> Text.Resource(R.string.color_contrast_medium_title)
                    CONTRAST_LEVEL_HIGH -> Text.Resource(R.string.color_contrast_high_title)
                    else -> {
                        iconInner.isVisible = false
                        iconOuter.isVisible = false
                        Log.e(TAG, "Invalid contrast value: $contrast")
                        throw IllegalArgumentException("Invalid contrast value: $contrast")
                    }
                },
        )

        return object : Binding {
            override fun destroy() {
                bindingInner.destroy()
                bindingOuter.destroy()
            }
        }
    }
}
+24 −21
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.widget.ImageView
import android.widget.Switch
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -40,6 +39,7 @@ import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder2
import com.android.customization.picker.color.ui.view.ColorOptionIconView2
import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
import com.android.customization.picker.grid.ui.binder.GridIconViewBinder
import com.android.customization.picker.settings.ui.binder.ColorContrastSectionViewBinder2
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockPreviewConfig
import com.android.systemui.shared.Flags
@@ -195,10 +195,6 @@ constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationO
                .first { it.first == ThemePickerHomeCustomizationOption.COLOR_CONTRAST }
                .second
        optionColorContrast.setOnClickListener { navigateToColorContrastSettingsActivity.invoke() }
        val optionColorContrastDescription: TextView =
            optionColorContrast.requireViewById(R.id.option_entry_description)
        val optionColorContrastIcon: ImageView =
            optionColorContrast.requireViewById(R.id.option_entry_icon)

        val optionThemedIcons =
            homeScreenCustomizationOptionEntries
@@ -207,6 +203,18 @@ constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationO
        val optionThemedIconsSwitch =
            optionThemedIcons?.findViewById<Switch>(R.id.option_entry_switch)

        ColorUpdateBinder.bind(
            setColor = { color ->
                optionClockIcon.setColorFilter(color)
                optionShortcutIcon1.setColorFilter(color)
                optionShortcutIcon2.setColorFilter(color)
                optionShapeGridIcon.setColorFilter(color)
            },
            color = colorUpdateViewModel.colorOnSurfaceVariant,
            shouldAnimate = isOnMainScreen,
            lifecycleOwner = lifecycleOwner,
        )

        lifecycleOwner.lifecycleScope.launch {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
@@ -270,28 +278,23 @@ constructor(private val defaultCustomizationOptionsBinder: DefaultCustomizationO
                                view = optionShapeGridIcon,
                                viewModel = gridIconViewModel,
                            )
                            // TODO(b/363018910): Use ColorUpdateBinder to update color
                            optionShapeGridIcon.setColorFilter(
                                ContextCompat.getColor(
                                    view.context,
                                    com.android.wallpaper.R.color.system_on_surface_variant,
                                )
                            )
                        }
                    }
                }

                launch {
                    optionsViewModel.colorContrastSectionViewModel.summary.collectLatest { summary
                    var binding: ColorContrastSectionViewBinder2.Binding? = null
                    optionsViewModel.colorContrastSectionViewModel.contrast.collectLatest { contrast
                        ->
                        TextViewBinder.bind(
                            view = optionColorContrastDescription,
                            viewModel = summary.description,
                        binding?.destroy()
                        binding =
                            ColorContrastSectionViewBinder2.bind(
                                view = optionColorContrast,
                                contrast = contrast,
                                colorUpdateViewModel = colorUpdateViewModel,
                                shouldAnimateColor = isOnMainScreen,
                                lifecycleOwner = lifecycleOwner,
                            )
                        summary.icon?.let {
                            IconViewBinder.bind(view = optionColorContrastIcon, viewModel = it)
                        }
                        optionColorContrastIcon.isVisible = summary.icon != null
                    }
                }

Loading