Make UMO squishy and add motion to widgets only for split shade
Test: atest MediaCarouselControllerTest and MediaViewControllerTest Bug: 227478127 Change-Id: I4eae9413ae89950e1589487b2ff7c38e4ea8fa58
This commit is contained in:
@@ -16,6 +16,6 @@
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="@dimen/qs_media_album_radius" />
|
||||
</shape>
|
||||
@@ -11,6 +11,7 @@ import android.util.MathUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.PathInterpolator
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.internal.logging.InstanceId
|
||||
@@ -95,7 +96,8 @@ class MediaCarouselController @Inject constructor(
|
||||
* finished
|
||||
*/
|
||||
@MediaLocation
|
||||
private var currentEndLocation: Int = -1
|
||||
@VisibleForTesting
|
||||
var currentEndLocation: Int = -1
|
||||
|
||||
/**
|
||||
* The ending location of the view where it ends when all animations and transitions have
|
||||
@@ -126,7 +128,8 @@ class MediaCarouselController @Inject constructor(
|
||||
lateinit var settingsButton: View
|
||||
private set
|
||||
private val mediaContent: ViewGroup
|
||||
private val pageIndicator: PageIndicator
|
||||
@VisibleForTesting
|
||||
val pageIndicator: PageIndicator
|
||||
private val visualStabilityCallback: OnReorderingAllowedListener
|
||||
private var needsReordering: Boolean = false
|
||||
private var keysNeedRemoval = mutableSetOf<String>()
|
||||
@@ -149,6 +152,27 @@ class MediaCarouselController @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ANIMATION_BASE_DURATION = 2200f
|
||||
const val DURATION = 167f
|
||||
const val DETAILS_DELAY = 1067f
|
||||
const val CONTROLS_DELAY = 1400f
|
||||
const val PAGINATION_DELAY = 1900f
|
||||
const val MEDIATITLES_DELAY = 1000f
|
||||
const val MEDIACONTAINERS_DELAY = 967f
|
||||
val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F)
|
||||
val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F)
|
||||
|
||||
fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
|
||||
val transformStartFraction = delay / ANIMATION_BASE_DURATION
|
||||
val transformDurationFraction = duration / ANIMATION_BASE_DURATION
|
||||
val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
|
||||
return MathUtils.constrain((squishinessToTime - transformStartFraction) /
|
||||
transformDurationFraction, 0F, 1F)
|
||||
}
|
||||
}
|
||||
|
||||
private val configListener = object : ConfigurationController.ConfigurationListener {
|
||||
override fun onDensityOrFontScaleChanged() {
|
||||
// System font changes should only happen when UMO is offscreen or a flicker may occur
|
||||
@@ -633,12 +657,17 @@ class MediaCarouselController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePageIndicatorAlpha() {
|
||||
@VisibleForTesting
|
||||
fun updatePageIndicatorAlpha() {
|
||||
val hostStates = mediaHostStatesManager.mediaHostStates
|
||||
val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
|
||||
val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
|
||||
val startAlpha = if (startIsVisible) 1.0f else 0.0f
|
||||
val endAlpha = if (endIsVisible) 1.0f else 0.0f
|
||||
// when squishing in split shade, only use endState, which keeps changing
|
||||
// to provide squishFraction
|
||||
val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
|
||||
val endAlpha = (if (endIsVisible) 1.0f else 0.0f) *
|
||||
calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
|
||||
var alpha = 1.0f
|
||||
if (!endIsVisible || !startIsVisible) {
|
||||
var progress = currentTransitionProgress
|
||||
@@ -687,6 +716,7 @@ class MediaCarouselController @Inject constructor(
|
||||
mediaCarouselScrollHandler.setCarouselBounds(
|
||||
currentCarouselWidth, currentCarouselHeight)
|
||||
updatePageIndicatorLocation()
|
||||
updatePageIndicatorAlpha()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -203,6 +203,14 @@ class MediaHost constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override var squishFraction: Float = 1.0f
|
||||
set(value) {
|
||||
if (!value.equals(field)) {
|
||||
field = value
|
||||
changedListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
override var showsOnlyActiveMedia: Boolean = false
|
||||
set(value) {
|
||||
if (!value.equals(field)) {
|
||||
@@ -253,6 +261,7 @@ class MediaHost constructor(
|
||||
override fun copy(): MediaHostState {
|
||||
val mediaHostState = MediaHostStateHolder()
|
||||
mediaHostState.expansion = expansion
|
||||
mediaHostState.squishFraction = squishFraction
|
||||
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
|
||||
mediaHostState.measurementInput = measurementInput?.copy()
|
||||
mediaHostState.visible = visible
|
||||
@@ -271,6 +280,9 @@ class MediaHost constructor(
|
||||
if (expansion != other.expansion) {
|
||||
return false
|
||||
}
|
||||
if (squishFraction != other.squishFraction) {
|
||||
return false
|
||||
}
|
||||
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
|
||||
return false
|
||||
}
|
||||
@@ -289,6 +301,7 @@ class MediaHost constructor(
|
||||
override fun hashCode(): Int {
|
||||
var result = measurementInput?.hashCode() ?: 0
|
||||
result = 31 * result + expansion.hashCode()
|
||||
result = 31 * result + squishFraction.hashCode()
|
||||
result = 31 * result + falsingProtectionNeeded.hashCode()
|
||||
result = 31 * result + showsOnlyActiveMedia.hashCode()
|
||||
result = 31 * result + if (visible) 1 else 2
|
||||
@@ -328,6 +341,11 @@ interface MediaHostState {
|
||||
*/
|
||||
var expansion: Float
|
||||
|
||||
/**
|
||||
* Fraction of the height animation.
|
||||
*/
|
||||
var squishFraction: Float
|
||||
|
||||
/**
|
||||
* Is this host only showing active media or is it showing all of them including resumption?
|
||||
*/
|
||||
|
||||
@@ -18,8 +18,15 @@ package com.android.systemui.media
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.DURATION
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.calculateAlpha
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.animation.MeasurementOutput
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
@@ -50,6 +57,24 @@ class MediaViewController @Inject constructor(
|
||||
companion object {
|
||||
@JvmField
|
||||
val GUTS_ANIMATION_DURATION = 500L
|
||||
val controlIds = setOf(
|
||||
R.id.media_progress_bar,
|
||||
R.id.actionNext,
|
||||
R.id.actionPrev,
|
||||
R.id.action0,
|
||||
R.id.action1,
|
||||
R.id.action2,
|
||||
R.id.action3,
|
||||
R.id.action4,
|
||||
R.id.media_scrubbing_elapsed_time,
|
||||
R.id.media_scrubbing_total_time
|
||||
)
|
||||
|
||||
val detailIds = setOf(
|
||||
R.id.header_title,
|
||||
R.id.header_artist,
|
||||
R.id.actionPlayPause,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +82,7 @@ class MediaViewController @Inject constructor(
|
||||
*/
|
||||
lateinit var sizeChangedListener: () -> Unit
|
||||
private var firstRefresh: Boolean = true
|
||||
@VisibleForTesting
|
||||
private var transitionLayout: TransitionLayout? = null
|
||||
private val layoutController = TransitionLayoutController()
|
||||
private var animationDelay: Long = 0
|
||||
@@ -278,11 +304,48 @@ class MediaViewController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply squishFraction to a copy of viewState such that the cached version is untouched.
|
||||
*/
|
||||
internal fun squishViewState(
|
||||
viewState: TransitionViewState,
|
||||
squishFraction: Float
|
||||
): TransitionViewState {
|
||||
val squishedViewState = viewState.copy()
|
||||
squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
|
||||
controlIds.forEach { id ->
|
||||
squishedViewState.widgetStates.get(id)?.let { state ->
|
||||
state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION)
|
||||
}
|
||||
}
|
||||
|
||||
detailIds.forEach { id ->
|
||||
squishedViewState.widgetStates.get(id)?.let { state ->
|
||||
state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION)
|
||||
}
|
||||
}
|
||||
|
||||
RecommendationViewHolder.mediaContainersIds.forEach { id ->
|
||||
squishedViewState.widgetStates.get(id)?.let { state ->
|
||||
state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
|
||||
}
|
||||
}
|
||||
|
||||
RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
|
||||
squishedViewState.widgetStates.get(id)?.let { state ->
|
||||
state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
|
||||
}
|
||||
}
|
||||
|
||||
return squishedViewState
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
|
||||
* it's not available, it will recreate one by measuring, which may be expensive.
|
||||
*/
|
||||
private fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
||||
@VisibleForTesting
|
||||
fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
||||
if (state == null || state.measurementInput == null) {
|
||||
return null
|
||||
}
|
||||
@@ -291,41 +354,46 @@ class MediaViewController @Inject constructor(
|
||||
val viewState = viewStates[cacheKey]
|
||||
if (viewState != null) {
|
||||
// we already have cached this measurement, let's continue
|
||||
if (state.squishFraction <= 1f) {
|
||||
return squishViewState(viewState, state.squishFraction)
|
||||
}
|
||||
return viewState
|
||||
}
|
||||
// Copy the key since this might call recursively into it and we're using tmpKey
|
||||
cacheKey = cacheKey.copy()
|
||||
val result: TransitionViewState?
|
||||
|
||||
if (transitionLayout != null) {
|
||||
// Let's create a new measurement
|
||||
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
||||
result = transitionLayout!!.calculateViewState(
|
||||
state.measurementInput!!,
|
||||
constraintSetForExpansion(state.expansion),
|
||||
TransitionViewState())
|
||||
if (transitionLayout == null) {
|
||||
return null
|
||||
}
|
||||
// Let's create a new measurement
|
||||
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
||||
result = transitionLayout!!.calculateViewState(
|
||||
state.measurementInput!!,
|
||||
constraintSetForExpansion(state.expansion),
|
||||
TransitionViewState())
|
||||
|
||||
setGutsViewState(result)
|
||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
||||
// our cache. We only cache the start and the end states since the interpolation
|
||||
// is cheap
|
||||
viewStates[cacheKey] = result
|
||||
} else {
|
||||
// This is an interpolated state
|
||||
val startState = state.copy().also { it.expansion = 0.0f }
|
||||
|
||||
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
|
||||
// from the start and end state and interpolate them
|
||||
val startViewState = obtainViewState(startState) as TransitionViewState
|
||||
val endState = state.copy().also { it.expansion = 1.0f }
|
||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||
result = layoutController.getInterpolatedState(
|
||||
startViewState,
|
||||
endViewState,
|
||||
state.expansion)
|
||||
}
|
||||
setGutsViewState(result)
|
||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
||||
// our cache. We only cache the start and the end states since the interpolation
|
||||
// is cheap
|
||||
viewStates[cacheKey] = result
|
||||
} else {
|
||||
result = null
|
||||
// This is an interpolated state
|
||||
val startState = state.copy().also { it.expansion = 0.0f }
|
||||
|
||||
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
|
||||
// from the start and end state and interpolate them
|
||||
val startViewState = obtainViewState(startState) as TransitionViewState
|
||||
val endState = state.copy().also { it.expansion = 1.0f }
|
||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||
result = layoutController.getInterpolatedState(
|
||||
startViewState,
|
||||
endViewState,
|
||||
state.expansion)
|
||||
}
|
||||
if (state.squishFraction <= 1f) {
|
||||
return squishViewState(result, state.squishFraction)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -106,5 +106,20 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
||||
R.id.media_subtitle2,
|
||||
R.id.media_subtitle3
|
||||
)
|
||||
|
||||
val mediaTitlesAndSubtitlesIds = setOf(
|
||||
R.id.media_title1,
|
||||
R.id.media_title2,
|
||||
R.id.media_title3,
|
||||
R.id.media_subtitle1,
|
||||
R.id.media_subtitle2,
|
||||
R.id.media_subtitle3
|
||||
)
|
||||
|
||||
val mediaContainersIds = setOf(
|
||||
R.id.media_cover1_container,
|
||||
R.id.media_cover2_container,
|
||||
R.id.media_cover3_container
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,6 +691,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
if (mQSAnimator != null) {
|
||||
mQSAnimator.setPosition(expansion);
|
||||
}
|
||||
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD
|
||||
|| mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
|
||||
// At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
|
||||
// and media player expect no change by squishiness in lock screen shade
|
||||
mQsMediaHost.setSquishFraction(1.0F);
|
||||
} else {
|
||||
mQsMediaHost.setSquishFraction(mSquishinessFraction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setAlphaAnimationProgress(float progress) {
|
||||
|
||||
@@ -25,6 +25,11 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.DURATION
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
|
||||
import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
|
||||
@@ -398,4 +403,24 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
// added to the end because it was active less recently.
|
||||
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
|
||||
val delta = 0.0001F
|
||||
val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation(
|
||||
(PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION)
|
||||
val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation(
|
||||
(PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION)
|
||||
whenever(mediaHostStatesManager.mediaHostStates)
|
||||
.thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
|
||||
whenever(mediaHostState.visible).thenReturn(true)
|
||||
mediaCarouselController.currentEndLocation = LOCATION_QS
|
||||
whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
|
||||
mediaCarouselController.updatePageIndicatorAlpha()
|
||||
assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
|
||||
|
||||
whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
|
||||
mediaCarouselController.updatePageIndicatorAlpha()
|
||||
assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.DURATION
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
|
||||
import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
|
||||
import com.android.systemui.util.animation.MeasurementInput
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
import com.android.systemui.util.animation.TransitionViewState
|
||||
import com.android.systemui.util.animation.WidgetState
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.floatThat
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class MediaViewControllerTest : SysuiTestCase() {
|
||||
private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
|
||||
private val mediaHostStatesManager = MediaHostStatesManager()
|
||||
private val configurationController =
|
||||
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
|
||||
private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
|
||||
private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
|
||||
@Mock lateinit var logger: MediaViewLogger
|
||||
@Mock private lateinit var mockViewState: TransitionViewState
|
||||
@Mock private lateinit var mockCopiedState: TransitionViewState
|
||||
@Mock private lateinit var detailWidgetState: WidgetState
|
||||
@Mock private lateinit var controlWidgetState: WidgetState
|
||||
@Mock private lateinit var mediaTitleWidgetState: WidgetState
|
||||
@Mock private lateinit var mediaContainerWidgetState: WidgetState
|
||||
|
||||
val delta = 0.0001F
|
||||
|
||||
private lateinit var mediaViewController: MediaViewController
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mediaViewController =
|
||||
MediaViewController(context, configurationController, mediaHostStatesManager, logger)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
|
||||
mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
|
||||
player.measureState = TransitionViewState().apply { this.height = 100 }
|
||||
mediaHostStateHolder.expansion = 1f
|
||||
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||
mediaHostStateHolder.measurementInput =
|
||||
MeasurementInput(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
// Test no squish
|
||||
mediaHostStateHolder.squishFraction = 1f
|
||||
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
|
||||
|
||||
// Test half squish
|
||||
mediaHostStateHolder.squishFraction = 0.5f
|
||||
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() {
|
||||
mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
|
||||
recommendation.measureState = TransitionViewState().apply { this.height = 100 }
|
||||
mediaHostStateHolder.expansion = 1f
|
||||
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||
mediaHostStateHolder.measurementInput =
|
||||
MeasurementInput(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
// Test no squish
|
||||
mediaHostStateHolder.squishFraction = 1f
|
||||
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
|
||||
|
||||
// Test half squish
|
||||
mediaHostStateHolder.squishFraction = 0.5f
|
||||
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() {
|
||||
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
|
||||
whenever(mockCopiedState.widgetStates)
|
||||
.thenReturn(
|
||||
mutableMapOf(
|
||||
R.id.media_progress_bar to controlWidgetState,
|
||||
R.id.header_artist to detailWidgetState
|
||||
)
|
||||
)
|
||||
|
||||
val detailSquishMiddle =
|
||||
TRANSFORM_BEZIER.getInterpolation(
|
||||
(DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||
)
|
||||
mediaViewController.squishViewState(mockViewState, detailSquishMiddle)
|
||||
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
|
||||
|
||||
val detailSquishEnd =
|
||||
TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
|
||||
mediaViewController.squishViewState(mockViewState, detailSquishEnd)
|
||||
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
|
||||
|
||||
val controlSquishMiddle =
|
||||
TRANSFORM_BEZIER.getInterpolation(
|
||||
(CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||
)
|
||||
mediaViewController.squishViewState(mockViewState, controlSquishMiddle)
|
||||
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
|
||||
|
||||
val controlSquishEnd =
|
||||
TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
|
||||
mediaViewController.squishViewState(mockViewState, controlSquishEnd)
|
||||
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
|
||||
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
|
||||
whenever(mockCopiedState.widgetStates)
|
||||
.thenReturn(
|
||||
mutableMapOf(
|
||||
R.id.media_title1 to mediaTitleWidgetState,
|
||||
R.id.media_cover1_container to mediaContainerWidgetState
|
||||
)
|
||||
)
|
||||
|
||||
val containerSquishMiddle =
|
||||
TRANSFORM_BEZIER.getInterpolation(
|
||||
(MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||
)
|
||||
mediaViewController.squishViewState(mockViewState, containerSquishMiddle)
|
||||
verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
|
||||
|
||||
val containerSquishEnd =
|
||||
TRANSFORM_BEZIER.getInterpolation(
|
||||
(MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION
|
||||
)
|
||||
mediaViewController.squishViewState(mockViewState, containerSquishEnd)
|
||||
verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
|
||||
|
||||
val titleSquishMiddle =
|
||||
TRANSFORM_BEZIER.getInterpolation(
|
||||
(MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||
)
|
||||
mediaViewController.squishViewState(mockViewState, titleSquishMiddle)
|
||||
verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
|
||||
|
||||
val titleSquishEnd =
|
||||
TRANSFORM_BEZIER.getInterpolation(
|
||||
(MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION
|
||||
)
|
||||
mediaViewController.squishViewState(mockViewState, titleSquishEnd)
|
||||
verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user