1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.media.controls.ui.animation 18 19 import android.animation.ArgbEvaluator 20 import android.animation.ValueAnimator 21 import android.animation.ValueAnimator.AnimatorUpdateListener 22 import android.content.Context 23 import android.content.res.ColorStateList 24 import android.content.res.Configuration 25 import android.content.res.Configuration.UI_MODE_NIGHT_YES 26 import android.graphics.drawable.RippleDrawable 27 import com.android.internal.R 28 import com.android.internal.annotations.VisibleForTesting 29 import com.android.settingslib.Utils 30 import com.android.systemui.media.controls.ui.view.MediaViewHolder 31 import com.android.systemui.monet.ColorScheme 32 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect 33 import com.android.systemui.surfaceeffects.ripple.MultiRippleController 34 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController 35 36 /** 37 * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme] 38 * is triggered. 39 */ 40 interface ColorTransition { 41 fun updateColorScheme(scheme: ColorScheme?): Boolean 42 } 43 44 /** 45 * A [ColorTransition] that animates between two specific colors. It uses a ValueAnimator to execute 46 * the animation and interpolate between the source color and the target color. 47 * 48 * Selection of the target color from the scheme, and application of the interpolated color are 49 * delegated to callbacks. 50 */ 51 open class AnimatingColorTransition( 52 private val defaultColor: Int, 53 private val extractColor: (ColorScheme) -> Int, 54 private val applyColor: (Int) -> Unit 55 ) : AnimatorUpdateListener, ColorTransition { 56 57 private val argbEvaluator = ArgbEvaluator() 58 private val valueAnimator = buildAnimator() 59 var sourceColor: Int = defaultColor 60 var currentColor: Int = defaultColor 61 var targetColor: Int = defaultColor 62 onAnimationUpdatenull63 override fun onAnimationUpdate(animation: ValueAnimator) { 64 currentColor = 65 argbEvaluator.evaluate(animation.animatedFraction, sourceColor, targetColor) as Int 66 applyColor(currentColor) 67 } 68 updateColorSchemenull69 override fun updateColorScheme(scheme: ColorScheme?): Boolean { 70 val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme) 71 if (newTargetColor != targetColor) { 72 sourceColor = currentColor 73 targetColor = newTargetColor 74 valueAnimator.cancel() 75 valueAnimator.start() 76 return true 77 } 78 return false 79 } 80 81 init { 82 applyColor(defaultColor) 83 } 84 85 @VisibleForTesting buildAnimatornull86 open fun buildAnimator(): ValueAnimator { 87 val animator = ValueAnimator.ofFloat(0f, 1f) 88 animator.duration = 333 89 animator.addUpdateListener(this) 90 return animator 91 } 92 } 93 94 typealias AnimatingColorTransitionFactory = 95 (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition 96 97 /** 98 * ColorSchemeTransition constructs a ColorTransition for each color in the scheme that needs to be 99 * transitioned when changed. It also sets up the assignment functions for sending the sending the 100 * interpolated colors to the appropriate views. 101 */ 102 class ColorSchemeTransition 103 internal constructor( 104 private val context: Context, 105 private val mediaViewHolder: MediaViewHolder, 106 private val multiRippleController: MultiRippleController, 107 private val turbulenceNoiseController: TurbulenceNoiseController, 108 animatingColorTransitionFactory: AnimatingColorTransitionFactory 109 ) { 110 constructor( 111 context: Context, 112 mediaViewHolder: MediaViewHolder, 113 multiRippleController: MultiRippleController, 114 turbulenceNoiseController: TurbulenceNoiseController 115 ) : this( 116 context, 117 mediaViewHolder, 118 multiRippleController, 119 turbulenceNoiseController, 120 ::AnimatingColorTransition 121 ) 122 var loadingEffect: LoadingEffect? = null 123 124 val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20) 125 val surfaceColor = surfaceColornull126 animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor -> 127 val colorList = ColorStateList.valueOf(surfaceColor) 128 mediaViewHolder.seamlessIcon.imageTintList = colorList 129 mediaViewHolder.seamlessText.setTextColor(surfaceColor) 130 mediaViewHolder.albumView.backgroundTintList = colorList 131 mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) 132 } 133 val accentPrimary = 134 animatingColorTransitionFactory( 135 loadDefaultColor(R.attr.textColorPrimary), 136 ::accentPrimaryFromScheme accentPrimarynull137 ) { accentPrimary -> 138 val accentColorList = ColorStateList.valueOf(accentPrimary) 139 mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList 140 mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) 141 multiRippleController.updateColor(accentPrimary) 142 turbulenceNoiseController.updateNoiseColor(accentPrimary) 143 loadingEffect?.updateColor(accentPrimary) 144 } 145 146 val accentSecondary = 147 animatingColorTransitionFactory( 148 loadDefaultColor(R.attr.textColorPrimary), 149 ::accentSecondaryFromScheme accentSecondarynull150 ) { accentSecondary -> 151 val colorList = ColorStateList.valueOf(accentSecondary) 152 (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let { 153 it.setColor(colorList) 154 it.effectColor = colorList 155 } 156 } 157 158 val colorSeamless = 159 animatingColorTransitionFactory( 160 loadDefaultColor(R.attr.textColorPrimary), colorSchemenull161 { colorScheme: ColorScheme -> 162 // A1-100 dark in dark theme, A1-200 in light theme 163 if ( 164 context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == 165 UI_MODE_NIGHT_YES 166 ) 167 colorScheme.accent1.s100 168 else colorScheme.accent1.s200 169 }, seamlessColornull170 { seamlessColor: Int -> 171 val accentColorList = ColorStateList.valueOf(seamlessColor) 172 mediaViewHolder.seamlessButton.backgroundTintList = accentColorList 173 } 174 ) 175 176 val textPrimary = 177 animatingColorTransitionFactory( 178 loadDefaultColor(R.attr.textColorPrimary), 179 ::textPrimaryFromScheme textPrimarynull180 ) { textPrimary -> 181 mediaViewHolder.titleText.setTextColor(textPrimary) 182 val textColorList = ColorStateList.valueOf(textPrimary) 183 mediaViewHolder.seekBar.thumb.setTintList(textColorList) 184 mediaViewHolder.seekBar.progressTintList = textColorList 185 mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList) 186 mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList) 187 for (button in mediaViewHolder.getTransparentActionButtons()) { 188 button.imageTintList = textColorList 189 } 190 mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) 191 } 192 193 val textPrimaryInverse = 194 animatingColorTransitionFactory( 195 loadDefaultColor(R.attr.textColorPrimaryInverse), 196 ::textPrimaryInverseFromScheme textPrimaryInversenull197 ) { textPrimaryInverse -> 198 mediaViewHolder.actionPlayPause.imageTintList = 199 ColorStateList.valueOf(textPrimaryInverse) 200 } 201 202 val textSecondary = 203 animatingColorTransitionFactory( 204 loadDefaultColor(R.attr.textColorSecondary), 205 ::textSecondaryFromScheme textSecondarynull206 ) { textSecondary -> 207 mediaViewHolder.artistText.setTextColor(textSecondary) 208 } 209 210 val textTertiary = 211 animatingColorTransitionFactory( 212 loadDefaultColor(R.attr.textColorTertiary), 213 ::textTertiaryFromScheme textTertiarynull214 ) { textTertiary -> 215 mediaViewHolder.seekBar.progressBackgroundTintList = 216 ColorStateList.valueOf(textTertiary) 217 } 218 219 val colorTransitions = 220 arrayOf( 221 surfaceColor, 222 colorSeamless, 223 accentPrimary, 224 accentSecondary, 225 textPrimary, 226 textPrimaryInverse, 227 textSecondary, 228 textTertiary, 229 ) 230 loadDefaultColornull231 private fun loadDefaultColor(id: Int): Int { 232 return Utils.getColorAttr(context, id).defaultColor 233 } 234 updateColorSchemenull235 fun updateColorScheme(colorScheme: ColorScheme?): Boolean { 236 var anyChanged = false 237 colorTransitions.forEach { 238 val isChanged = it.updateColorScheme(colorScheme) 239 240 // Ignore changes to colorSeamless, since that is expected when toggling dark mode 241 if (it == colorSeamless) return@forEach 242 243 anyChanged = isChanged || anyChanged 244 } 245 colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme } 246 return anyChanged 247 } 248 } 249