blob: 556560c3534c8f5168dea6b0dbdfe81225e27aee [file] [log] [blame]
/*
* Copyright (C) 2022 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.systemui.media
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.drawable.RippleDrawable
import com.android.internal.R
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.monet.ColorScheme
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
* is triggered.
*/
interface ColorTransition {
fun updateColorScheme(scheme: ColorScheme?): Boolean
}
/**
* A [ColorTransition] that animates between two specific colors.
* It uses a ValueAnimator to execute the animation and interpolate between the source color and
* the target color.
*
* Selection of the target color from the scheme, and application of the interpolated color
* are delegated to callbacks.
*/
open class AnimatingColorTransition(
private val defaultColor: Int,
private val extractColor: (ColorScheme) -> Int,
private val applyColor: (Int) -> Unit
) : AnimatorUpdateListener, ColorTransition {
private val argbEvaluator = ArgbEvaluator()
private val valueAnimator = buildAnimator()
var sourceColor: Int = defaultColor
var currentColor: Int = defaultColor
var targetColor: Int = defaultColor
override fun onAnimationUpdate(animation: ValueAnimator) {
currentColor = argbEvaluator.evaluate(
animation.animatedFraction, sourceColor, targetColor
) as Int
applyColor(currentColor)
}
override fun updateColorScheme(scheme: ColorScheme?): Boolean {
val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
if (newTargetColor != targetColor) {
sourceColor = currentColor
targetColor = newTargetColor
valueAnimator.cancel()
valueAnimator.start()
return true
}
return false
}
init {
applyColor(defaultColor)
}
@VisibleForTesting
open fun buildAnimator(): ValueAnimator {
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = 333
animator.addUpdateListener(this)
return animator
}
}
typealias AnimatingColorTransitionFactory =
(Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
/**
* ColorSchemeTransition constructs a ColorTransition for each color in the scheme
* that needs to be transitioned when changed. It also sets up the assignment functions for sending
* the sending the interpolated colors to the appropriate views.
*/
class ColorSchemeTransition internal constructor(
private val context: Context,
private val mediaViewHolder: MediaViewHolder,
animatingColorTransitionFactory: AnimatingColorTransitionFactory
) {
constructor(context: Context, mediaViewHolder: MediaViewHolder) :
this(context, mediaViewHolder, ::AnimatingColorTransition)
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor = animatingColorTransitionFactory(
bgColor,
::surfaceFromScheme
) { surfaceColor ->
val colorList = ColorStateList.valueOf(surfaceColor)
mediaViewHolder.seamlessIcon.imageTintList = colorList
mediaViewHolder.seamlessText.setTextColor(surfaceColor)
mediaViewHolder.albumView.backgroundTintList = colorList
mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
}
val accentPrimary = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorPrimary),
::accentPrimaryFromScheme
) { accentPrimary ->
val accentColorList = ColorStateList.valueOf(accentPrimary)
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
}
val accentSecondary = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorPrimary),
::accentSecondaryFromScheme
) { accentSecondary ->
val colorList = ColorStateList.valueOf(accentSecondary)
(mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
it.setColor(colorList)
it.effectColor = colorList
}
}
val colorSeamless = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorPrimary),
{ colorScheme: ColorScheme ->
// A1-100 dark in dark theme, A1-200 in light theme
if (context.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
colorScheme.accent1[2]
else colorScheme.accent1[3]
}, { seamlessColor: Int ->
val accentColorList = ColorStateList.valueOf(seamlessColor)
mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
})
val textPrimary = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorPrimary),
::textPrimaryFromScheme
) { textPrimary ->
mediaViewHolder.titleText.setTextColor(textPrimary)
val textColorList = ColorStateList.valueOf(textPrimary)
mediaViewHolder.seekBar.thumb.setTintList(textColorList)
mediaViewHolder.seekBar.progressTintList = textColorList
mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
for (button in mediaViewHolder.getTransparentActionButtons()) {
button.imageTintList = textColorList
}
mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary)
}
val textPrimaryInverse = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorPrimaryInverse),
::textPrimaryInverseFromScheme
) { textPrimaryInverse ->
mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse)
}
val textSecondary = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorSecondary),
::textSecondaryFromScheme
) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) }
val textTertiary = animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorTertiary),
::textTertiaryFromScheme
) { textTertiary ->
mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
}
val colorTransitions = arrayOf(
surfaceColor,
colorSeamless,
accentPrimary,
accentSecondary,
textPrimary,
textPrimaryInverse,
textSecondary,
textTertiary,
)
private fun loadDefaultColor(id: Int): Int {
return Utils.getColorAttr(context, id).defaultColor
}
fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
var anyChanged = false
colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
return anyChanged
}
}