Add touch ripple in the media control panel, with flag protected.
Recordings are attached in the b/237282226 (tested with the flag enabled) Bug: 237282226 Test: MediaControlPanelTest, ColorSchemeTransitionTest, RippleAnimationTest, MultiRippleControllerTest, Manual Change-Id: Ib7409c946421950f3857dbd442bab10d0481fcf3
This commit is contained in:
@@ -44,6 +44,15 @@
|
||||
android:background="@drawable/qs_media_outline_album_bg"
|
||||
/>
|
||||
|
||||
<com.android.systemui.ripple.MultiRippleView
|
||||
android:id="@+id/touch_ripple_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_session_height_expanded"
|
||||
app:layout_constraintStart_toStartOf="@id/album_art"
|
||||
app:layout_constraintEnd_toEndOf="@id/album_art"
|
||||
app:layout_constraintTop_toTopOf="@id/album_art"
|
||||
app:layout_constraintBottom_toBottomOf="@id/album_art" />
|
||||
|
||||
<!-- Guideline for output switcher -->
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/center_vertical_guideline"
|
||||
|
||||
@@ -34,6 +34,16 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<!-- Touch ripple must have the same constraint as the album art. -->
|
||||
<Constraint
|
||||
android:id="@+id/touch_ripple_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_session_height_collapsed"
|
||||
app:layout_constraintStart_toStartOf="@+id/album_art"
|
||||
app:layout_constraintEnd_toEndOf="@+id/album_art"
|
||||
app:layout_constraintTop_toTopOf="@+id/album_art"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/header_title"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<!-- Touch ripple must have the same constraint as the album art. -->
|
||||
<Constraint
|
||||
android:id="@+id/touch_ripple_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_session_height_expanded"
|
||||
app:layout_constraintStart_toStartOf="@+id/album_art"
|
||||
app:layout_constraintEnd_toEndOf="@+id/album_art"
|
||||
app:layout_constraintTop_toTopOf="@+id/album_art"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/header_title"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -222,7 +222,7 @@ object Flags {
|
||||
@JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
|
||||
|
||||
// TODO(b/254513168): Tracking Bug
|
||||
val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
|
||||
@JvmField val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
|
||||
|
||||
// 1000 - dock
|
||||
val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.Barrier
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||
import com.android.systemui.ripple.MultiRippleView
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
|
||||
private const val TAG = "MediaViewHolder"
|
||||
@@ -36,6 +37,7 @@ class MediaViewHolder constructor(itemView: View) {
|
||||
|
||||
// Player information
|
||||
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
|
||||
val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
|
||||
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
|
||||
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
||||
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
||||
import com.android.systemui.monet.ColorScheme
|
||||
import com.android.systemui.ripple.MultiRippleController
|
||||
|
||||
/**
|
||||
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
|
||||
@@ -100,12 +101,14 @@ class ColorSchemeTransition
|
||||
internal constructor(
|
||||
private val context: Context,
|
||||
private val mediaViewHolder: MediaViewHolder,
|
||||
private val multiRippleController: MultiRippleController,
|
||||
animatingColorTransitionFactory: AnimatingColorTransitionFactory
|
||||
) {
|
||||
constructor(
|
||||
context: Context,
|
||||
mediaViewHolder: MediaViewHolder
|
||||
) : this(context, mediaViewHolder, ::AnimatingColorTransition)
|
||||
mediaViewHolder: MediaViewHolder,
|
||||
multiRippleController: MultiRippleController,
|
||||
) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
|
||||
|
||||
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
|
||||
val surfaceColor =
|
||||
@@ -125,6 +128,7 @@ internal constructor(
|
||||
val accentColorList = ColorStateList.valueOf(accentPrimary)
|
||||
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
|
||||
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
|
||||
multiRippleController.updateColor(accentPrimary)
|
||||
}
|
||||
|
||||
val accentSecondary =
|
||||
|
||||
@@ -76,6 +76,8 @@ import com.android.systemui.bluetooth.BroadcastDialogController;
|
||||
import com.android.systemui.broadcast.BroadcastSender;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.media.controls.models.GutsViewHolder;
|
||||
import com.android.systemui.media.controls.models.player.MediaAction;
|
||||
import com.android.systemui.media.controls.models.player.MediaButton;
|
||||
@@ -95,6 +97,10 @@ import com.android.systemui.monet.ColorScheme;
|
||||
import com.android.systemui.monet.Style;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.ripple.MultiRippleController;
|
||||
import com.android.systemui.ripple.RippleAnimation;
|
||||
import com.android.systemui.ripple.RippleAnimationConfig;
|
||||
import com.android.systemui.ripple.RippleShader;
|
||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
@@ -209,6 +215,8 @@ public class MediaControlPanel {
|
||||
private boolean mIsCurrentBroadcastedApp = false;
|
||||
private boolean mShowBroadcastDialogButton = false;
|
||||
private String mSwitchBroadcastApp;
|
||||
private MultiRippleController mMultiRippleController;
|
||||
private FeatureFlags mFeatureFlags;
|
||||
|
||||
/**
|
||||
* Initialize a new control panel
|
||||
@@ -236,7 +244,9 @@ public class MediaControlPanel {
|
||||
KeyguardStateController keyguardStateController,
|
||||
ActivityIntentHelper activityIntentHelper,
|
||||
NotificationLockscreenUserManager lockscreenUserManager,
|
||||
BroadcastDialogController broadcastDialogController) {
|
||||
BroadcastDialogController broadcastDialogController,
|
||||
FeatureFlags featureFlags
|
||||
) {
|
||||
mContext = context;
|
||||
mBackgroundExecutor = backgroundExecutor;
|
||||
mMainExecutor = mainExecutor;
|
||||
@@ -262,6 +272,8 @@ public class MediaControlPanel {
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
mFeatureFlags = featureFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,7 +393,9 @@ public class MediaControlPanel {
|
||||
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
|
||||
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
|
||||
|
||||
mColorSchemeTransition = new ColorSchemeTransition(mContext, mMediaViewHolder);
|
||||
mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
|
||||
mColorSchemeTransition = new ColorSchemeTransition(
|
||||
mContext, mMediaViewHolder, mMultiRippleController);
|
||||
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
|
||||
}
|
||||
|
||||
@@ -982,6 +996,9 @@ public class MediaControlPanel {
|
||||
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
|
||||
action.run();
|
||||
if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) {
|
||||
mMultiRippleController.play(createTouchRippleAnimation(button));
|
||||
}
|
||||
|
||||
if (icon instanceof Animatable) {
|
||||
((Animatable) icon).start();
|
||||
@@ -997,6 +1014,26 @@ public class MediaControlPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private RippleAnimation createTouchRippleAnimation(ImageButton button) {
|
||||
float maxSize = mMediaViewHolder.getMultiRippleView().getWidth() * 2;
|
||||
return new RippleAnimation(
|
||||
new RippleAnimationConfig(
|
||||
RippleShader.RippleShape.CIRCLE,
|
||||
/* duration= */ 1500L,
|
||||
/* centerX= */ button.getX() + button.getWidth() * 0.5f,
|
||||
/* centerY= */ button.getY() + button.getHeight() * 0.5f,
|
||||
/* maxWidth= */ maxSize,
|
||||
/* maxHeight= */ maxSize,
|
||||
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
|
||||
mColorSchemeTransition.getAccentPrimary().getTargetColor(),
|
||||
/* opacity= */ 100,
|
||||
/* shouldFillRipple= */ false,
|
||||
/* sparkleStrength= */ 0f,
|
||||
/* shouldDistort= */ false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void clearButton(final ImageButton button) {
|
||||
button.setImageDrawable(null);
|
||||
button.setContentDescription(null);
|
||||
|
||||
@@ -26,6 +26,11 @@ class MultiRippleController(private val multipleRippleView: MultiRippleView) {
|
||||
@VisibleForTesting const val MAX_RIPPLE_NUMBER = 10
|
||||
}
|
||||
|
||||
/** Updates all the ripple colors during the animation. */
|
||||
fun updateColor(color: Int) {
|
||||
multipleRippleView.ripples.forEach { anim -> anim.updateColor(color) }
|
||||
}
|
||||
|
||||
fun play(rippleAnimation: RippleAnimation) {
|
||||
if (multipleRippleView.ripples.size >= MAX_RIPPLE_NUMBER) {
|
||||
return
|
||||
|
||||
@@ -27,17 +27,18 @@ class RippleAnimation(private val config: RippleAnimationConfig) {
|
||||
private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
|
||||
|
||||
init {
|
||||
rippleShader.setCenter(config.centerX, config.centerY)
|
||||
rippleShader.setMaxSize(config.maxWidth, config.maxHeight)
|
||||
rippleShader.rippleFill = config.shouldFillRipple
|
||||
rippleShader.pixelDensity = config.pixelDensity
|
||||
rippleShader.color = ColorUtils.setAlphaComponent(config.color, config.opacity)
|
||||
rippleShader.sparkleStrength = config.sparkleStrength
|
||||
applyConfigToShader()
|
||||
}
|
||||
|
||||
/** Updates the ripple color during the animation. */
|
||||
fun updateColor(color: Int) {
|
||||
config.apply { config.color = color }
|
||||
applyConfigToShader()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun play(onAnimationEnd: Runnable? = null) {
|
||||
if (animator.isRunning) {
|
||||
if (isPlaying()) {
|
||||
return // Ignore if ripple effect is already playing
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) {
|
||||
val now = updateListener.currentPlayTime
|
||||
val progress = updateListener.animatedValue as Float
|
||||
rippleShader.progress = progress
|
||||
rippleShader.distortionStrength = 1 - progress
|
||||
rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f
|
||||
rippleShader.time = now.toFloat()
|
||||
}
|
||||
animator.addListener(
|
||||
@@ -61,4 +62,13 @@ class RippleAnimation(private val config: RippleAnimationConfig) {
|
||||
|
||||
/** Indicates whether the animation is playing. */
|
||||
fun isPlaying(): Boolean = animator.isRunning
|
||||
|
||||
private fun applyConfigToShader() {
|
||||
rippleShader.setCenter(config.centerX, config.centerY)
|
||||
rippleShader.setMaxSize(config.maxWidth, config.maxHeight)
|
||||
rippleShader.rippleFill = config.shouldFillRipple
|
||||
rippleShader.pixelDensity = config.pixelDensity
|
||||
rippleShader.color = ColorUtils.setAlphaComponent(config.color, config.opacity)
|
||||
rippleShader.sparkleStrength = config.sparkleStrength
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ import android.graphics.Color
|
||||
/**
|
||||
* A struct that holds the ripple animation configurations.
|
||||
*
|
||||
* <p>This is designed to be used only once. Create a new instance when the animation needs to
|
||||
* change, instead of modifying each parameter. This data class is pulled out to make the
|
||||
* [RippleAnimation] constructor succinct.
|
||||
* <p>This configuration is designed to play a SINGLE animation. Do not reuse or modify the
|
||||
* configuration parameters to play different animations, unless the value has to change within the
|
||||
* single animation (e.g. Change color or opacity during the animation). Note that this data class
|
||||
* is pulled out to make the [RippleAnimation] constructor succinct.
|
||||
*/
|
||||
data class RippleAnimationConfig(
|
||||
val rippleShape: RippleShader.RippleShape = RippleShader.RippleShape.CIRCLE,
|
||||
@@ -17,10 +18,11 @@ data class RippleAnimationConfig(
|
||||
val maxWidth: Float = 0f,
|
||||
val maxHeight: Float = 0f,
|
||||
val pixelDensity: Float = 1f,
|
||||
val color: Int = Color.WHITE,
|
||||
var color: Int = Color.WHITE,
|
||||
val opacity: Int = RIPPLE_DEFAULT_ALPHA,
|
||||
val shouldFillRipple: Boolean = false,
|
||||
val sparkleStrength: Float = RIPPLE_SPARKLE_STRENGTH
|
||||
val sparkleStrength: Float = RIPPLE_SPARKLE_STRENGTH,
|
||||
val shouldDistort: Boolean = true
|
||||
) {
|
||||
companion object {
|
||||
const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
||||
import com.android.systemui.monet.ColorScheme
|
||||
import com.android.systemui.ripple.MultiRippleController
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@@ -60,6 +61,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
|
||||
private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory
|
||||
@Mock private lateinit var mediaViewHolder: MediaViewHolder
|
||||
@Mock private lateinit var gutsViewHolder: GutsViewHolder
|
||||
@Mock private lateinit var multiRippleController: MultiRippleController
|
||||
|
||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@@ -70,7 +72,12 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
|
||||
whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
|
||||
|
||||
colorSchemeTransition =
|
||||
ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory)
|
||||
ColorSchemeTransition(
|
||||
context,
|
||||
mediaViewHolder,
|
||||
multiRippleController,
|
||||
animatingColorTransitionFactory
|
||||
)
|
||||
|
||||
colorTransition =
|
||||
object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) {
|
||||
|
||||
@@ -59,6 +59,8 @@ import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.bluetooth.BroadcastDialogController
|
||||
import com.android.systemui.broadcast.BroadcastSender
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.media.controls.MediaTestUtils
|
||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||
import com.android.systemui.media.controls.models.player.MediaAction
|
||||
@@ -76,6 +78,7 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.ripple.MultiRippleView
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
@@ -174,6 +177,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
private lateinit var cancelText: TextView
|
||||
private lateinit var dismiss: FrameLayout
|
||||
private lateinit var dismissText: TextView
|
||||
private lateinit var multiRippleView: MultiRippleView
|
||||
|
||||
private lateinit var session: MediaSession
|
||||
private lateinit var device: MediaDeviceData
|
||||
@@ -205,6 +209,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
private lateinit var recSubtitle2: TextView
|
||||
private lateinit var recSubtitle3: TextView
|
||||
private var shouldShowBroadcastButton: Boolean = false
|
||||
private val fakeFeatureFlag =
|
||||
FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
|
||||
|
||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||
|
||||
@@ -244,7 +250,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
keyguardStateController,
|
||||
activityIntentHelper,
|
||||
lockscreenUserManager,
|
||||
broadcastDialogController
|
||||
broadcastDialogController,
|
||||
fakeFeatureFlag
|
||||
) {
|
||||
override fun loadAnimator(
|
||||
animId: Int,
|
||||
@@ -374,6 +381,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
)
|
||||
}
|
||||
|
||||
multiRippleView = MultiRippleView(context, null)
|
||||
|
||||
whenever(viewHolder.player).thenReturn(view)
|
||||
whenever(viewHolder.appIcon).thenReturn(appIcon)
|
||||
whenever(viewHolder.albumView).thenReturn(albumView)
|
||||
@@ -414,6 +423,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4)
|
||||
|
||||
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
|
||||
|
||||
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
|
||||
}
|
||||
|
||||
/** Initialize elements for the recommendation view holder */
|
||||
@@ -1973,6 +1984,50 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
|
||||
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
|
||||
val semanticActions =
|
||||
MediaButton(
|
||||
playOrPause =
|
||||
MediaAction(
|
||||
icon = null,
|
||||
action = {},
|
||||
contentDescription = "play",
|
||||
background = null
|
||||
)
|
||||
)
|
||||
val data = mediaData.copy(semanticActions = semanticActions)
|
||||
player.attachPlayer(viewHolder)
|
||||
player.bindPlayer(data, KEY)
|
||||
|
||||
viewHolder.actionPlayPause.callOnClick()
|
||||
|
||||
assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onButtonClick_touchRippleFlagDisabled_doesNotPlayTouchRipple() {
|
||||
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, false)
|
||||
val semanticActions =
|
||||
MediaButton(
|
||||
playOrPause =
|
||||
MediaAction(
|
||||
icon = null,
|
||||
action = {},
|
||||
contentDescription = "play",
|
||||
background = null
|
||||
)
|
||||
)
|
||||
val data = mediaData.copy(semanticActions = semanticActions)
|
||||
player.attachPlayer(viewHolder)
|
||||
player.bindPlayer(data, KEY)
|
||||
|
||||
viewHolder.actionPlayPause.callOnClick()
|
||||
|
||||
assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(0)
|
||||
}
|
||||
|
||||
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
|
||||
withArgCaptor {
|
||||
verify(seekBarViewModel).setScrubbingChangeListener(capture())
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.ripple
|
||||
|
||||
import android.graphics.Color
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
@@ -45,6 +46,24 @@ class MultiRippleControllerTest : SysuiTestCase() {
|
||||
multiRippleController = MultiRippleController(multiRippleView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateColor_updatesColor() {
|
||||
val initialColor = Color.WHITE
|
||||
val expectedColor = Color.RED
|
||||
|
||||
fakeExecutor.execute {
|
||||
val rippleAnimation =
|
||||
RippleAnimation(rippleAnimationConfig.apply { this.color = initialColor })
|
||||
|
||||
with(multiRippleController) {
|
||||
play(rippleAnimation)
|
||||
updateColor(expectedColor)
|
||||
}
|
||||
|
||||
assertThat(rippleAnimationConfig.color).isEqualTo(expectedColor)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun play_playsRipple() {
|
||||
fakeExecutor.execute {
|
||||
|
||||
@@ -55,6 +55,23 @@ class RippleAnimationTest : SysuiTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateColor_updatesColorCorrectly() {
|
||||
val initialColor = Color.WHITE
|
||||
val expectedColor = Color.RED
|
||||
val config = RippleAnimationConfig(color = initialColor)
|
||||
val rippleAnimation = RippleAnimation(config)
|
||||
|
||||
fakeExecutor.execute {
|
||||
with(rippleAnimation) {
|
||||
play()
|
||||
updateColor(expectedColor)
|
||||
}
|
||||
|
||||
assertThat(config.color).isEqualTo(expectedColor)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun play_updatesIsPlaying() {
|
||||
val config = RippleAnimationConfig(duration = 1000L)
|
||||
|
||||
Reference in New Issue
Block a user