ColorScheme.kt

/*
 * Copyright 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 androidx.wear.compose.material3

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse

/**
 * A [ColorScheme] holds all the named color parameters for a [MaterialTheme].
 *
 * Color schemes are designed to be harmonious, ensure accessible text, and distinguish UI
 * elements and surfaces from one another.
 *
 * The Material color system and custom schemes provide default values for color as a starting point
 * for customization.
 *
 * To learn more about color schemes,
 * see [Material Design Color System](https://m3.material.io/styles/color/the-color-system/color-roles).
 *
 * @property primary The primary color is the color displayed most frequently across your app’s
 * screens and components.
 * @property primaryDim is less prominent than [primary] for component backgrounds
 * @property primaryContainer is a standout container color for key components.
 * @property onPrimary Color used for text and icons displayed on top of the primary color.
 * @property onPrimaryContainer The color (and state variants) that should be used for content on
 * top of [primaryContainer].
 * @property secondary The secondary color provides more ways to accent and distinguish your
 * product.
 * @property secondaryDim is less prominent than [secondary] for component backgrounds.
 * @property secondaryContainer A tonal color to be used in containers.
 * @property onSecondary Color used for text and icons displayed on top of the secondary color.
 * @property onSecondaryContainer The color (and state variants) that should be used for content on
 * top of [secondaryContainer].
 * @property tertiary The tertiary color that can be used to balance primary and secondary
 * colors, or bring heightened attention to an element.
 * @property tertiaryDim A less prominent tertiary color that can be used to balance
 * primary and secondary colors, or bring heightened attention to an element.
 * @property tertiaryContainer A tonal color to be used in containers.
 * @property onTertiary Color used for text and icons displayed on top of the tertiary color.
 * @property onTertiaryContainer The color (and state variants) that should be used for content on
 * top of [tertiaryContainer].
 * @property surfaceDim A surface color used for large containment components
 * such as Card and Button with low prominence.
 * @property surface The main surface color that affect surfaces of components with large
 * containment areas, such as Card and Button.
 * @property surfaceBright A surface color used for large containment components
 * such Card and Button with high prominence.
 * @property onSurface Color used for text and icons displayed on top of the surface color.
 * @property onSurfaceVariant The color for secondary text and icons on top of
 * [surface].
 * @property outline The main color for primary outline components.
 * The outline color role adds contrast for accessibility purposes.
 * @property outlineVariant The secondary color for secondary outline components.
 * @property background The background color that appears behind other content.
 * @property onBackground Color used for text and icons displayed on top of the background color.
 * @property error The error color is used to indicate errors.
 * @property onError Color used for text and icons displayed on top of the error color.
 */@Stable
