Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a7aace0 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Unify slider control between Volume Panel and Volume Dialog" into main

parents 7f5756ba dfd9d303
Loading
Loading
Loading
Loading
+27 −64
Original line number Diff line number Diff line
@@ -35,9 +35,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon as MaterialIcon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -73,11 +73,15 @@ import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
import com.android.systemui.volume.ui.slider.AccessibilityParams
import com.android.systemui.volume.ui.slider.Haptics
import com.android.systemui.volume.ui.slider.Slider
import kotlin.math.round
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VolumeSlider(
    state: SliderState,
@@ -102,17 +106,6 @@ fun VolumeSlider(
        return
    }

    val value by valueState(state)
    val interactionSource = remember { MutableInteractionSource() }
    val hapticsViewModel: SliderHapticsViewModel? =
        setUpHapticsViewModel(
            value,
            state.valueRange,
            state.hapticFilter,
            interactionSource,
            hapticsViewModelFactory,
        )

    Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) {
        Row(
            horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -134,60 +127,30 @@ fun VolumeSlider(
            )
            button?.invoke()
        }

        Slider(
            value = value,
            value = state.value,
            valueRange = state.valueRange,
            onValueChange = { newValue ->
                hapticsViewModel?.addVelocityDataPoint(newValue)
                onValueChange(newValue)
            },
            onValueChangeFinished = {
                hapticsViewModel?.onValueChangeEnded()
                onValueChangeFinished?.invoke()
            },
            enabled = state.isEnabled,
            modifier =
                Modifier.height(40.dp)
                    .padding(top = 4.dp, bottom = 12.dp)
                    .sysuiResTag(state.label)
                    .clearAndSetSemantics {
                        if (state.isEnabled) {
                            contentDescription = state.label
                            state.a11yClickDescription?.let {
                                customActions =
                                    listOf(
                                        CustomAccessibilityAction(it) {
                                            onIconTapped()
                                            true
                                        }
                                    )
                            }

                            state.a11yStateDescription?.let { stateDescription = it }
                            progressBarRangeInfo =
                                ProgressBarRangeInfo(state.value, state.valueRange)
                        } else {
                            disabled()
                            contentDescription =
                                state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
                        }
                        setProgress { targetValue ->
                            val targetDirection =
                                when {
                                    targetValue > value -> 1
                                    targetValue < value -> -1
                                    else -> 0
                                }

                            val newValue =
                                (value + targetDirection * state.a11yStep).coerceIn(
                                    state.valueRange.start,
                                    state.valueRange.endInclusive,
            onValueChanged = onValueChange,
            onValueChangeFinished = { onValueChangeFinished?.invoke() },
            isEnabled = state.isEnabled,
            stepDistance = state.a11yStep,
            accessibilityParams =
                AccessibilityParams(
                    label = state.label,
                    disabledMessage = state.disabledMessage,
                    currentStateDescription = state.a11yStateDescription,
                ),
            haptics =
                hapticsViewModelFactory?.let {
                    Haptics.Enabled(
                        hapticsViewModelFactory = it,
                        hapticFilter = state.hapticFilter,
                        orientation = Orientation.Horizontal,
                    )
                            onValueChange(newValue)
                            true
                        }
                    },
                } ?: Haptics.Disabled,
            modifier =
                Modifier.height(40.dp).padding(top = 4.dp, bottom = 12.dp).sysuiResTag(state.label),
        )
        state.disabledMessage?.let { disabledMessage ->
            AnimatedVisibility(visible = !state.isEnabled) {
@@ -348,7 +311,7 @@ private fun SliderIcon(
}

@Composable
fun setUpHapticsViewModel(
private fun setUpHapticsViewModel(
    value: Float,
    valueRange: ClosedFloatingPointRange<Float>,
    hapticFilter: SliderHapticFeedbackFilter,
+3 −0
Original line number Diff line number Diff line
@@ -4178,4 +4178,7 @@
    <string name="qs_edit_mode_reset_dialog_content">
        All Quick Settings tiles will reset to the device’s original settings
    </string>

    <!-- Template that joins disabled message with the label for the voice over. [CHAR LIMIT=NONE] -->
    <string name="volume_slider_disabled_message_template"><xliff:g example="Notification" id="stream_name">%1$s</xliff:g>, <xliff:g example="Disabled because ring is muted" id="disabled_message">%2$s</xliff:g></string>
</resources>
+43 −76
Original line number Diff line number Diff line
@@ -29,18 +29,13 @@ import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.SliderState
import androidx.compose.material3.VerticalSlider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
@@ -49,16 +44,17 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.graphics.painter.DrawablePainter
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.compose.VolumeDialogSliderTrack
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import com.android.systemui.volume.ui.slider.AccessibilityParams
import com.android.systemui.volume.ui.slider.Haptics
import com.android.systemui.volume.ui.slider.Slider
import javax.inject.Inject
import kotlin.math.round
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
@@ -90,7 +86,7 @@ constructor(
    }
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun VolumeDialogSlider(
    viewModel: VolumeDialogSliderViewModel,
@@ -108,59 +104,8 @@ private fun VolumeDialogSlider(
        )
    val collectedSliderStateModel by viewModel.state.collectAsStateWithLifecycle(null)
    val sliderStateModel = collectedSliderStateModel ?: return

    val steps = with(sliderStateModel.valueRange) { endInclusive - start - 1 }.toInt()

    val interactionSource = remember { MutableInteractionSource() }
    val hapticsViewModel: SliderHapticsViewModel? =
        hapticsViewModelFactory?.let {
            rememberViewModel(traceName = "SliderHapticsViewModel") {
                it.create(
                    interactionSource,
                    sliderStateModel.valueRange,
                    Orientation.Vertical,
                    VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(
                        sliderStateModel.valueRange
                    ),
                    VolumeHapticsConfigsProvider.seekableSliderTrackerConfig,
                )
            }
        }

    val sliderState =
        remember(steps, sliderStateModel.valueRange) {
            SliderState(
                    value = sliderStateModel.value,
                    valueRange = sliderStateModel.valueRange,
                    steps = steps,
                )
                .also { sliderState ->
                    sliderState.onValueChangeFinished = {
                        viewModel.onSliderChangeFinished(sliderState.value)
                        hapticsViewModel?.onValueChangeEnded()
                    }
                    sliderState.onValueChange = { newValue ->
                        sliderState.value = newValue
                        hapticsViewModel?.addVelocityDataPoint(newValue)
                        overscrollViewModel.setSlider(
                            value = sliderState.value,
                            min = sliderState.valueRange.start,
                            max = sliderState.valueRange.endInclusive,
                        )
                        viewModel.setStreamVolume(newValue, true)
                    }
                }
        }

    var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderStateModel.value)) }
    LaunchedEffect(sliderStateModel.value) {
        val value = sliderStateModel.value
        sliderState.value = value
        if (value != lastDiscreteStep) {
            lastDiscreteStep = value
            hapticsViewModel?.onValueChange(value)
        }
    }
    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect {
            when (it) {
@@ -171,24 +116,33 @@ private fun VolumeDialogSlider(
        }
    }

    VerticalSlider(
        state = sliderState,
        enabled = !sliderStateModel.isDisabled,
        reverseDirection = true,
    Slider(
        value = sliderStateModel.value,
        valueRange = sliderStateModel.valueRange,
        onValueChanged = { value ->
            overscrollViewModel.setSlider(
                value = value,
                min = sliderStateModel.valueRange.start,
                max = sliderStateModel.valueRange.endInclusive,
            )
            viewModel.setStreamVolume(value, true)
        },
        onValueChangeFinished = { viewModel.onSliderChangeFinished(it) },
        isEnabled = !sliderStateModel.isDisabled,
        isReverseDirection = true,
        isVertical = true,
        colors = colors,
        interactionSource = interactionSource,
        modifier =
            modifier.pointerInput(Unit) {
                coroutineScope {
                    val currentContext = currentCoroutineContext()
                    awaitPointerEventScope {
                        while (currentContext.isActive) {
                            viewModel.onTouchEvent(awaitPointerEvent())
                        }
                    }
                }
            },
        track = {
        haptics =
            hapticsViewModelFactory?.let {
                Haptics.Enabled(
                    hapticsViewModelFactory = it,
                    hapticFilter = SliderHapticFeedbackFilter(),
                    orientation = Orientation.Vertical,
                )
            } ?: Haptics.Disabled,
        stepDistance = 1f,
        track = { sliderState ->
            VolumeDialogSliderTrack(
                sliderState,
                colors = colors,
@@ -201,6 +155,19 @@ private fun VolumeDialogSlider(
                },
            )
        },
        accessibilityParams =
            AccessibilityParams(label = "", currentStateDescription = "", disabledMessage = ""),
        modifier =
            modifier.pointerInput(Unit) {
                coroutineScope {
                    val currentContext = currentCoroutineContext()
                    awaitPointerEventScope {
                        while (currentContext.isActive) {
                            viewModel.onTouchEvent(awaitPointerEvent())
                        }
                    }
                }
            },
    )
}

+2 −2
Original line number Diff line number Diff line
@@ -116,8 +116,8 @@ constructor(
        override val isEnabled: Boolean
            get() = true

        override val a11yStep: Int
            get() = 1
        override val a11yStep: Float
            get() = 1f

        override val disabledMessage: String?
            get() = null
+2 −2
Original line number Diff line number Diff line
@@ -165,7 +165,7 @@ constructor(
            label = label,
            disabledMessage = disabledMessage,
            isEnabled = isEnabled,
            a11yStep = volumeRange.step,
            a11yStep = volumeRange.step.toFloat(),
            a11yClickDescription =
                if (isAffectedByMute) {
                    context.getString(
@@ -307,7 +307,7 @@ constructor(
        override val label: String,
        override val disabledMessage: String?,
        override val isEnabled: Boolean,
        override val a11yStep: Int,
        override val a11yStep: Float,
        override val a11yClickDescription: String?,
        override val a11yStateDescription: String?,
        override val isMutable: Boolean,
Loading