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

Commit 0587b45d authored by Catherine Liang's avatar Catherine Liang
Browse files

New Monochrome Color Preview

To allow Monochrome tile to be displayed similar to a wallpaper color,
code was refactored to use a singular color option class for both
wallpaper and basic colors going forward to allow more flexibility. The
ColorOptionImpl class unifies ColorBundle and ColorSeedOption for
revamped UI and beyond, since the two ColorOption types differ mainly
in their preview. This refactor will also move toward cleaner
architecture by removing ColorOption's ability to bind views in the
revamped UI and beyond, keeping it soley as a model.

Bug: 275625332
Bug: 270187739
Test: Manually tested in revamped UI and original UI
Test: Adjusted fake repository for new ColorOptionImpl model, interactor
and view-model unit tests still pass with the same logic

Change-Id: I5d61cecd7e17c2327a3b0eed52c5d7d57769ac62
parent a4e5a624
Loading
Loading
Loading
Loading
+110 −0
Original line number Diff line number Diff line
/*
 * 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.model.color

import android.content.Context
import android.view.View
import androidx.annotation.ColorInt
import com.android.customization.model.color.ColorOptionsProvider.ColorSource
import com.android.customization.picker.color.shared.model.ColorType
import com.android.systemui.monet.Style
import com.android.wallpaper.R

/**
 * Represents a color option in the revamped UI, it can be used for both wallpaper and preset colors
 */