public class ColorScheme(
    primary: Color = Color(0xFFD3E3FD),
    primaryDim: Color = Color(0xFFA8C7FA),
    primaryContainer: Color = Color(0xFF04409F),
    onPrimary: Color = Color(0xFF001944),
    onPrimaryContainer: Color = Color(0xFFD3E3FD),
    secondary: Color = Color(0xFFC2E7FF),
    secondaryDim: Color = Color(0xFF7FCFFF),
    secondaryContainer: Color = Color(0xFF004A77),
    onSecondary: Color = Color(0xFF001D35),
    onSecondaryContainer: Color = Color(0xFFC2E7FF),
    tertiary: Color = Color(0xFFC3EDCF),
    tertiaryDim: Color = Color(0xFF73DC92),
    tertiaryContainer: Color = Color(0xFF0F5223),
    onTertiary: Color = Color(0xFF02210C),
    onTertiaryContainer: Color = Color(0xFFC3EDCF),
    surfaceDim: Color = Color(0xFF252626),
    surface: Color = Color(0xFF303030),
    surfaceBright: Color = Color(0xFF474747),
    onSurface: Color = Color(0xFFF2F2F2),
    onSurfaceVariant: Color = Color(0xFFC4C7C5),
    outline: Color = Color(0xFF8E918F),
    outlineVariant: Color = Color(0xFF5C5F5E),
    background: Color = Color.Black,
    onBackground: Color = Color(0xFFFFFFFF),
    error: Color = Color(0xFFFD7267),
    onError: Color = Color(0xFF410002),
) {
    /**
     * [primary] is the main color used across screens and components
     */
    public var primary: Color by mutableStateOf(primary)
        internal set

    /**
     * [primaryDim] is less prominent than [primary] for component backgrounds
     */
    public var primaryDim: Color by mutableStateOf(primaryDim)
        internal set

    /**
     * [primaryContainer] is a standout container color for key components
     */
    public var primaryContainer: Color by mutableStateOf(primaryContainer)
        internal set

    /**
     * [onPrimary] is for text and icons shown against the Primary and primaryDim colors
     */
    public var onPrimary: Color by mutableStateOf(onPrimary)
        internal set

    /**
     * [onPrimaryContainer] is a contrast-passing color shown against the primaryContainer
     */
    public var onPrimaryContainer: Color by mutableStateOf(onPrimaryContainer)
        internal set

    /**
     * [secondary] is an accent color used across screens and components
     */
    public var secondary: Color by mutableStateOf(secondary)
        internal set

    /**
     * [secondaryDim] is less prominent than [secondary] for component backgrounds
     */
    public var secondaryDim: Color by mutableStateOf(secondaryDim)

    /**
     * [secondaryContainer] is a less prominent container color than [primaryContainer],
     * for components like tonal buttons
     */
    public var secondaryContainer: Color by mutableStateOf(secondaryContainer)
        internal set

    /**
     * [onSecondary] is for text and icons shown against the Secondary and SecondaryDim colors
     */
    public var onSecondary: Color by mutableStateOf(onSecondary)
        internal set

    /**
     * [onSecondaryContainer] is a contrast-passing color shown against the secondaryContainer
     */
    public var onSecondaryContainer: Color by mutableStateOf(onSecondaryContainer)
        internal set

    /**
     * [tertiary] is a complementary color to create contrast and draw attention to elements
     */
    public var tertiary: Color by mutableStateOf(tertiary)
        internal set

    /**
     * [tertiaryDim] is less prominent than [tertiary] for component backgrounds
     */
    public var tertiaryDim: Color by mutableStateOf(tertiaryDim)
        internal set

    /**
     * [tertiaryContainer] is a contrasting container color for components
     */
    public var tertiaryContainer: Color by mutableStateOf(tertiaryContainer)
        internal set

    /**
     * [onTertiary] is for text and icons shown against the Tertiary and tertiaryDim colors
     */
    public var onTertiary: Color by mutableStateOf(onTertiary)
        internal set

    /**
     * [onTertiaryContainer] is a contrast-passing color shown against the tertiaryContainer
     */
    public var onTertiaryContainer: Color by mutableStateOf(onTertiaryContainer)
        internal set

    /**
     * [surfaceDim] is a surface color used for large containment components, with the
     * lowest prominence behind Surface and surfaceBright
     */
    public var surfaceDim: Color by mutableStateOf(surfaceDim)
        internal set

    /**
     * [surface] is the main color for large containment components like card and button backgrounds
     */
    public var surface: Color by mutableStateOf(surface)
        internal set

    /**
     * [surfaceBright] is a surface color used for large containment components, with
     * the highest prominence, ahead of Surface and surfaceDim
     */
    public var surfaceBright: Color by mutableStateOf(surfaceBright)
        internal set

    /**
     * [onSurface] for primary text and icons shown against the
     * [surface], [surfaceDim] and [surfaceBright]
     */
    public var onSurface: Color by mutableStateOf(onSurface)
        internal set

    /**
     * [onSurfaceVariant] for secondary text and icons on
     * [surface], [surfaceDim] and [surfaceBright]
     */
    public var onSurfaceVariant: Color by mutableStateOf(onSurfaceVariant)
        internal set

    /**
     * [outline] is the main color for primary outline components
     */
    public var outline: Color by mutableStateOf(outline)
        internal set

    /**
     * [outlineVariant] is the secondary color for secondary outline components
     */
    public var outlineVariant: Color by mutableStateOf(outlineVariant)
        internal set

    /**
     * [background] is the static color used behind all texts and components
     */
    public var background: Color by mutableStateOf(background)
        internal set

    /**
     * [onBackground] is used for text and icons shown against the background color
     */
    public var onBackground: Color by mutableStateOf(onBackground)
        internal set

    /**
     * [error] indicates errors and emergency states
     */
    public var error: Color by mutableStateOf(error)
        internal set

    /**
     * [onError] is used for text and icons on the error color
     */
    public var onError: Color by mutableStateOf(onError)
        internal set

    /**
     * Returns a copy of this Colors, optionally overriding some of the values.
     */
    public fun copy(
        primary: Color = this.primary,
        primaryDim: Color = this.primaryDim,
        primaryContainer: Color = this.primaryContainer,
        onPrimary: Color = this.onPrimary,
        onPrimaryContainer: Color = this.onPrimaryContainer,
        secondary: Color = this.secondary,
        secondaryDim: Color = this.secondaryDim,
        secondaryContainer: Color = this.secondaryContainer,
        onSecondary: Color = this.onSecondary,
        onSecondaryContainer: Color = this.onSecondaryContainer,
        tertiary: Color = this.tertiary,
        tertiaryDim: Color = this.tertiaryDim,
        tertiaryContainer: Color = this.tertiaryContainer,
        onTertiary: Color = this.onTertiary,
        onTertiaryContainer: Color = this.onTertiaryContainer,
        surfaceDim: Color = this.surfaceDim,
        surface: Color = this.surface,
        surfaceBright: Color = this.surfaceBright,
        onSurface: Color = this.onSurface,
        onSurfaceVariant: Color = this.onSurfaceVariant,
        outline: Color = this.outline,
        outlineVariant: Color = this.outlineVariant,
        background: Color = this.background,
        onBackground: Color = this.onBackground,
        error: Color = this.error,
        onError: Color = this.onError
    ): ColorScheme = ColorScheme(
        primary = primary,
        primaryDim = primaryDim,
        primaryContainer = primaryContainer,
        onPrimary = onPrimary,
        onPrimaryContainer = onPrimaryContainer,
        secondary = secondary,
        secondaryDim = secondaryDim,
        secondaryContainer = secondaryContainer,
        onSecondary = onSecondary,
        onSecondaryContainer = onSecondaryContainer,
        tertiary = tertiary,
        tertiaryDim = tertiaryDim,
        tertiaryContainer = tertiaryContainer,
        onTertiary = onTertiary,
        onTertiaryContainer = onTertiaryContainer,
        surfaceDim = surfaceDim,
        surface = surface,
        surfaceBright = surfaceBright,
        onSurface = onSurface,
        onSurfaceVariant = onSurfaceVariant,
        outline = outline,
        outlineVariant = outlineVariant,
        background = background,
        onBackground = onBackground,
        error = error,
        onError = onError
    )

    override fun toString(): String {
        return "Colors(" +
            "primary=$primary, " +
            "primaryDim=$primaryDim, " +
            "primaryContainer=$primaryContainer, " +
            "onPrimary=$onPrimary, " +
            "onPrimaryContainer=$onPrimaryContainer, " +
            "secondary=$secondary, " +
            "secondaryDim=$secondaryDim, " +
            "secondaryContainer=$secondaryContainer, " +
            "onSecondary=$onSecondary, " +
            "onSecondaryContainer=$onSecondaryContainer, " +
            "tertiary=$tertiary, " +
            "tertiaryDim=$tertiaryDim, " +
            "tertiaryContainer=$tertiaryContainer, " +
            "onTertiary=$onTertiary, " +
            "onTertiaryContainer=$onTertiaryContainer, " +
            "surfaceDim=$surfaceDim, " +
            "surface=$surface, " +
            "surfaceBright=$surfaceBright, " +
            "onSurface=$onSurface, " +
            "onSurfaceVariant=$onSurfaceVariant, " +
            "outline=$outline, " +
            "outlineVariant=$outlineVariant, " +
            "background=$background, " +
            "onBackground=$onBackground, " +
            "error=$error, " +
            "onError=$onError" +
            ")"
    }
}

