summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Coco Duan <cocod@google.com> 2025-02-10 11:32:51 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-10 11:32:51 -0800
commit52cb45a7e98b28cba214d43b15d460123d4119ed (patch)
tree7bfcd3bad0486fea8f656f884f2b5a8beac52109
parent502c8a76fa49d3cc821cb9eada96acd6327ae1b9 (diff)
parent7b89ab31a4ac56a5d65eaf6d37746861c3009044 (diff)
Merge "Reapply "Add custom actions to improve Voice Access swipe on UMO in hub"" into main
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt135
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt55
7 files changed, 240 insertions, 27 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9c57efc24a22..418a7a52a97e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1705,15 +1705,38 @@ private fun Umo(
contentScope: ContentScope?,
modifier: Modifier = Modifier,
) {
- if (SceneContainerFlag.isEnabled && contentScope != null) {
- contentScope.MediaCarousel(
- modifier = modifier.fillMaxSize(),
- isVisible = true,
- mediaHost = viewModel.mediaHost,
- carouselController = viewModel.mediaCarouselController,
- )
- } else {
- UmoLegacy(viewModel, modifier)
+ val showNextActionLabel = stringResource(R.string.accessibility_action_label_umo_show_next)
+ val showPreviousActionLabel =
+ stringResource(R.string.accessibility_action_label_umo_show_previous)
+
+ Box(
+ modifier =
+ modifier.thenIf(!viewModel.isEditMode) {
+ Modifier.semantics {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(showNextActionLabel) {
+ viewModel.onShowNextMedia()
+ true
+ },
+ CustomAccessibilityAction(showPreviousActionLabel) {
+ viewModel.onShowPreviousMedia()
+ true
+ },
+ )
+ }
+ }
+ ) {
+ if (SceneContainerFlag.isEnabled && contentScope != null) {
+ contentScope.MediaCarousel(
+ modifier = modifier.fillMaxSize(),
+ isVisible = true,
+ mediaHost = viewModel.mediaHost,
+ carouselController = viewModel.mediaCarouselController,
+ )
+ } else {
+ UmoLegacy(viewModel, modifier)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 85155157eda2..433894b58350 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -78,6 +78,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -120,6 +121,7 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -161,6 +163,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
whenever(mediaHost.visible).thenReturn(true)
+ whenever(kosmos.mediaCarouselController.mediaCarouselScrollHandler)
+ .thenReturn(mediaCarouselScrollHandler)
kosmos.powerInteractor.setAwakeForTest()
@@ -903,6 +907,20 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ fun onShowPreviousMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowPreviousMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(-1)
+ }
+
+ @Test
+ fun onShowNextMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowNextMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(1)
+ }
+
+ @Test
@EnableFlags(FLAG_BOUNCER_UI_REVAMP)
fun uiIsBlurred_whenPrimaryBouncerIsShowing() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index d073cf1ac9db..c2f0ab92b32b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.media.controls.ui.view
+import android.content.res.Resources
import android.testing.TestableLooper
import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,16 +28,21 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -42,7 +50,9 @@ import org.mockito.MockitoAnnotations
class MediaCarouselScrollHandlerTest : SysuiTestCase() {
private val carouselWidth = 1038
+ private val settingsButtonWidth = 200
private val motionEventUp = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ private lateinit var testableLooper: TestableLooper
@Mock lateinit var mediaCarousel: MediaScrollView
@Mock lateinit var pageIndicator: PageIndicator
@@ -53,6 +63,9 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var logSmartspaceImpression: (Boolean) -> Unit
@Mock lateinit var logger: MediaUiEventLogger
+ @Mock lateinit var contentContainer: ViewGroup
+ @Mock lateinit var settingsButton: View
+ @Mock lateinit var resources: Resources
lateinit var executor: FakeExecutor
private val clock = FakeSystemClock()
@@ -63,6 +76,11 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(clock)
+ testableLooper = TestableLooper.get(this)
+ PhysicsAnimatorTestUtils.prepareForTest()
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+ whenever(mediaCarousel.contentContainer).thenReturn(contentContainer)
mediaCarouselScrollHandler =
MediaCarouselScrollHandler(
mediaCarousel,
@@ -74,13 +92,17 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
closeGuts,
falsingManager,
logSmartspaceImpression,
- logger
+ logger,
)
mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth
-
whenever(mediaCarousel.touchListener).thenReturn(mediaCarouselScrollHandler.touchListener)
}
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
@Test
fun testCarouselScroll_shortScroll() {
whenever(mediaCarousel.isLayoutRtl).thenReturn(false)
@@ -128,4 +150,109 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
}
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(carouselWidth), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart_isRTL() {
+ setupMediaContainer(visibleIndex = 0)
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd_isRTL() {
+ setupMediaContainer(visibleIndex = 1)
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testScrollByStep_noScroll_notDismissible() {
+ setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel, never()).animationTargetX = anyFloat()
+ }
+
+ private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
+ whenever(contentContainer.childCount).thenReturn(2)
+ val child1: View = mock()
+ val child2: View = mock()
+ whenever(child1.left).thenReturn(0)
+ whenever(child2.left).thenReturn(carouselWidth)
+ whenever(contentContainer.getChildAt(0)).thenReturn(child1)
+ whenever(contentContainer.getChildAt(1)).thenReturn(child2)
+
+ whenever(settingsButton.width).thenReturn(settingsButtonWidth)
+ whenever(settingsButton.context).thenReturn(context)
+ whenever(settingsButton.resources).thenReturn(resources)
+ whenever(settingsButton.resources.getDimensionPixelSize(anyInt())).thenReturn(20)
+ mediaCarouselScrollHandler.onSettingsButtonUpdated(settingsButton)
+
+ mediaCarouselScrollHandler.visibleMediaIndex = visibleIndex
+ mediaCarouselScrollHandler.showsSettingsButton = showsSettingsButton
+ }
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d18a90a17abe..86292039d93d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1351,6 +1351,10 @@
<string name="accessibility_action_label_shrink_widget">Decrease height</string>
<!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_expand_widget">Increase height</string>
+ <!-- Label for accessibility action to show the next media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_next">Show next</string>
+ <!-- Label for accessibility action to show the previous media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_previous">Show previous</string>
<!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
<!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 49003a735fbd..a4860dfc47ce 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -202,6 +202,12 @@ abstract class BaseCommunalViewModel(
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called as the user requests to switch to the previous player in UMO. */
+ open fun onShowPreviousMedia() {}
+
+ /** Called as the user requests to switch to the next player in UMO. */
+ open fun onShowNextMedia() {}
+
/** Called as the UI determines that a new widget has been added to the grid. */
open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 4bc44005d2fc..dd4018a9d7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -254,6 +254,14 @@ constructor(
}
}
+ override fun onShowPreviousMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(-1)
+ }
+
+ override fun onShowNextMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(1)
+ }
+
override fun onTapWidget(componentName: ComponentName, rank: Int) {
metricsLogger.logTapWidget(componentName.flattenToString(), rank)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index d63c2e07b94f..0107a5278e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -23,11 +23,11 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
+import androidx.annotation.VisibleForTesting
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.tracing.TraceStateLogger
-import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
@@ -38,9 +38,10 @@ import com.android.systemui.res.R
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimator
+import kotlin.math.sign
private const val FLING_SLOP = 1000000
-private const val DISMISS_DELAY = 100L
+@VisibleForTesting const val DISMISS_DELAY = 100L
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
@@ -64,7 +65,7 @@ class MediaCarouselScrollHandler(
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingManager: FalsingManager,
private val logSmartspaceImpression: (Boolean) -> Unit,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
) {
/** Trace state logger for media carousel visibility */
private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
@@ -96,7 +97,7 @@ class MediaCarouselScrollHandler(
/** What's the currently visible player index? */
var visibleMediaIndex: Int = 0
- private set
+ @VisibleForTesting set
/** How much are we scrolled into the current media? */
private var scrollIntoCurrentMedia: Int = 0
@@ -137,14 +138,14 @@ class MediaCarouselScrollHandler(
eStart: MotionEvent?,
eCurrent: MotionEvent,
vX: Float,
- vY: Float
+ vY: Float,
) = onFling(vX, vY)
override fun onScroll(
down: MotionEvent?,
lastMotion: MotionEvent,
distanceX: Float,
- distanceY: Float
+ distanceY: Float,
) = onScroll(down!!, lastMotion, distanceX)
override fun onDown(e: MotionEvent): Boolean {
@@ -157,6 +158,7 @@ class MediaCarouselScrollHandler(
val touchListener =
object : Gefingerpoken {
override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+
override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
}
@@ -168,7 +170,7 @@ class MediaCarouselScrollHandler(
scrollX: Int,
scrollY: Int,
oldScrollX: Int,
- oldScrollY: Int
+ oldScrollY: Int,
) {
if (playerWidthPlusPadding == 0) {
return
@@ -177,7 +179,7 @@ class MediaCarouselScrollHandler(
val relativeScrollX = scrollView.relativeScrollX
onMediaScrollingChanged(
relativeScrollX / playerWidthPlusPadding,
- relativeScrollX % playerWidthPlusPadding
+ relativeScrollX % playerWidthPlusPadding,
)
}
}
@@ -209,7 +211,7 @@ class MediaCarouselScrollHandler(
0,
carouselWidth,
carouselHeight,
- cornerRadius.toFloat()
+ cornerRadius.toFloat(),
)
}
}
@@ -235,7 +237,7 @@ class MediaCarouselScrollHandler(
getMaxTranslation().toFloat(),
0.0f,
1.0f,
- Math.abs(contentTranslation)
+ Math.abs(contentTranslation),
)
val settingsTranslation =
(1.0f - settingsOffset) *
@@ -323,7 +325,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -391,7 +393,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
} else {
@@ -430,7 +432,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = vX,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -583,10 +585,35 @@ class MediaCarouselScrollHandler(
// We need to post this to wait for the active player becomes visible.
mainExecutor.executeDelayed(
{ scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
- SCROLL_DELAY
+ SCROLL_DELAY,
)
}
+ /**
+ * Scrolls the media carousel by the number of players specified by [step]. If scrolling beyond
+ * the carousel's bounds:
+ * - If the carousel is not dismissible, the settings button is displayed.
+ * - If the carousel is dismissible, no action taken.
+ *
+ * @param step A positive number means next, and negative means previous.
+ */
+ fun scrollByStep(step: Int) {
+ val destIndex = visibleMediaIndex + step
+ if (destIndex >= mediaContent.childCount || destIndex < 0) {
+ if (!showsSettingsButton) return
+ var translation = getMaxTranslation() * sign(-step.toFloat())
+ translation = if (isRtl) -translation else translation
+ PhysicsAnimator.getInstance(this)
+ .spring(CONTENT_TRANSLATION, translation, config = translationConfig)
+ .start()
+ scrollView.animationTargetX = translation
+ } else if (scrollView.getContentTranslation() != 0.0f) {
+ resetTranslation(true)
+ } else {
+ scrollToPlayer(destIndex = destIndex)
+ }
+ }
+
companion object {
private val CONTENT_TRANSLATION =
object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {