From 72a0801020d320100de3a713acca5e97ae3e3ca4 Mon Sep 17 00:00:00 2001 From: Ryan Moelter Date: Thu, 23 Apr 2020 21:09:32 -0700 Subject: [PATCH 1/4] Prevent animated values from leaving expected bounds https://github.com/wealthfront/blend/issues/4 --- .../main/java/com/wealthfront/blend/Blend.kt | 2 +- .../blend/animator/BlendableAnimator.kt | 13 ++++- .../blend/animator/SinglePropertyAnimation.kt | 25 +++++++-- .../wealthfront/blend/dsl/BuilderTraits.kt | 6 +-- .../blend/properties/AdditiveProperty.kt | 20 +++---- .../blend/properties/AnimationData.kt | 39 ++++++++------ .../properties/DimensionViewProperties.kt | 4 +- .../blend/properties/ViewPropertySupport.kt | 6 +-- .../com/wealthfront/blend/BlendDslTest.kt | 2 +- .../blend/properties/AnimationDataTest.kt | 52 +++++++++++++++---- 10 files changed, 118 insertions(+), 51 deletions(-) diff --git a/blend-library/src/main/java/com/wealthfront/blend/Blend.kt b/blend-library/src/main/java/com/wealthfront/blend/Blend.kt index ca1daaa..7581ff6 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/Blend.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/Blend.kt @@ -83,7 +83,7 @@ open class Blend { */ open fun stopPulsing(vararg views: View) { views - .flatMap { view -> AdditiveViewProperties.ALPHA.getAnimationData(view).runningAnimations } + .flatMap { view -> AdditiveViewProperties.ALPHA.getAnimationData(view).committedAnimations } .mapNotNull { singlePropertyAnimation -> singlePropertyAnimation.animator } .filter { animator -> animator.repeatCount != 0 } .toSet() diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt index 85c2835..af5ee38 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt @@ -104,11 +104,21 @@ open class BlendableAnimator : Animator() { open fun commitFutureValuesIfNotCommitted() { if (!valuesCommitted) { beforeStartActions.forEach { it() } - animations.forEach { it.setUpOnAnimationStart(this) } + animations.forEach { it.setUpOnAnimationCommitted(this) } } valuesCommitted = true } + /** + * Mark all [animations] as part of a fully-committed set of animations. + * + * This step is necessary for individual animations in a set to not remove each other when being committing. + */ + open fun markAnimationsAsFullyCommitted() { + beforeStartActions.forEach { it() } + animations.forEach { it.isPartOfAFullyCommittedSet = true } + } + override fun start() { if (isRunning) { throw IllegalStateException("Cannot start an already-running animation") @@ -125,6 +135,7 @@ open class BlendableAnimator : Animator() { }) commitFutureValuesIfNotCommitted() + markAnimationsAsFullyCommitted() if (repeatCount > 0) { cancelAnimatorOnViewsDetached() } diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt index ac58616..b95f251 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt @@ -1,5 +1,6 @@ package com.wealthfront.blend.animator +import androidx.annotation.VisibleForTesting import com.wealthfront.blend.properties.AdditiveProperty /** @@ -29,17 +30,26 @@ class SinglePropertyAnimation( * The animator running this animation */ var animator: BlendableAnimator? = null - private set + @VisibleForTesting internal set + /** + * Whether this animation has started running. Note that it can be committed (i.e. queued) and not started. + */ + val isStarted: Boolean = animator?.isStarted ?: false + /** + * Whether the set that this animation belongs to is done committing all of its animations. Useful for figuring out + * when to cancel unstarted (but committed) animations. + */ + var isPartOfAFullyCommittedSet: Boolean = false /** * Set up the starting values for this animation and commit the [targetValue] as [property]'s future value. */ - fun setUpOnAnimationStart(animator: BlendableAnimator) { + fun setUpOnAnimationCommitted(animator: BlendableAnimator) { this.animator = animator - property.setUpOnAnimationStart(subject) + property.setUpOnAnimationCommitted(subject) startValue = property.getFutureValue(subject) previousValue = startValue - property.addRunningAnimation(subject, this) + property.addCommittedAnimation(subject, this) property.addInterruptableEndActions(subject, *interruptibleEndActions.toTypedArray()) } @@ -52,12 +62,17 @@ class SinglePropertyAnimation( previousValue = newValue } + fun markCancelled() { + startValue = targetValue + previousValue = targetValue + } + /** * Run the end actions added by [BlendableAnimator.doOnFinishedUnlessLastAnimationInterrupted], if we haven't been * interrupted by another animation on [subject].[property]. */ fun runEndActions() { property.runEndActions(subject, this) - property.removeRunningAnimator(subject, this) + property.removeCommittedAnimation(subject, this) } } \ No newline at end of file diff --git a/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt b/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt index 2e855b5..707d706 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt @@ -35,9 +35,9 @@ interface AnimationStarter { return animatorSet.asAnimator().apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { - animatorSet.childAnimators - .mapNotNull { it as? BlendableAnimator } - .forEach { it.commitFutureValuesIfNotCommitted() } + val animators = animatorSet.childAnimators.mapNotNull { it as? BlendableAnimator } + animators.forEach { it.commitFutureValuesIfNotCommitted() } + animators.forEach { it.markAnimationsAsFullyCommitted() } } }) } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt index 8e5b655..f5a1a57 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt @@ -10,8 +10,8 @@ import kotlin.math.roundToInt * To properly blend animations, it includes methods like [getFutureValue], [addInterruptableEndActions], and * [getAnimationData]. * - * We store data on running animations ([AnimationData]) on each subject, ideally. This systematically prevents leaks of - * references to [Subject]s. The mechanism for storing is described by the implementation of [getAnimationData] + * We store data on committed animations ([AnimationData]) on each subject, ideally. This systematically prevents leaks + * of references to [Subject]s. The mechanism for storing is described by the implementation of [getAnimationData] */ interface AdditiveProperty { @@ -52,19 +52,19 @@ interface AdditiveProperty { /** * Perform any setup tasks when an animation on this property starts. */ - fun setUpOnAnimationStart(subject: Subject) { } + fun setUpOnAnimationCommitted(subject: Subject) { } /** - * Add a running animation to the [subject]'s [AnimationData] for this property. + * Add a committed animation to the [subject]'s [AnimationData] for this property. */ - fun addRunningAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = - getAnimationData(subject).addRunningAnimation(animation) + fun addCommittedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = + getAnimationData(subject).addCommittedAnimation(animation) /** - * Remove a running animation to the [subject]'s [AnimationData] for this property. + * Remove a committed animation from the [subject]'s [AnimationData] for this property. */ - fun removeRunningAnimator(subject: Subject, animation: SinglePropertyAnimation<*>) = - getAnimationData(subject).removeRunningAnimation(animation) + fun removeCommittedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = + getAnimationData(subject).removeCommittedAnimation(animation) /** * Add [endActions] to the [subject]'s [AnimationData] for this property so they can be cancelled if another @@ -79,7 +79,7 @@ interface AdditiveProperty { */ fun runEndActions(subject: Subject, animation: SinglePropertyAnimation<*>) { val animationData = getAnimationData(subject) - if (animation == animationData.runningAnimations.lastOrNull()) { + if (animation == animationData.committedAnimations.lastOrNull()) { animationData.interruptableEndActions.forEach { it() } animationData.interruptableEndActions.clear() } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt index 4745dee..ae8455e 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt @@ -4,16 +4,17 @@ import com.wealthfront.blend.animator.SinglePropertyAnimation import kotlin.math.roundToInt /** - * A container for storing data about running animations to facilitate blending. + * A container for storing data about committed animations to facilitate blending. * * Associated with a specific subject and a property. */ class AnimationData { /** - * All animations currently running on the associated subject for the associated property. + * All animations currently committed to run on the associated subject for the associated property. These animations + * may or may not be currently running, however. */ - val runningAnimations: MutableList> = mutableListOf() + val committedAnimations: MutableList> = mutableListOf() /** * End actions that will be cancelled if another animation is started for the associated subject.property. */ @@ -21,35 +22,41 @@ class AnimationData { var isLatestAnimationDone = false /** - * Get the value of this property as if all running animations have finished. + * Get the value of this property as if all committed animations have finished. */ val futureValue: Float? - get() = runningAnimations.lastOrNull()?.targetValue + get() = committedAnimations.lastOrNull()?.targetValue /** - * Register a new animation that has started. + * Register a new animation that has started. This cancels any [interruptableEndActions] that we have, since a new + * animation has started. * - * This also cancels any [interruptableEndActions] that we have, since a new animation has started. + * This also cancels all non-started [committedAnimations], in order to avoid animating to any unexpected values. + * See [this github issue](https://github.com/wealthfront/blend/issues/4) for more information. */ - fun addRunningAnimation(animation: SinglePropertyAnimation<*>) { + fun addCommittedAnimation(animation: SinglePropertyAnimation<*>) { if (isLatestAnimationDone) { - runningAnimations -= runningAnimations.last() + committedAnimations -= committedAnimations.last() } isLatestAnimationDone = false - runningAnimations += animation + committedAnimations.removeAll { + !it.isStarted && it.isPartOfAFullyCommittedSet + } + committedAnimations += animation interruptableEndActions.clear() } /** - * Remove a running animation because it has either finished or been cancelled. + * Remove a committed animation because it has either finished or been cancelled. */ - fun removeRunningAnimation(animation: SinglePropertyAnimation<*>) { - if (runningAnimations.lastOrNull() == animation && runningAnimations.size > 1) { + fun removeCommittedAnimation(animation: SinglePropertyAnimation<*>) { + animation.markCancelled() + if (committedAnimations.lastOrNull() == animation && committedAnimations.size > 1) { isLatestAnimationDone = true } else { - runningAnimations -= animation - if (isLatestAnimationDone && runningAnimations.size == 1) { - runningAnimations.clear() + committedAnimations -= animation + if (isLatestAnimationDone && committedAnimations.size == 1) { + committedAnimations.clear() isLatestAnimationDone = false } } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt index 0be6d69..39e9c0c 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt @@ -35,7 +35,7 @@ object DimensionViewProperties { override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationStart(subject: View) { + override fun setUpOnAnimationCommitted(subject: View) { if (subject.visibility == GONE) { setValue(subject, 0f) subject.visibility = VISIBLE @@ -67,7 +67,7 @@ object DimensionViewProperties { override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationStart(subject: View) { + override fun setUpOnAnimationCommitted(subject: View) { if (subject.visibility == GONE) { setValue(subject, 0f) subject.visibility = VISIBLE diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt index f882940..d4f44de 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt @@ -44,7 +44,7 @@ fun makeExternalAdditiveViewProperty( interpolateAction(startValue, endValue, timeFraction, subject) } - override fun setUpOnAnimationStart(subject: Subject) { + override fun setUpOnAnimationCommitted(subject: Subject) { setUpAction?.invoke(subject) } } @@ -101,7 +101,7 @@ internal fun makeAdditiveViewProperty( interpolateAction(startValue, endValue, timeFraction, subject) } - override fun setUpOnAnimationStart(subject: Subject) { + override fun setUpOnAnimationCommitted(subject: Subject) { setUpAction?.invoke(subject) } } @@ -159,7 +159,7 @@ internal fun wrapViewProperty( override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationStart(subject: View) { + override fun setUpOnAnimationCommitted(subject: View) { setUpAction?.invoke(subject) } } diff --git a/blend-library/src/test/java/com/wealthfront/blend/BlendDslTest.kt b/blend-library/src/test/java/com/wealthfront/blend/BlendDslTest.kt index 7d0d46b..ce989a4 100644 --- a/blend-library/src/test/java/com/wealthfront/blend/BlendDslTest.kt +++ b/blend-library/src/test/java/com/wealthfront/blend/BlendDslTest.kt @@ -331,7 +331,7 @@ class BlendDslTest { return animationDatas[subject] ?: AnimationData().also { animationDatas[subject] = it } } - override fun setUpOnAnimationStart(subject: TestObject) { + override fun setUpOnAnimationCommitted(subject: TestObject) { addInterruptableEndActions(subject, { animationDatas.remove(subject) }) diff --git a/blend-library/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt b/blend-library/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt index 4a8f764..2220235 100644 --- a/blend-library/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt +++ b/blend-library/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt @@ -2,7 +2,9 @@ package com.wealthfront.blend.properties import android.view.View import com.google.common.truth.Truth.assertThat +import com.wealthfront.blend.animator.BlendableAnimator import com.wealthfront.blend.animator.SinglePropertyAnimation +import com.wealthfront.whenever import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -13,11 +15,15 @@ class AnimationDataTest { lateinit var animationData: AnimationData @Mock lateinit var view: View @Mock lateinit var property: AdditiveProperty + @Mock lateinit var startedAnimator: BlendableAnimator + @Mock lateinit var unstartedAnimator: BlendableAnimator @Before fun setUp() { initMocks(this) animationData = AnimationData() + + whenever(startedAnimator.isStarted).thenReturn(true) } @Test @@ -29,8 +35,8 @@ class AnimationDataTest { fun getFutureValue_futureValue() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addRunningAnimation(firstAnimation) - animationData.addRunningAnimation(latestAnimation) + animationData.addCommittedAnimation(firstAnimation) + animationData.addCommittedAnimation(latestAnimation) assertThat(animationData.futureValue).isWithin(0.01f).of(20f) } @@ -38,9 +44,9 @@ class AnimationDataTest { fun getFutureValue_futureValue_animationEndedEarly() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addRunningAnimation(firstAnimation) - animationData.addRunningAnimation(latestAnimation) - animationData.removeRunningAnimation(latestAnimation) + animationData.addCommittedAnimation(firstAnimation) + animationData.addCommittedAnimation(latestAnimation) + animationData.removeCommittedAnimation(latestAnimation) assertThat(animationData.futureValue).isWithin(0.01f).of(20f) } @@ -48,10 +54,38 @@ class AnimationDataTest { fun getFutureValue_futureValue_animationEndedEarly_clear() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addRunningAnimation(firstAnimation) - animationData.addRunningAnimation(latestAnimation) - animationData.removeRunningAnimation(latestAnimation) - animationData.removeRunningAnimation(firstAnimation) + animationData.addCommittedAnimation(firstAnimation) + animationData.addCommittedAnimation(latestAnimation) + animationData.removeCommittedAnimation(latestAnimation) + animationData.removeCommittedAnimation(firstAnimation) + assertThat(animationData.futureValue).isNull() + } + + @Test + fun removeChainedAnimation_newAnimationAdded() { + val delayedAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = unstartedAnimator + isPartOfAFullyCommittedSet = true + } + val newAnimation = SinglePropertyAnimation(view, property, 20f) + animationData.addCommittedAnimation(delayedAnimation) + animationData.addCommittedAnimation(newAnimation) + animationData.removeCommittedAnimation(newAnimation) assertThat(animationData.futureValue).isNull() } + + @Test + fun doNotRemoveChainedAnimation_newAnimationAdded_chainedAnimationIsPartOfUnfinishedSet() { + val delayedAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = unstartedAnimator + isPartOfAFullyCommittedSet = false + } + val newAnimation = SinglePropertyAnimation(view, property, 20f) + animationData.addCommittedAnimation(delayedAnimation) + animationData.addCommittedAnimation(newAnimation) + animationData.removeCommittedAnimation(newAnimation) + assertThat(animationData.futureValue).isNotNull() + } } \ No newline at end of file From d4b219e15ae8b617c11d1d7ed09f63a59265d0ae Mon Sep 17 00:00:00 2001 From: Ryan Moelter Date: Wed, 29 Apr 2020 20:31:21 -0700 Subject: [PATCH 2/4] Fix some details --- .../com/wealthfront/blend/animator/BlendableAnimator.kt | 6 ++---- .../wealthfront/blend/animator/SinglePropertyAnimation.kt | 2 +- .../java/com/wealthfront/blend/properties/AnimationData.kt | 7 +++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt index af5ee38..279af17 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt @@ -115,7 +115,6 @@ open class BlendableAnimator : Animator() { * This step is necessary for individual animations in a set to not remove each other when being committing. */ open fun markAnimationsAsFullyCommitted() { - beforeStartActions.forEach { it() } animations.forEach { it.isPartOfAFullyCommittedSet = true } } @@ -134,13 +133,12 @@ open class BlendableAnimator : Animator() { } }) - commitFutureValuesIfNotCommitted() - markAnimationsAsFullyCommitted() if (repeatCount > 0) { cancelAnimatorOnViewsDetached() } + commitFutureValuesIfNotCommitted() innerAnimator.start() - super.start() + markAnimationsAsFullyCommitted() } private fun cancelAnimatorOnViewsDetached() { diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt index b95f251..a98aa20 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt @@ -34,7 +34,7 @@ class SinglePropertyAnimation( /** * Whether this animation has started running. Note that it can be committed (i.e. queued) and not started. */ - val isStarted: Boolean = animator?.isStarted ?: false + val isStarted: Boolean get() = animator?.isStarted ?: false /** * Whether the set that this animation belongs to is done committing all of its animations. Useful for figuring out * when to cancel unstarted (but committed) animations. diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt index ae8455e..8b8adca 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt @@ -25,7 +25,7 @@ class AnimationData { * Get the value of this property as if all committed animations have finished. */ val futureValue: Float? - get() = committedAnimations.lastOrNull()?.targetValue + get() = committedAnimations.lastOrNull { it.isStarted }?.targetValue /** * Register a new animation that has started. This cancels any [interruptableEndActions] that we have, since a new @@ -39,8 +39,11 @@ class AnimationData { committedAnimations -= committedAnimations.last() } isLatestAnimationDone = false - committedAnimations.removeAll { + committedAnimations.filter { !it.isStarted && it.isPartOfAFullyCommittedSet + }.forEach { + it.markCancelled() + committedAnimations.remove(it) } committedAnimations += animation interruptableEndActions.clear() From 855115e67089186dce3f2685a5b007d3129aee00 Mon Sep 17 00:00:00 2001 From: Ryan Moelter Date: Sun, 3 May 2020 12:58:29 -0700 Subject: [PATCH 3/4] "Commit" -> "Queue" --- .../main/java/com/wealthfront/blend/Blend.kt | 2 +- .../blend/animator/BlendableAnimator.kt | 33 ++++++++--------- .../blend/animator/SinglePropertyAnimation.kt | 18 +++++----- .../wealthfront/blend/dsl/BuilderTraits.kt | 4 +-- .../blend/properties/AdditiveProperty.kt | 18 +++++----- .../blend/properties/AnimationData.kt | 36 +++++++++---------- .../properties/DimensionViewProperties.kt | 4 +-- .../blend/properties/ViewPropertySupport.kt | 6 ++-- .../blend/mock/ImmediateBlendableAnimator.kt | 2 +- .../com/wealthfront/blend/BlendDslTest.kt | 2 +- .../blend/animator/BlendableAnimatorTest.kt | 2 +- .../blend/properties/AnimationDataTest.kt | 34 +++++++++--------- 12 files changed, 81 insertions(+), 80 deletions(-) diff --git a/blend-library/src/main/java/com/wealthfront/blend/Blend.kt b/blend-library/src/main/java/com/wealthfront/blend/Blend.kt index 7581ff6..e33d6b0 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/Blend.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/Blend.kt @@ -83,7 +83,7 @@ open class Blend { */ open fun stopPulsing(vararg views: View) { views - .flatMap { view -> AdditiveViewProperties.ALPHA.getAnimationData(view).committedAnimations } + .flatMap { view -> AdditiveViewProperties.ALPHA.getAnimationData(view).queuedAnimations } .mapNotNull { singlePropertyAnimation -> singlePropertyAnimation.animator } .filter { animator -> animator.repeatCount != 0 } .toSet() diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt index 279af17..e4ee593 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/BlendableAnimator.kt @@ -21,7 +21,7 @@ import java.util.ArrayList * It wraps a [ValueAnimator] ([innerAnimator]) and delegates all [Animator] methods to it, with some redirection to * ensure proper adherence to e.g. listener contracts. * - * For proper functioning in sets, you must call [commitFutureValuesIfNotCommitted] when the set of animations is + * For proper functioning in sets, you must call [queueAnimationsIfNotAlreadyQueued] when the set of animations is * started, even if this animation is delayed. If not, the end state of the set of animations becomes dependent on * when the individual animators start, which can be unpredictable when they're user-initiated. */ @@ -43,7 +43,7 @@ open class BlendableAnimator : Animator() { * The actions to run before this animator starts. */ @VisibleForTesting val beforeStartActions: MutableList<() -> Unit> = mutableListOf() - private var valuesCommitted = false + private var animationsQueued = false val allAnimations: List> get() = animations.toList() @@ -96,26 +96,27 @@ open class BlendableAnimator : Animator() { } /** - * Commit the future values of all [animations] to the [com.wealthfront.blend.properties.AnimationData] objects - * described by their properties. + * Queue [animations], adding their future values to the [com.wealthfront.blend.properties.AnimationData] associated + * with each property/subject pair. * - * These future values are necessary for any animations started after this animator to blend properly. + * This should be called when the animation set that contains this animator is started, so animations started after + * this one can properly blend with this one. */ - open fun commitFutureValuesIfNotCommitted() { - if (!valuesCommitted) { + open fun queueAnimationsIfNotAlreadyQueued() { + if (!animationsQueued) { beforeStartActions.forEach { it() } - animations.forEach { it.setUpOnAnimationCommitted(this) } + animations.forEach { it.setUpOnAnimationQueued(this) } } - valuesCommitted = true + animationsQueued = true } /** - * Mark all [animations] as part of a fully-committed set of animations. + * Mark all [animations] as part of a fully-queued set of animations. * - * This step is necessary for individual animations in a set to not remove each other when being committing. + * This step is necessary for individual animations in a set to not remove each other when being queued. */ - open fun markAnimationsAsFullyCommitted() { - animations.forEach { it.isPartOfAFullyCommittedSet = true } + open fun markAnimationsAsFullyQueued() { + animations.forEach { it.isPartOfAFullyQueuedSet = true } } override fun start() { @@ -129,16 +130,16 @@ open class BlendableAnimator : Animator() { innerAnimator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { animations.forEach { it.runEndActions() } - valuesCommitted = false + animationsQueued = false } }) if (repeatCount > 0) { cancelAnimatorOnViewsDetached() } - commitFutureValuesIfNotCommitted() + queueAnimationsIfNotAlreadyQueued() innerAnimator.start() - markAnimationsAsFullyCommitted() + markAnimationsAsFullyQueued() } private fun cancelAnimatorOnViewsDetached() { diff --git a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt index a98aa20..f001299 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/animator/SinglePropertyAnimation.kt @@ -32,24 +32,24 @@ class SinglePropertyAnimation( var animator: BlendableAnimator? = null @VisibleForTesting internal set /** - * Whether this animation has started running. Note that it can be committed (i.e. queued) and not started. + * Whether this animation has started running. Note that it can be queued and not started. */ val isStarted: Boolean get() = animator?.isStarted ?: false /** - * Whether the set that this animation belongs to is done committing all of its animations. Useful for figuring out - * when to cancel unstarted (but committed) animations. + * Whether the set that this animation belongs to is done queuing all of its animations. Useful for figuring out + * when to cancel unstarted (but queued) animations. */ - var isPartOfAFullyCommittedSet: Boolean = false + var isPartOfAFullyQueuedSet: Boolean = false /** - * Set up the starting values for this animation and commit the [targetValue] as [property]'s future value. + * Set up the starting values for this animation and queue the [targetValue] as [property]'s future value. */ - fun setUpOnAnimationCommitted(animator: BlendableAnimator) { + fun setUpOnAnimationQueued(animator: BlendableAnimator) { this.animator = animator - property.setUpOnAnimationCommitted(subject) + property.setUpOnAnimationQueued(subject) startValue = property.getFutureValue(subject) previousValue = startValue - property.addCommittedAnimation(subject, this) + property.addQueuedAnimation(subject, this) property.addInterruptableEndActions(subject, *interruptibleEndActions.toTypedArray()) } @@ -73,6 +73,6 @@ class SinglePropertyAnimation( */ fun runEndActions() { property.runEndActions(subject, this) - property.removeCommittedAnimation(subject, this) + property.removeQueuedAnimation(subject, this) } } \ No newline at end of file diff --git a/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt b/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt index 707d706..7203da0 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/dsl/BuilderTraits.kt @@ -36,8 +36,8 @@ interface AnimationStarter { addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { val animators = animatorSet.childAnimators.mapNotNull { it as? BlendableAnimator } - animators.forEach { it.commitFutureValuesIfNotCommitted() } - animators.forEach { it.markAnimationsAsFullyCommitted() } + animators.forEach { it.queueAnimationsIfNotAlreadyQueued() } + animators.forEach { it.markAnimationsAsFullyQueued() } } }) } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt index f5a1a57..ce6c0ef 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AdditiveProperty.kt @@ -10,7 +10,7 @@ import kotlin.math.roundToInt * To properly blend animations, it includes methods like [getFutureValue], [addInterruptableEndActions], and * [getAnimationData]. * - * We store data on committed animations ([AnimationData]) on each subject, ideally. This systematically prevents leaks + * We store data on queued animations ([AnimationData]) on each subject, ideally. This systematically prevents leaks * of references to [Subject]s. The mechanism for storing is described by the implementation of [getAnimationData] */ interface AdditiveProperty { @@ -52,19 +52,19 @@ interface AdditiveProperty { /** * Perform any setup tasks when an animation on this property starts. */ - fun setUpOnAnimationCommitted(subject: Subject) { } + fun setUpOnAnimationQueued(subject: Subject) { } /** - * Add a committed animation to the [subject]'s [AnimationData] for this property. + * Add a queued animation to the [subject]'s [AnimationData] for this property. */ - fun addCommittedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = - getAnimationData(subject).addCommittedAnimation(animation) + fun addQueuedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = + getAnimationData(subject).addQueuedAnimation(animation) /** - * Remove a committed animation from the [subject]'s [AnimationData] for this property. + * Remove a queued animation from the [subject]'s [AnimationData] for this property. */ - fun removeCommittedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = - getAnimationData(subject).removeCommittedAnimation(animation) + fun removeQueuedAnimation(subject: Subject, animation: SinglePropertyAnimation<*>) = + getAnimationData(subject).removeQueuedAnimation(animation) /** * Add [endActions] to the [subject]'s [AnimationData] for this property so they can be cancelled if another @@ -79,7 +79,7 @@ interface AdditiveProperty { */ fun runEndActions(subject: Subject, animation: SinglePropertyAnimation<*>) { val animationData = getAnimationData(subject) - if (animation == animationData.committedAnimations.lastOrNull()) { + if (animation == animationData.queuedAnimations.lastOrNull()) { animationData.interruptableEndActions.forEach { it() } animationData.interruptableEndActions.clear() } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt index 8b8adca..bbc6f16 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/AnimationData.kt @@ -4,17 +4,17 @@ import com.wealthfront.blend.animator.SinglePropertyAnimation import kotlin.math.roundToInt /** - * A container for storing data about committed animations to facilitate blending. + * A container for storing data about queued animations to facilitate blending. * * Associated with a specific subject and a property. */ class AnimationData { /** - * All animations currently committed to run on the associated subject for the associated property. These animations + * All animations currently queued to run on the associated subject for the associated property. These animations * may or may not be currently running, however. */ - val committedAnimations: MutableList> = mutableListOf() + val queuedAnimations: MutableList> = mutableListOf() /** * End actions that will be cancelled if another animation is started for the associated subject.property. */ @@ -22,44 +22,44 @@ class AnimationData { var isLatestAnimationDone = false /** - * Get the value of this property as if all committed animations have finished. + * Get the value of this property as if all queued animations have finished. */ val futureValue: Float? - get() = committedAnimations.lastOrNull { it.isStarted }?.targetValue + get() = queuedAnimations.lastOrNull { it.isStarted }?.targetValue /** * Register a new animation that has started. This cancels any [interruptableEndActions] that we have, since a new * animation has started. * - * This also cancels all non-started [committedAnimations], in order to avoid animating to any unexpected values. + * This also cancels all non-started [queuedAnimations], in order to avoid animating to any unexpected values. * See [this github issue](https://github.com/wealthfront/blend/issues/4) for more information. */ - fun addCommittedAnimation(animation: SinglePropertyAnimation<*>) { + fun addQueuedAnimation(animation: SinglePropertyAnimation<*>) { if (isLatestAnimationDone) { - committedAnimations -= committedAnimations.last() + queuedAnimations -= queuedAnimations.last() } isLatestAnimationDone = false - committedAnimations.filter { - !it.isStarted && it.isPartOfAFullyCommittedSet + queuedAnimations.filter { + !it.isStarted && it.isPartOfAFullyQueuedSet }.forEach { it.markCancelled() - committedAnimations.remove(it) + queuedAnimations.remove(it) } - committedAnimations += animation + queuedAnimations += animation interruptableEndActions.clear() } /** - * Remove a committed animation because it has either finished or been cancelled. + * Remove a queued animation because it has either finished or been cancelled. */ - fun removeCommittedAnimation(animation: SinglePropertyAnimation<*>) { + fun removeQueuedAnimation(animation: SinglePropertyAnimation<*>) { animation.markCancelled() - if (committedAnimations.lastOrNull() == animation && committedAnimations.size > 1) { + if (queuedAnimations.lastOrNull() == animation && queuedAnimations.size > 1) { isLatestAnimationDone = true } else { - committedAnimations -= animation - if (isLatestAnimationDone && committedAnimations.size == 1) { - committedAnimations.clear() + queuedAnimations -= animation + if (isLatestAnimationDone && queuedAnimations.size == 1) { + queuedAnimations.clear() isLatestAnimationDone = false } } diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt index 39e9c0c..a7b3041 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/DimensionViewProperties.kt @@ -35,7 +35,7 @@ object DimensionViewProperties { override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationCommitted(subject: View) { + override fun setUpOnAnimationQueued(subject: View) { if (subject.visibility == GONE) { setValue(subject, 0f) subject.visibility = VISIBLE @@ -67,7 +67,7 @@ object DimensionViewProperties { override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationCommitted(subject: View) { + override fun setUpOnAnimationQueued(subject: View) { if (subject.visibility == GONE) { setValue(subject, 0f) subject.visibility = VISIBLE diff --git a/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt b/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt index d4f44de..3e7effe 100644 --- a/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt +++ b/blend-library/src/main/java/com/wealthfront/blend/properties/ViewPropertySupport.kt @@ -44,7 +44,7 @@ fun makeExternalAdditiveViewProperty( interpolateAction(startValue, endValue, timeFraction, subject) } - override fun setUpOnAnimationCommitted(subject: Subject) { + override fun setUpOnAnimationQueued(subject: Subject) { setUpAction?.invoke(subject) } } @@ -101,7 +101,7 @@ internal fun makeAdditiveViewProperty( interpolateAction(startValue, endValue, timeFraction, subject) } - override fun setUpOnAnimationCommitted(subject: Subject) { + override fun setUpOnAnimationQueued(subject: Subject) { setUpAction?.invoke(subject) } } @@ -159,7 +159,7 @@ internal fun wrapViewProperty( override fun getAnimationData(subject: View): AnimationData = subject.getAnimationData(id) - override fun setUpOnAnimationCommitted(subject: View) { + override fun setUpOnAnimationQueued(subject: View) { setUpAction?.invoke(subject) } } diff --git a/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt b/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt index 41ef1bd..e5c6a8f 100644 --- a/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt +++ b/blend-test/src/main/java/com/wealthfront/blend/mock/ImmediateBlendableAnimator.kt @@ -5,7 +5,7 @@ import com.wealthfront.blend.animator.BlendableAnimator class ImmediateBlendableAnimator : BlendableAnimator() { override fun start() { - commitFutureValuesIfNotCommitted() + queueAnimationsIfNotAlreadyQueued() innerAnimator.listeners?.forEach { it.onAnimationStart(innerAnimator) } animations.forEach { it.applyChanges(1f) } everyFrameActions.forEach { it() } diff --git a/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt b/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt index ba39486..70325de 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt @@ -332,7 +332,7 @@ class BlendDslTest { return animationDatas[subject] ?: AnimationData().also { animationDatas[subject] = it } } - override fun setUpOnAnimationCommitted(subject: TestObject) { + override fun setUpOnAnimationQueued(subject: TestObject) { addInterruptableEndActions(subject, { animationDatas.remove(subject) }) diff --git a/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt b/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt index 7320ad7..d03310b 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt @@ -145,7 +145,7 @@ class BlendableAnimatorTest { secondBlendableAnimator.interpolator = LinearInterpolator() whenever(X.getCurrentValue(subject)).thenReturn(0f) - blendableAnimator.commitFutureValuesIfNotCommitted() + blendableAnimator.queueAnimationsIfNotAlreadyQueued() secondBlendableAnimator.start() verify(secondValueAnimator).addUpdateListener(animatorUpdateListenerCaptor.capture()) diff --git a/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt b/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt index 2220235..4cad17b 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt @@ -35,8 +35,8 @@ class AnimationDataTest { fun getFutureValue_futureValue() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addCommittedAnimation(firstAnimation) - animationData.addCommittedAnimation(latestAnimation) + animationData.addQueuedAnimation(firstAnimation) + animationData.addQueuedAnimation(latestAnimation) assertThat(animationData.futureValue).isWithin(0.01f).of(20f) } @@ -44,9 +44,9 @@ class AnimationDataTest { fun getFutureValue_futureValue_animationEndedEarly() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addCommittedAnimation(firstAnimation) - animationData.addCommittedAnimation(latestAnimation) - animationData.removeCommittedAnimation(latestAnimation) + animationData.addQueuedAnimation(firstAnimation) + animationData.addQueuedAnimation(latestAnimation) + animationData.removeQueuedAnimation(latestAnimation) assertThat(animationData.futureValue).isWithin(0.01f).of(20f) } @@ -54,10 +54,10 @@ class AnimationDataTest { fun getFutureValue_futureValue_animationEndedEarly_clear() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) val latestAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addCommittedAnimation(firstAnimation) - animationData.addCommittedAnimation(latestAnimation) - animationData.removeCommittedAnimation(latestAnimation) - animationData.removeCommittedAnimation(firstAnimation) + animationData.addQueuedAnimation(firstAnimation) + animationData.addQueuedAnimation(latestAnimation) + animationData.removeQueuedAnimation(latestAnimation) + animationData.removeQueuedAnimation(firstAnimation) assertThat(animationData.futureValue).isNull() } @@ -66,12 +66,12 @@ class AnimationDataTest { val delayedAnimation = SinglePropertyAnimation(view, property, 10f) .apply { animator = unstartedAnimator - isPartOfAFullyCommittedSet = true + isPartOfAFullyQueuedSet = true } val newAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addCommittedAnimation(delayedAnimation) - animationData.addCommittedAnimation(newAnimation) - animationData.removeCommittedAnimation(newAnimation) + animationData.addQueuedAnimation(delayedAnimation) + animationData.addQueuedAnimation(newAnimation) + animationData.removeQueuedAnimation(newAnimation) assertThat(animationData.futureValue).isNull() } @@ -80,12 +80,12 @@ class AnimationDataTest { val delayedAnimation = SinglePropertyAnimation(view, property, 10f) .apply { animator = unstartedAnimator - isPartOfAFullyCommittedSet = false + isPartOfAFullyQueuedSet = false } val newAnimation = SinglePropertyAnimation(view, property, 20f) - animationData.addCommittedAnimation(delayedAnimation) - animationData.addCommittedAnimation(newAnimation) - animationData.removeCommittedAnimation(newAnimation) + animationData.addQueuedAnimation(delayedAnimation) + animationData.addQueuedAnimation(newAnimation) + animationData.removeQueuedAnimation(newAnimation) assertThat(animationData.futureValue).isNotNull() } } \ No newline at end of file From baf9ab5d1387eee46bdcce4ffefad82df7b08642 Mon Sep 17 00:00:00 2001 From: Ryan Moelter Date: Sun, 3 May 2020 13:32:59 -0700 Subject: [PATCH 4/4] Fix tests --- .../java/com/wealthfront/TestExtensions.kt | 39 +++++++++++++++++++ .../properties/AnimationDataTest.kt | 33 ++++++++++++++-- blend-test/build.gradle.kts | 2 + .../wealthfront/ThemedApplicationProvider.kt | 9 +++++ .../ViewAssertionExtensionsTest.kt | 4 -- .../java/com/wealthfront/ViewSubjectTest.kt | 4 -- .../com/wealthfront/blend/BlendDslTest.kt | 6 +-- .../blend/animator/BlendableAnimatorTest.kt | 10 ++--- .../mock/ImmediateBlendableAnimatorTest.kt | 5 +-- .../blend/mock/MockBlendDslTest.kt | 3 -- 10 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 blend-library/src/test/java/com/wealthfront/TestExtensions.kt rename {blend-test/src/test/java/com/wealthfront/blend => blend-library/src/test/java/com/wealthfront}/properties/AnimationDataTest.kt (81%) create mode 100644 blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt diff --git a/blend-library/src/test/java/com/wealthfront/TestExtensions.kt b/blend-library/src/test/java/com/wealthfront/TestExtensions.kt new file mode 100644 index 0000000..b0f9088 --- /dev/null +++ b/blend-library/src/test/java/com/wealthfront/TestExtensions.kt @@ -0,0 +1,39 @@ +@file:Suppress("UNCHECKED_CAST") + +package com.wealthfront + +import org.mockito.ArgumentCaptor +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.stubbing.OngoingStubbing +import org.mockito.stubbing.Stubber + +internal fun whenever(x: T): OngoingStubbing = `when`(x) + +internal fun Stubber.whenever(x: T): T = `when`(x) + +// Casting to avoid a kotlin-NPE ( See : https://medium.com/@elye.project/befriending-kotlin-and-mockito-1c2e7b0ef791 ) +internal fun any(): T { + Mockito.any() + return null as T +} + +internal fun eq(value: T): T { + Mockito.eq(value) + return value +} + +internal fun isA(value: Class): T { + Mockito.isA(value) + return null as T +} + +internal fun argThat(argumentMatcher: (R) -> Boolean): T { + Mockito.argThat { arg: R -> argumentMatcher(arg) } + return null as T +} + +internal fun ArgumentCaptor.captureNotNull(): T { + this.capture() + return null as T +} diff --git a/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt b/blend-library/src/test/java/com/wealthfront/properties/AnimationDataTest.kt similarity index 81% rename from blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt rename to blend-library/src/test/java/com/wealthfront/properties/AnimationDataTest.kt index 4cad17b..52683e0 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/properties/AnimationDataTest.kt +++ b/blend-library/src/test/java/com/wealthfront/properties/AnimationDataTest.kt @@ -34,7 +34,15 @@ class AnimationDataTest { @Test fun getFutureValue_futureValue() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } val latestAnimation = SinglePropertyAnimation(view, property, 20f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } animationData.addQueuedAnimation(firstAnimation) animationData.addQueuedAnimation(latestAnimation) assertThat(animationData.futureValue).isWithin(0.01f).of(20f) @@ -43,7 +51,15 @@ class AnimationDataTest { @Test fun getFutureValue_futureValue_animationEndedEarly() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } val latestAnimation = SinglePropertyAnimation(view, property, 20f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } animationData.addQueuedAnimation(firstAnimation) animationData.addQueuedAnimation(latestAnimation) animationData.removeQueuedAnimation(latestAnimation) @@ -53,7 +69,15 @@ class AnimationDataTest { @Test fun getFutureValue_futureValue_animationEndedEarly_clear() { val firstAnimation = SinglePropertyAnimation(view, property, 10f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } val latestAnimation = SinglePropertyAnimation(view, property, 20f) + .apply { + animator = startedAnimator + isPartOfAFullyQueuedSet = true + } animationData.addQueuedAnimation(firstAnimation) animationData.addQueuedAnimation(latestAnimation) animationData.removeQueuedAnimation(latestAnimation) @@ -78,14 +102,15 @@ class AnimationDataTest { @Test fun doNotRemoveChainedAnimation_newAnimationAdded_chainedAnimationIsPartOfUnfinishedSet() { val delayedAnimation = SinglePropertyAnimation(view, property, 10f) - .apply { - animator = unstartedAnimator - isPartOfAFullyQueuedSet = false - } + .apply { + animator = unstartedAnimator + isPartOfAFullyQueuedSet = false + } val newAnimation = SinglePropertyAnimation(view, property, 20f) animationData.addQueuedAnimation(delayedAnimation) animationData.addQueuedAnimation(newAnimation) animationData.removeQueuedAnimation(newAnimation) + delayedAnimation.animator = startedAnimator assertThat(animationData.futureValue).isNotNull() } } \ No newline at end of file diff --git a/blend-test/build.gradle.kts b/blend-test/build.gradle.kts index 3232cc5..5b7b7ef 100644 --- a/blend-test/build.gradle.kts +++ b/blend-test/build.gradle.kts @@ -19,6 +19,8 @@ dependencies { exclude(group = "commons-logging", module = "commons-logging") exclude(group = "org.apache.httpcomponents", module = "httpclient") } + + testImplementation(Libs.testCore) } android { diff --git a/blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt b/blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt new file mode 100644 index 0000000..f911575 --- /dev/null +++ b/blend-test/src/test/java/com/wealthfront/ThemedApplicationProvider.kt @@ -0,0 +1,9 @@ +package com.wealthfront + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider.getApplicationContext + +val application: Context + @JvmName("application") + get() = getApplicationContext() diff --git a/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt b/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt index b06b008..3bc72cc 100644 --- a/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt +++ b/blend-test/src/test/java/com/wealthfront/ViewAssertionExtensionsTest.kt @@ -17,17 +17,13 @@ import com.google.common.truth.Truth.assertThat import com.wealthfront.ViewSubject.Companion.VIEW_SUBJECT_FACTORY import com.wealthfront.blend.ALPHA_FULL import com.wealthfront.blend.ALPHA_TRANSPARENT -import com.wealthfront.blend.BuildConfig import com.wealthfront.ktx.wrapContentHeight import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class ViewAssertionExtensionsTest { val view = View(application) diff --git a/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt b/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt index 537aa4f..4663f64 100644 --- a/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt +++ b/blend-test/src/test/java/com/wealthfront/ViewSubjectTest.kt @@ -17,17 +17,13 @@ import com.wealthfront.ViewAssertions.assertThatView import com.wealthfront.ViewSubject.Companion.VIEW_SUBJECT_FACTORY import com.wealthfront.blend.ALPHA_FULL import com.wealthfront.blend.ALPHA_TRANSPARENT -import com.wealthfront.blend.BuildConfig import com.wealthfront.ktx.wrapContentHeight import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class ViewSubjectTest { val view = View(application) diff --git a/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt b/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt index 70325de..54ac3e2 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/BlendDslTest.kt @@ -13,25 +13,23 @@ import android.widget.TextView import androidx.annotation.ColorInt import androidx.core.content.ContextCompat.getColor import com.google.common.truth.Truth.assertThat -import com.wealthfront.blend.test.R import com.wealthfront.ViewAssertions.assertThatView +import com.wealthfront.application import com.wealthfront.blend.animator.BlendableAnimator import com.wealthfront.blend.mock.ImmediateBlend import com.wealthfront.blend.properties.AdditiveProperty import com.wealthfront.blend.properties.AnimationData +import com.wealthfront.blend.test.R import com.wealthfront.ktx.matchParentHeight import com.wealthfront.ktx.wrapContentHeight import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config // Tests requiring animation listeners use an `Instant` subclass of Blend to avoid the robolectric issue // described here: https://github.com/robolectric/robolectric/issues/2930 @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class BlendDslTest { val blend: Blend = Blend() diff --git a/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt b/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt index d03310b..bba98d1 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/animator/BlendableAnimatorTest.kt @@ -8,9 +8,7 @@ import android.view.ViewGroup import android.view.animation.LinearInterpolator import com.google.common.truth.Truth.assertThat import com.wealthfront.any -import com.wealthfront.blend.BuildConfig import com.wealthfront.blend.properties.AdditiveProperty -import com.wealthfront.blend.properties.AnimationData import com.wealthfront.blend.properties.AdditiveViewProperties.ALPHA import com.wealthfront.blend.properties.AdditiveViewProperties.ELEVATION import com.wealthfront.blend.properties.AdditiveViewProperties.HEIGHT @@ -32,6 +30,7 @@ import com.wealthfront.blend.properties.AdditiveViewProperties.WIDTH import com.wealthfront.blend.properties.AdditiveViewProperties.X import com.wealthfront.blend.properties.AdditiveViewProperties.Y import com.wealthfront.blend.properties.AdditiveViewProperties.Z +import com.wealthfront.blend.properties.AnimationData import com.wealthfront.eq import com.wealthfront.whenever import org.junit.Before @@ -44,10 +43,8 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class BlendableAnimatorTest { lateinit var blendableAnimator: BlendableAnimator @@ -129,7 +126,7 @@ class BlendableAnimatorTest { } @Test - fun animateValue_multipleAnimators_respectFutureAnimations() { + fun animateValue_multipleAnimators_respectStartedAnimations() { setUpTag(subject, X) blendableAnimator.addAnimation(SinglePropertyAnimation( subject = subject, @@ -144,8 +141,9 @@ class BlendableAnimatorTest { )) secondBlendableAnimator.interpolator = LinearInterpolator() whenever(X.getCurrentValue(subject)).thenReturn(0f) + whenever(valueAnimator.isStarted).thenReturn(true) - blendableAnimator.queueAnimationsIfNotAlreadyQueued() + blendableAnimator.start() secondBlendableAnimator.start() verify(secondValueAnimator).addUpdateListener(animatorUpdateListenerCaptor.capture()) diff --git a/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt b/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt index 8e06304..2c99587 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/mock/ImmediateBlendableAnimatorTest.kt @@ -2,9 +2,9 @@ package com.wealthfront.blend.mock import android.view.View import com.google.common.truth.Truth.assertThat +import com.wealthfront.application import com.wealthfront.blend.ALPHA_FULL import com.wealthfront.blend.ALPHA_TRANSPARENT -import com.wealthfront.blend.BuildConfig import com.wealthfront.blend.animator.BlendableAnimator import com.wealthfront.blend.animator.SinglePropertyAnimation import com.wealthfront.blend.properties.AdditiveViewProperties.ALPHA @@ -13,11 +13,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations.initMocks import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment.application -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class ImmediateBlendableAnimatorTest { lateinit var animator: BlendableAnimator diff --git a/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt b/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt index 691aaf5..ee9dc1f 100644 --- a/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt +++ b/blend-test/src/test/java/com/wealthfront/blend/mock/MockBlendDslTest.kt @@ -3,7 +3,6 @@ package com.wealthfront.blend.mock import android.view.View import android.view.ViewGroup import com.google.common.truth.Truth.assertThat -import com.wealthfront.blend.BuildConfig import com.wealthfront.blend.builder.SetPropertyValueAction import com.wealthfront.blend.properties.AdditiveViewProperties.ALPHA import com.wealthfront.blend.properties.AdditiveViewProperties.TRANSLATION_X @@ -14,10 +13,8 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations.initMocks import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class) class MockBlendDslTest { lateinit var blend: MockBlend