/**
 * The Material color system contains pairs of colors that are typically used for the background
 * and content color inside a component. For example, a Button typically uses `primary` for its
 * background, and `onPrimary` for the color of its content (usually text or iconography).
 *
 * This function tries to match the provided [backgroundColor] to a 'background' color in this
 * [ColorScheme], and then will return the corresponding color used for content. For example, when
 * [backgroundColor] is [ColorScheme.primary], this will return [ColorScheme.onPrimary].
 *
 * If [backgroundColor] does not match a background color in the theme, this will return
 * [Color.Unspecified].
 *
 * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
 * the theme's [ColorScheme], then returns [Color.Unspecified].
 *
 * @see contentColorFor
 */
public fun ColorScheme.contentColorFor(backgroundColor: Color): Color {
    return when (backgroundColor) {
        primary, primaryDim -> onPrimary
        primaryContainer -> onPrimaryContainer
        secondary, secondaryDim -> onSecondary
        secondaryContainer -> onSecondaryContainer
        tertiary, tertiaryDim -> onTertiary
        tertiaryContainer -> onTertiaryContainer
        surface, surfaceDim, surfaceBright -> onSurface
        background -> onBackground
        error -> onError
        else -> Color.Unspecified
    }
}

/**
 * The Material color system contains pairs of colors that are typically used for the background
 * and content color inside a component. For example, a Button typically uses `primary` for its
 * background, and `onPrimary` for the color of its content (usually text or iconography).
 *
 * This function tries to match the provided [backgroundColor] to a 'background' color in this
 * [ColorScheme], and then will return the corresponding color used for content. For example, when
 * [backgroundColor] is [ColorScheme.primary], this will return [ColorScheme.onPrimary].
 *
 * If [backgroundColor] does not match a background color in the theme, this will return
 * the current value of [LocalContentColor] as a best-effort color.
 *
 * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
 * the theme's [ColorScheme], then returns the current value of [LocalContentColor].
 *
 * @see ColorScheme.contentColorFor
 */
