summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anton Potapov <apotapov@google.com> 2024-10-28 15:03:58 +0000
committer Anton Potapov <apotapov@google.com> 2024-12-19 12:44:50 +0000
commiteebd136f1103bce576252a0129536a8e957dc074 (patch)
tree6c3424dfa550d06a042aa23c3c773cd37c97bff3
parent559895c29f230f97954b74cf891a2f3daf106af3 (diff)
Add overscroll animation to the Volume Dialog sliders
Flag: com.android.systemui.volume_redesign Test: manual on foldable Bug: 369995895 Change-Id: I31b79f00a18284fee4b7c691f0f484147153c545
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt152
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt2
10 files changed, 274 insertions, 19 deletions
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index a3bad8f012ac..054c4f2f052b 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -71,6 +71,9 @@
android:layout_height="0dp"
android:layout_marginTop="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
android:layout_marginBottom="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
+ android:clipChildren="false"
+ android:clipToOutline="false"
+ android:clipToPadding="false"
android:divider="@drawable/volume_dialog_floating_sliders_spacer"
android:gravity="bottom"
android:orientation="horizontal"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 35cfd082c537..05c4d1b662db 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2106,6 +2106,8 @@
<fraction name="volume_dialog_half_opened_bias">0.2</fraction>
+ <dimen name="volume_dialog_slider_max_deviation">56dp</dimen>
+
<dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
<dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index c0c525bcb37d..88af210b6a36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.dialog.sliders.dagger
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
@@ -37,6 +38,8 @@ interface VolumeDialogSliderComponent {
fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
+ fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder
+
@Subcomponent.Factory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
index adc2383c3a46..82885d65c513 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.volume.dialog.sliders.data.repository
-import android.annotation.SuppressLint
import android.view.MotionEvent
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import javax.inject.Inject
@@ -27,7 +26,6 @@ import kotlinx.coroutines.flow.filterNotNull
@VolumeDialogSliderScope
class VolumeDialogSliderTouchEventsRepository @Inject constructor() {
- @SuppressLint("SharedFlowCreation")
private val mutableSliderTouchEvents: MutableStateFlow<MotionEvent?> = MutableStateFlow(null)
val sliderTouchEvent: Flow<MotionEvent> = mutableSliderTouchEvents.filterNotNull()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index 2967fe8ca906..04dc80c45a18 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -17,14 +17,18 @@
package com.android.systemui.volume.dialog.sliders.domain.interactor
import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
/** Operates a state of particular slider of the Volume Dialog. */
@VolumeDialogSliderScope
@@ -32,6 +36,7 @@ class VolumeDialogSliderInteractor
@Inject
constructor(
private val sliderType: VolumeDialogSliderType,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
volumeDialogStateInteractor: VolumeDialogStateInteractor,
private val volumeDialogController: VolumeDialogController,
) {
@@ -47,7 +52,8 @@ constructor(
}
}
}
- .distinctUntilChanged()
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
fun setStreamVolume(userLevel: Int) {
with(volumeDialogController) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
new file mode 100644
index 000000000000..8109b50aa34a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.sliders.ui
+
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel.OverscrollEventModel
+import com.google.android.material.slider.Slider
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogSliderScope
+class VolumeDialogOverscrollViewBinder
+@Inject
+constructor(private val viewModel: VolumeDialogOverscrollViewModel) {
+
+ /**
+ * [viewsToAnimate] is an array of [View] to be affected by the overscroll animation. [view] is
+ * NOT animated by default.
+ */
+ fun CoroutineScope.bind(view: View, viewsToAnimate: Array<View>) {
+ val animationValueHolder = FloatValueHolder(0f)
+ val animation: SpringAnimation =
+ SpringAnimation(animationValueHolder)
+ .setSpring(
+ SpringForce(0f).apply {
+ stiffness = 800f
+ dampingRatio = 0.6f
+ }
+ )
+ .addUpdateListener { _, value, _ -> viewsToAnimate.setTranslationY(value) }
+
+ view.requireViewById<Slider>(R.id.volume_dialog_slider).addOnChangeListener { s, value, _ ->
+ viewModel.setSlider(value = value, min = s.valueFrom, max = s.valueTo)
+ }
+
+ viewModel.overscrollEvent
+ .onEach { event ->
+ when (event) {
+ is OverscrollEventModel.Animate -> {
+ animation.animateToFinalPosition(event.targetOffsetPx)
+ }
+ is OverscrollEventModel.Move -> {
+ animation.cancel()
+ viewsToAnimate.setTranslationY(event.touchOffsetPx)
+ animationValueHolder.value = event.touchOffsetPx
+ }
+ }
+ }
+ .launchIn(this)
+ }
+}
+
+private fun Array<View>.setTranslationY(translation: Float) {
+ for (viewToAnimate in this) {
+ viewToAnimate.translationY = translation
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index f30524638150..ba658fe30787 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -55,22 +55,21 @@ constructor(
viewModel.setStreamVolume(value.roundToInt(), fromUser)
}
- viewModel.state.onEach { it.bindToSlider(sliderView) }.launchIn(this)
+ viewModel.state.onEach { sliderView.setModel(it) }.launchIn(this)
}
@SuppressLint("UseCompatLoadingForDrawables")
- private suspend fun VolumeDialogSliderStateModel.bindToSlider(slider: Slider) {
- with(slider) {
- valueFrom = minValue
- valueTo = maxValue
- // coerce the current value to the new value range before animating it
- value = value.coerceIn(valueFrom, valueTo)
- setValueAnimated(
- value,
- jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
- )
- trackIconActiveEnd = context.getDrawable(iconRes)
- }
+ private suspend fun Slider.setModel(model: VolumeDialogSliderStateModel) {
+ valueFrom = model.minValue
+ valueTo = model.maxValue
+ // coerce the current value to the new value range before animating it. This prevents
+ // animating from the value that is outside of current [valueFrom, valueTo].
+ value = value.coerceIn(valueFrom, valueTo)
+ setValueAnimated(
+ model.value,
+ jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
+ )
+ trackIconActiveEnd = context.getDrawable(model.iconRes)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index c9b525930ed3..f066b56e7de0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -40,9 +40,17 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) {
view.requireViewById(R.id.volume_dialog_floating_sliders_container)
val mainSliderContainer: View =
view.requireViewById(R.id.volume_dialog_main_slider_container)
+ val background: View = view.requireViewById(R.id.volume_dialog_background)
+ val settingsButton: View = view.requireViewById(R.id.volume_dialog_settings)
+ val ringerDrawer: View = view.requireViewById(R.id.volume_ringer_drawer)
+
viewModel.sliders
.onEach { uiModel ->
- bindSlider(uiModel.sliderComponent, mainSliderContainer)
+ bindSlider(
+ uiModel.sliderComponent,
+ mainSliderContainer,
+ arrayOf(mainSliderContainer, background, settingsButton, ringerDrawer),
+ )
val floatingSliderViewBinders = uiModel.floatingSliderComponent
floatingSlidersContainer.ensureChildCount(
@@ -50,7 +58,8 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) {
count = floatingSliderViewBinders.size,
)
floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
- bindSlider(sliderComponent, floatingSlidersContainer.getChildAt(index))
+ val sliderContainer = floatingSlidersContainer.getChildAt(index)
+ bindSlider(sliderComponent, sliderContainer, arrayOf(sliderContainer))
}
}
.launchIn(this)
@@ -59,10 +68,12 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) {
private fun CoroutineScope.bindSlider(
component: VolumeDialogSliderComponent,
sliderContainer: View,
+ viewsToAnimate: Array<View>,
) {
with(component.sliderViewBinder()) { bind(sliderContainer) }
with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
+ with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
new file mode 100644
index 000000000000..0d41860d9f57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.sliders.ui.viewmodel
+
+import android.content.Context
+import android.view.MotionEvent
+import android.view.animation.PathInterpolator
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.transform
+
+@VolumeDialogSliderScope
+@OptIn(ExperimentalCoroutinesApi::class)
+class VolumeDialogOverscrollViewModel
+@Inject
+constructor(
+ context: Context,
+ private val inputEventsInteractor: VolumeDialogSliderInputEventsInteractor,
+) {
+
+ /**
+ * This is the ratio between the pointer distance and the dialog offset. The pointer has to
+ * travel this distance for a single point of an offset.
+ *
+ * When greater than 1 this makes the dialog to follow the touch behind.
+ */
+ private val offsetToTranslationRatio: Float = 3f
+ private val maxDeviation: Float =
+ context.resources
+ .getDimensionPixelSize(R.dimen.volume_dialog_slider_max_deviation)
+ .toFloat()
+ private val offsetInterpolator = PathInterpolator(0.15f, 0.00f, 0.20f, 1.00f)
+
+ private val sliderValue = MutableStateFlow<Slider?>(null)
+
+ val overscrollEvent: Flow<OverscrollEventModel> =
+ sliderValue
+ .filterNotNull()
+ .map { slider ->
+ when (slider.value) {
+ slider.min -> 1f
+ slider.max -> -1f
+ else -> 0f
+ }
+ }
+ .distinctUntilChanged()
+ .flatMapLatest { direction ->
+ if (direction == 0f) {
+ flowOf(OverscrollEventModel.Animate(0f))
+ } else {
+ overscrollEvents(direction)
+ }
+ }
+
+ fun setSlider(value: Float, min: Float, max: Float) {
+ sliderValue.value = Slider(value = value, min = min, max = max)
+ }
+
+ /**
+ * Returns a flow that for each another [MotionEvent] it receives maps into a path from the
+ * first event.
+ *
+ * Emits [OverscrollEventModel.Move] that follows the [SliderInputEvent.Touch] from the pointer
+ * down position. Emits [OverscrollEventModel.Animate] when the gesture is terminated to create
+ * a spring-back effect.
+ */
+ private fun overscrollEvents(direction: Float): Flow<OverscrollEventModel> {
+ var startPosition: Float? = null
+ return inputEventsInteractor.event
+ .mapNotNull { (it as? SliderInputEvent.Touch)?.event }
+ .transform { touchEvent ->
+ // Skip events from inside the slider bounds for the case when the user adjusts
+ // slider
+ // towards max when the slider is already on max value.
+ if (touchEvent.isFinalEvent()) {
+ startPosition = null
+ emit(OverscrollEventModel.Animate(0f))
+ return@transform
+ }
+ val currentStartPosition = startPosition
+ val newPosition: Float = touchEvent.rawY
+ if (currentStartPosition == null) {
+ startPosition = newPosition
+ } else {
+ val offset = (newPosition - currentStartPosition) / offsetToTranslationRatio
+ val interpolatedOffset =
+ if (areOfTheSameSign(direction, offset)) {
+ sign(offset) *
+ (maxDeviation *
+ offsetInterpolator.getInterpolation(
+ (abs(offset)) / maxDeviation
+ ))
+ } else {
+ 0f
+ }
+ emit(OverscrollEventModel.Move(interpolatedOffset))
+ }
+ }
+ }
+
+ /** @return true when the [MotionEvent] indicates the end of the gesture. */
+ private fun MotionEvent.isFinalEvent(): Boolean {
+ return actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL
+ }
+
+ /** Models overscroll event */
+ sealed interface OverscrollEventModel {
+
+ /** Notifies the consumed to move by the [touchOffsetPx]. */
+ data class Move(val touchOffsetPx: Float) : OverscrollEventModel
+
+ /** Notifies the consume to animate to the [targetOffsetPx]. */
+ data class Animate(val targetOffsetPx: Float) : OverscrollEventModel
+ }
+
+ private data class Slider(val value: Float, val min: Float, val max: Float)
+}
+
+private fun areOfTheSameSign(lhs: Float, rhs: Float): Boolean =
+ when {
+ lhs < 0 -> rhs < 0
+ lhs > 0 -> rhs > 0
+ else -> rhs == 0f
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
index 423100a1addf..44917dd4ba48 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.dialog.sliders.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
@@ -25,6 +26,7 @@ val Kosmos.volumeDialogSliderInteractor: VolumeDialogSliderInteractor by
Kosmos.Fixture {
VolumeDialogSliderInteractor(
volumeDialogSliderType,
+ applicationCoroutineScope,
volumeDialogStateInteractor,
volumeDialogController,
)