class ColorOptionImpl(
    title: String?,
    overlayPackages: Map<String, String?>,
    isDefault: Boolean,
    private val source: String?,
    style: Style,
    index: Int,
    private val previewInfo: PreviewInfo,
    val type: ColorType,
) : ColorOption(title, overlayPackages, isDefault, style, index) {

    class PreviewInfo(
        @ColorInt val lightColors: IntArray,
        @ColorInt val darkColors: IntArray,
    ) : ColorOption.PreviewInfo {
        @ColorInt
        fun resolveColors(darkTheme: Boolean): IntArray {
            return if (darkTheme) darkColors else lightColors
        }
    }

    override fun bindThumbnailTile(view: View?) {
        // Do nothing. This function will no longer be used in the Revamped UI
    }

    override fun getLayoutResId(): Int {
        return R.layout.color_option
    }

    override fun getPreviewInfo(): PreviewInfo {
        return previewInfo
    }

    override fun getContentDescription(context: Context): CharSequence? {
        if (type == ColorType.WALLPAPER_COLOR) {
            return context.getString(R.string.wallpaper_color_title)
        }
        return super.getContentDescription(context)
    }

    override fun getSource(): String? {
        return source
    }

    class Builder {
        var title: String? = null

        @ColorInt var lightColors: IntArray = intArrayOf()

        @ColorInt var darkColors: IntArray = intArrayOf()

        @ColorSource var source: String? = null
        var isDefault = false
        var style = Style.TONAL_SPOT
        var index = 0
        var packages: MutableMap<String, String?> = HashMap()
        var type = ColorType.WALLPAPER_COLOR

        fun build(): ColorOptionImpl {
            return ColorOptionImpl(
                title,
                packages,
                isDefault,
                source,
                style,
                index,
                createPreviewInfo(),
                type
            )
        }

        private fun createPreviewInfo(): PreviewInfo {
            return PreviewInfo(lightColors, darkColors)
        }

        fun addOverlayPackage(category: String?, packageName: String?): ColorOptionImpl.Builder {
            category?.let { packages[category] = packageName }
            return this
        }
    }
}
+176 −64
Original line number Diff line number Diff line
@@ -34,11 +34,11 @@ import com.android.customization.model.ResourcesApkProvider
import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_HOME
import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_LOCK
import com.android.customization.model.color.ColorUtils.toColorString
import com.android.customization.picker.color.shared.model.ColorType
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.wallpaper.compat.WallpaperManagerCompat
import com.android.wallpaper.module.InjectorProvider
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -101,7 +101,7 @@ class ColorProvider(context: Context, stubPackageName: String) :
            scope.launch {
                try {
                    if (colorBundles == null || reload) {
                        loadPreset()
                        loadPreset(shouldUseRevampedUi)
                    }
                    if (wallpaperColorsChanged || reload) {
                        loadSeedColors(
@@ -176,7 +176,16 @@ class ColorProvider(context: Context, stubPackageName: String) :
            )
        }

        if (shouldUseRevampedUi) {
            bundles.addAll(
                colorBundles?.filterNot {
                    (it as ColorOptionImpl).type == ColorType.WALLPAPER_COLOR
                }
                    ?: emptyList()
            )
        } else {
            bundles.addAll(colorBundles?.filterNot { it is ColorSeedOption } ?: emptyList())
        }
        colorBundles = bundles
    }

@@ -206,18 +215,28 @@ class ColorProvider(context: Context, stubPackageName: String) :
    ) {
        // TODO(b/202145216): Measure time cost in the loop.
        for (style in styleList) {
            val builder = ColorSeedOption.Builder()
            val lightColorScheme = ColorScheme(colorInt, /* darkTheme= */ false, style)
            val darkColorScheme = ColorScheme(colorInt, /* darkTheme= */ true, style)
            builder
                .setLightColors(
                    if (shouldUseRevampedUi) lightColorScheme.getRevampedUILightColorPreview()
                    else lightColorScheme.getLightColorPreview()
                )
                .setDarkColors(
                    if (shouldUseRevampedUi) darkColorScheme.getRevampedUIDarkColorPreview()
                    else darkColorScheme.getDarkColorPreview()
            if (shouldUseRevampedUi) {
                val builder = ColorOptionImpl.Builder()
                builder.lightColors = lightColorScheme.getRevampedUILightColorPreview()
                builder.darkColors = darkColorScheme.getRevampedUIDarkColorPreview()
                builder.addOverlayPackage(
                    OVERLAY_CATEGORY_SYSTEM_PALETTE,
                    if (isDefault) "" else toColorString(colorInt)
                )
                builder.source = source
                builder.style = style
                // Color option index value starts from 1.
                builder.index = i + 1
                builder.isDefault = isDefault
                builder.type = ColorType.WALLPAPER_COLOR
                bundles.add(builder.build())
            } else {
                val builder = ColorSeedOption.Builder()
                builder
                    .setLightColors(lightColorScheme.getLightColorPreview())
                    .setDarkColors(darkColorScheme.getDarkColorPreview())
                    .addOverlayPackage(
                        OVERLAY_CATEGORY_SYSTEM_PALETTE,
                        if (isDefault) "" else toColorString(colorInt)
@@ -236,10 +255,11 @@ class ColorProvider(context: Context, stubPackageName: String) :
                bundles.add(builder.build())
            }
        }
    }

    /**
     * Returns the colors for the light theme version of the preview of a ColorScheme based on this
     * order: |-------| | 0 | 1 | |---+---| | 2 | 3 | |-------|
     * order: top left, top right, bottom left, bottom right
     */
    @ColorInt
    private fun ColorScheme.getLightColorPreview(): IntArray {
@@ -263,13 +283,17 @@ class ColorProvider(context: Context, stubPackageName: String) :

    /**
     * Returns the color for the dark theme version of the preview of a ColorScheme based on this
     * order: |-------| | 0 | 1 | |---+---| | 2 | 3 | |-------|
     * order: top left, top right, bottom left, bottom right
     */
    @ColorInt
    private fun ColorScheme.getDarkColorPreview(): IntArray {
        return getLightColorPreview()
    }

    /**
     * Returns the light theme version of the Revamped UI preview of a ColorScheme based on this
     * order: top left, top right, bottom left, bottom right
     */
    @ColorInt
    private fun ColorScheme.getRevampedUILightColorPreview(): IntArray {
        return intArrayOf(
@@ -281,8 +305,8 @@ class ColorProvider(context: Context, stubPackageName: String) :
    }

    /**
     * Returns the color for the dark theme version of the preview of a ColorScheme based on this
     * order: |-------| | 0 | 1 | |---+---| | 2 | 3 | |-------|
     * Returns the dark theme version of the Revamped UI preview of a ColorScheme based on this
     * order: top left, top right, bottom left, bottom right
     */
    @ColorInt
    private fun ColorScheme.getRevampedUIDarkColorPreview(): IntArray {
@@ -294,6 +318,25 @@ class ColorProvider(context: Context, stubPackageName: String) :
        )
    }

    /**
     * Returns the Revamped UI preview of a preset ColorScheme based on this order: top left, top
     * right, bottom left, bottom right
     */
    private fun ColorScheme.getRevampedUIPresetColorPreview(seed: Int): IntArray {
        val colors =
            when (this.style) {
                Style.FRUIT_SALAD -> intArrayOf(seed, this.accent1.s100)
                Style.TONAL_SPOT -> intArrayOf(this.accentColor, this.accentColor)
                else -> intArrayOf(this.accent1.s100, this.accent1.s100)
            }
        return intArrayOf(
            colors[0],
            colors[1],
            colors[0],
            colors[1],
        )
    }

    private fun ColorScheme.getPresetColorPreview(seed: Int): IntArray {
        return when (this.style) {
            Style.FRUIT_SALAD -> intArrayOf(seed, this.accent1.s100)
@@ -307,7 +350,7 @@ class ColorProvider(context: Context, stubPackageName: String) :
        }
    }

    private suspend fun loadPreset() =
    private suspend fun loadPreset(shouldUseRevampedUi: Boolean) =
        withContext(Dispatchers.IO) {
            val extractor = ColorBundlePreviewExtractor(mContext)
            val bundles: MutableList<ColorOption> = ArrayList()
@@ -317,10 +360,74 @@ class ColorProvider(context: Context, stubPackageName: String) :
            var index = 1
            val maxPresetColors = if (themeStyleEnabled) bundleNames.size else MAX_PRESET_COLORS
            for (bundleName in bundleNames.take(maxPresetColors)) {
                if (shouldUseRevampedUi) {
                    val builder = ColorOptionImpl.Builder()
                    builder.title = getItemStringFromStub(COLOR_BUNDLE_NAME_PREFIX, bundleName)
                    builder.index = index
                    builder.source = ColorOptionsProvider.COLOR_SOURCE_PRESET
                    builder.type = ColorType.PRESET_COLOR
                    val colorFromStub =
                        getItemColorFromStub(COLOR_BUNDLE_MAIN_COLOR_PREFIX, bundleName)
                    var darkColorScheme = ColorScheme(colorFromStub, /* darkTheme= */ true)
                    var lightColorScheme = ColorScheme(colorFromStub, /* darkTheme= */ false)
                    val lightColor = lightColorScheme.accentColor
                    val darkColor = darkColorScheme.accentColor
                    var lightColors = intArrayOf(lightColor, lightColor, lightColor, lightColor)
                    var darkColors = intArrayOf(darkColor, darkColor, darkColor, darkColor)
                    builder.addOverlayPackage(OVERLAY_CATEGORY_COLOR, toColorString(colorFromStub))
                    builder.addOverlayPackage(
                        OVERLAY_CATEGORY_SYSTEM_PALETTE,
                        toColorString(colorFromStub)
                    )
                    if (themeStyleEnabled) {
                        val styleName =
                            try {
                                getItemStringFromStub(COLOR_BUNDLE_STYLE_PREFIX, bundleName)
                            } catch (e: Resources.NotFoundException) {
                                null
                            }
                        val style =
                            try {
                                if (styleName != null) Style.valueOf(styleName)
                                else Style.TONAL_SPOT
                            } catch (e: IllegalArgumentException) {
                                Style.TONAL_SPOT
                            }
                        builder.style = style

                        lightColorScheme = ColorScheme(colorFromStub, /* darkTheme= */ false, style)
                        darkColorScheme = ColorScheme(colorFromStub, /* darkTheme= */ true, style)

                        when (style) {
                            Style.MONOCHROMATIC -> {
                                if (
                                    !InjectorProvider.getInjector()
                                        .getFlags()
                                        .isMonochromaticThemeEnabled(mContext)
                                ) {
                                    continue
                                }
                                darkColors = darkColorScheme.getRevampedUIDarkColorPreview()
                                lightColors = lightColorScheme.getRevampedUILightColorPreview()
                            }
                            else -> {
                                darkColors =
                                    darkColorScheme.getRevampedUIPresetColorPreview(colorFromStub)
                                lightColors =
                                    lightColorScheme.getRevampedUIPresetColorPreview(colorFromStub)
                            }
                        }
                    }
                    builder.lightColors = lightColors
                    builder.darkColors = darkColors

                    bundles.add(builder.build())
                } else {
                    val builder = ColorBundle.Builder()
                    builder.title = getItemStringFromStub(COLOR_BUNDLE_NAME_PREFIX, bundleName)
                    builder.setIndex(index)
                val colorFromStub = getItemColorFromStub(COLOR_BUNDLE_MAIN_COLOR_PREFIX, bundleName)
                    val colorFromStub =
                        getItemColorFromStub(COLOR_BUNDLE_MAIN_COLOR_PREFIX, bundleName)
                    extractor.addPrimaryColor(builder, colorFromStub)
                    extractor.addSecondaryColor(builder, colorFromStub)
                    if (themeStyleEnabled) {
@@ -333,7 +440,8 @@ class ColorProvider(context: Context, stubPackageName: String) :
                        extractor.addColorStyle(builder, styleName)
                        val style =
                            try {
                            if (styleName != null) Style.valueOf(styleName) else Style.TONAL_SPOT
                                if (styleName != null) Style.valueOf(styleName)
                                else Style.TONAL_SPOT
                            } catch (e: IllegalArgumentException) {
                                Style.TONAL_SPOT
                            }
@@ -348,17 +456,21 @@ class ColorProvider(context: Context, stubPackageName: String) :
                        }

                        val darkColors =
                        ColorScheme(colorFromStub, true, style).getPresetColorPreview(colorFromStub)
                            ColorScheme(colorFromStub, /* darkTheme= */ true, style)
                                .getPresetColorPreview(colorFromStub)
                        val lightColors =
                        ColorScheme(colorFromStub, false, style)
                            ColorScheme(colorFromStub, /* darkTheme= */ false, style)
                                .getPresetColorPreview(colorFromStub)
                    builder.setColorPrimaryDark(darkColors[0]).setColorSecondaryDark(darkColors[1])
                        builder
                            .setColorPrimaryDark(darkColors[0])
                            .setColorSecondaryDark(darkColors[1])
                        builder
                            .setColorPrimaryLight(lightColors[0])
                            .setColorSecondaryLight(lightColors[1])
                    }

                    bundles.add(builder.build(mContext))
                }
                index++
            }

+24 −45
Original line number Diff line number Diff line
@@ -20,8 +20,7 @@ import androidx.core.graphics.ColorUtils
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.customization.model.color.ColorBundle
import com.android.customization.model.color.ColorSeedOption
import com.android.customization.model.color.ColorOptionImpl
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
@@ -201,11 +200,10 @@ private constructor(
    private suspend fun ColorOptionModel.toOptionItemViewModel(
        context: Context
    ): OptionItemViewModel<ColorOptionIconViewModel> {
        val optionItemPayload =
            when (colorOption) {
                is ColorSeedOption -> {
        val lightThemeColors =
                        colorOption.previewInfo.resolveColors(
            (colorOption as ColorOptionImpl)
                .previewInfo
                .resolveColors(
                    /** darkTheme= */
                    false
                )
@@ -214,6 +212,10 @@ private constructor(
                /** darkTheme= */
                true
            )
        val isSelectedFlow = selectedColorId.map { it == null }.stateIn(viewModelScope)
        return OptionItemViewModel<ColorOptionIconViewModel>(
            key = MutableStateFlow(key) as StateFlow<String>,
            payload =
                ColorOptionIconViewModel(
                    lightThemeColor0 = lightThemeColors[0],
                    lightThemeColor1 = lightThemeColors[1],
@@ -223,30 +225,7 @@ private constructor(
                    darkThemeColor1 = darkThemeColors[1],
                    darkThemeColor2 = darkThemeColors[2],
                    darkThemeColor3 = darkThemeColors[3],
                    )
                }
                is ColorBundle -> {
                    val primaryColor =
                        colorOption.previewInfo.resolvePrimaryColor(context.resources)
                    val secondaryColor =
                        colorOption.previewInfo.resolveSecondaryColor(context.resources)
                    ColorOptionIconViewModel(
                        lightThemeColor0 = primaryColor,
                        lightThemeColor1 = secondaryColor,
                        lightThemeColor2 = primaryColor,
                        lightThemeColor3 = secondaryColor,
                        darkThemeColor0 = primaryColor,
                        darkThemeColor1 = secondaryColor,
                        darkThemeColor2 = primaryColor,
                        darkThemeColor3 = secondaryColor,
                    )
                }
                else -> null
            }
        val isSelectedFlow = selectedColorId.map { it == null }.stateIn(viewModelScope)
        return OptionItemViewModel<ColorOptionIconViewModel>(
            key = MutableStateFlow(key) as StateFlow<String>,
            payload = optionItemPayload,
                ),
            text = Text.Loaded(context.getString(R.string.default_theme_title)),
            isTextUserVisible = true,
            isSelected = isSelectedFlow,
+12 −12
Original line number Diff line number Diff line
@@ -19,10 +19,9 @@ package com.android.customization.picker.color.data.repository
import android.app.WallpaperColors
import android.util.Log
import com.android.customization.model.CustomizationManager
import com.android.customization.model.color.ColorBundle
import com.android.customization.model.color.ColorCustomizationManager
import com.android.customization.model.color.ColorOption
import com.android.customization.model.color.ColorSeedOption
import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
import com.android.systemui.monet.Style
@@ -60,10 +59,11 @@ class ColorPickerRepositoryImpl(
                                val presetColorOptions: MutableList<ColorOptionModel> =
                                    mutableListOf()
                                options?.forEach { option ->
                                    when (option) {
                                        is ColorSeedOption ->
                                    when ((option as ColorOptionImpl).type) {
                                        ColorType.WALLPAPER_COLOR ->
                                            wallpaperColorOptions.add(option.toModel())
                                        is ColorBundle -> presetColorOptions.add(option.toModel())
                                        ColorType.PRESET_COLOR ->
                                            presetColorOptions.add(option.toModel())
                                    }
                                }
                                continuation.resumeWith(
@@ -113,16 +113,16 @@ class ColorPickerRepositoryImpl(
        val overlays = colorManager.currentOverlays
        val styleOrNull = colorManager.currentStyle
        val style = styleOrNull?.let { Style.valueOf(it) } ?: Style.TONAL_SPOT
        val colorOptionBuilder =
            // Does not matter whether ColorSeedOption or ColorBundle builder is used here
            // because to apply the color, one just needs a generic ColorOption
            ColorSeedOption.Builder().setSource(colorManager.currentColorSource).setStyle(style)
        val source = colorManager.currentColorSource
        val colorOptionBuilder = ColorOptionImpl.Builder()
        colorOptionBuilder.source = source
        colorOptionBuilder.style = style
        for (overlay in overlays) {
            colorOptionBuilder.addOverlayPackage(overlay.key, overlay.value)
        }
        val colorOption = colorOptionBuilder.build()
        return ColorOptionModel(
            key = "${colorOption.style}::${colorOption.serializedPackages}",
            key = "",
            colorOption = colorOption,
            isSelected = false,
        )
@@ -132,9 +132,9 @@ class ColorPickerRepositoryImpl(
        return colorManager.currentColorSource
    }

    private fun ColorOption.toModel(): ColorOptionModel {
    private fun ColorOptionImpl.toModel(): ColorOptionModel {
        return ColorOptionModel(
            key = "${this.style}::${this.serializedPackages}",
            key = "${this.type}::${this.style}::${this.serializedPackages}",
            colorOption = this,
            isSelected = isActive(colorManager),
        )
+29 −32

File changed.

Preview size limit exceeded, changes collapsed.

Loading