@Composable
@ReadOnlyComposable
public fun contentColorFor(backgroundColor: Color): Color =
    MaterialTheme.colorScheme
        .contentColorFor(backgroundColor)
        .takeOrElse { LocalContentColor.current }

/**
 * Updates the internal values of the given [ColorScheme] with values from the [other] [ColorScheme]. This
 * allows efficiently updating a subset of [ColorScheme], without recomposing every composable that
 * consumes values from [LocalColors].
 *
 * Because [ColorScheme] is very wide-reaching, and used by many expensive composables in the
 * hierarchy, providing a new value to [LocalColors] causes every composable consuming
 * [LocalColors] to recompose, which is prohibitively expensive in cases such as animating one
 * color in the theme. Instead, [ColorScheme] is internally backed by [mutableStateOf], and this
 * function mutates the internal state of [this] to match values in [other]. This means that any
 * changes will mutate the internal state of [this], and only cause composables that are reading
 * the specific changed value to recompose.
 */
internal fun ColorScheme.updateColorSchemeFrom(other: ColorScheme) {
    primary = other.primary
    primaryDim = other.primaryDim
    primaryContainer = other.primaryContainer
    onPrimary = other.onPrimary
    onPrimaryContainer = other.onPrimaryContainer
    secondary = other.secondary
    secondaryDim = other.secondaryDim
    secondaryContainer = other.secondaryContainer
    onSecondary = other.onSecondary
    onSecondaryContainer = other.onSecondaryContainer
    tertiary = other.tertiary
    tertiaryDim = other.tertiaryDim
    tertiaryContainer = other.tertiaryContainer
    onTertiary = other.onTertiary
    onTertiaryContainer = other.onTertiaryContainer
    surfaceDim = other.surfaceDim
    surface = other.surface
    surfaceBright = other.surfaceBright
    onSurface = other.onSurface
    onSurfaceVariant = other.onSurfaceVariant
    outline = other.outline
    outlineVariant = other.outlineVariant
    background = other.background
    onBackground = other.onBackground
    error = other.error
    onError = other.onError
}

internal val LocalColors = staticCompositionLocalOf<ColorScheme> { ColorScheme() }