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

Commit 9022ca4f authored by Kelly's avatar Kelly
Browse files

Add the slider widget to SPA and create samples in codelab slider page.

Test: manually test on device.
Bug: 241857459
Change-Id: Ica8b10fcb9c12a1bab654452262ab765a187bafd
parent 44878640
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ private fun HomePage() {
        PreferencePageProvider.EntryItem()

        ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)

        SliderPageProvider.EntryItem()
    }
}

+7 −1
Original line number Diff line number Diff line
@@ -22,9 +22,15 @@ object Destinations {
    const val Home = "Home"
    const val Preference = "Preference"
    const val Argument = "Argument"
    const val Slider = "Slider"
}

val codelabPageRepository = SettingsPageRepository(
    allPages = listOf(HomePageProvider, PreferencePageProvider, ArgumentPageProvider),
    allPages = listOf(
        HomePageProvider,
        PreferencePageProvider,
        ArgumentPageProvider,
        SliderPageProvider,
    ),
    startDestination = Destinations.Home,
)
+107 −0
Original line number Diff line number Diff line
/*
 * 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.settingslib.spa.codelab.page

import android.os.Bundle
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessAlarm
import androidx.compose.material.icons.outlined.MusicNote
import androidx.compose.material.icons.outlined.MusicOff
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsSlider
import com.android.settingslib.spa.widget.ui.SettingsSliderModel

object SliderPageProvider : SettingsPageProvider {
    override val name = Destinations.Slider

    @Composable
    override fun Page(arguments: Bundle?) {
        SliderPage()
    }

    @Composable
    fun EntryItem() {
        Preference(object : PreferenceModel {
            override val title = "Sample Slider"
            override val onClick = navigator(Destinations.Slider)
        })
    }
}

@Composable
private fun SliderPage() {
    Column(Modifier.verticalScroll(rememberScrollState())) {
        SettingsSlider(object : SettingsSliderModel {
            override val title = "Slider"
            override val initValue = 40
        })

        SettingsSlider(object : SettingsSliderModel {
            override val title = "Slider with icon"
            override val initValue = 30
            override val onValueChangeFinished = {
                println("onValueChangeFinished")
            }
            override val icon = Icons.Outlined.AccessAlarm
        })

        val initValue = 0
        var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
        var sliderPosition by remember { mutableStateOf(initValue) }
        SettingsSlider(object : SettingsSliderModel {
            override val title = "Slider with changeable icon"
            override val initValue = initValue
            override val onValueChange = { it: Int ->
                sliderPosition = it
                icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
            }
            override val onValueChangeFinished = {
                println("onValueChangeFinished: the value is $sliderPosition")
            }
            override val icon = icon
        })

        SettingsSlider(object : SettingsSliderModel {
            override val title = "Slider with steps"
            override val initValue = 2
            override val valueRange = 1..5
            override val showSteps = true
        })
    }
}

@Preview(showBackground = true)
@Composable
private fun SliderPagePreview() {
    SettingsTheme {
        SliderPage()
    }
}
+185 −0
Original line number Diff line number Diff line
/*
 * 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.settingslib.spa.widget.ui

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessAlarm
import androidx.compose.material.icons.outlined.MusicNote
import androidx.compose.material.icons.outlined.MusicOff
import androidx.compose.material3.Icon
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.BaseLayout
import kotlin.math.roundToInt

/**
 * The widget model for [SettingsSlider] widget.
 */
interface SettingsSliderModel {
    /**
     * The title of this [SettingsSlider].
     */
    val title: String

    /**
     * The initial position of the [SettingsSlider].
     */
    val initValue: Int

    /**
     * The value range for this [SettingsSlider].
     */
    val valueRange: IntRange
        get() = 0..100

    /**
     * The lambda to be invoked during the value change by dragging or a click. This callback is
     * used to get the real time value of the [SettingsSlider].
     */
    val onValueChange: ((value: Int) -> Unit)?
        get() = null

    /**
     * The lambda to be invoked when value change has ended. This callback is used to get when the
     * user has completed selecting a new value by ending a drag or a click.
     */
    val onValueChangeFinished: (() -> Unit)?
        get() = null

    /**
     * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default.
     */
    val icon: ImageVector?
        get() = null

    /**
     * Indicates whether to show step marks. If show step marks, when user finish sliding,
     * the slider will automatically jump to the nearest step mark. Otherwise, the slider hides
     * the step marks by default.
     *
     * The step is fixed to 1.
     */
    val showSteps: Boolean
        get() = false
}

/**
 * Settings slider widget.
 *
 * Data is provided through [SettingsSliderModel].
 */
@Composable
fun SettingsSlider(model: SettingsSliderModel) {
    SettingsSlider(
        title = model.title,
        initValue = model.initValue,
        valueRange = model.valueRange,
        onValueChange = model.onValueChange,
        onValueChangeFinished = model.onValueChangeFinished,
        icon = model.icon,
        showSteps = model.showSteps,
    )
}

@Composable
internal fun SettingsSlider(
    title: String,
    initValue: Int,
    valueRange: IntRange = 0..100,
    onValueChange: ((value: Int) -> Unit)? = null,
    onValueChangeFinished: (() -> Unit)? = null,
    icon: ImageVector? = null,
    showSteps: Boolean = false,
    modifier: Modifier = Modifier,
) {
    var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
    BaseLayout(
        title = title,
        subTitle = {
            Slider(
                value = sliderPosition,
                onValueChange = {
                    sliderPosition = it
                    onValueChange?.invoke(sliderPosition.roundToInt())
                },
                modifier = modifier,
                valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
                steps = if (showSteps) (valueRange.count() - 2) else 0,
                onValueChangeFinished = onValueChangeFinished,
            )
        },
        icon = if (icon != null) ({
            Icon(imageVector = icon, contentDescription = null)
        }) else null,
    )
}

@Preview
@Composable
private fun SettingsSliderPreview() {
    SettingsTheme {
        val initValue = 30
        var sliderPosition by rememberSaveable { mutableStateOf(initValue) }
        SettingsSlider(
            title = "Alarm Volume",
            initValue = 30,
            onValueChange = { sliderPosition = it },
            onValueChangeFinished = {
                println("onValueChangeFinished: the value is $sliderPosition")
            },
            icon = Icons.Outlined.AccessAlarm,
        )
    }
}

@Preview
@Composable
private fun SettingsSliderIconChangePreview() {
    SettingsTheme {
        var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) }
        SettingsSlider(
            title = "Media Volume",
            initValue = 40,
            onValueChange = { it: Int ->
                icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
            },
            icon = icon,
        )
    }
}

@Preview
@Composable
private fun SettingsSliderStepsPreview() {
    SettingsTheme {
        SettingsSlider(
            title = "Display Text",
            initValue = 2,
            valueRange = 1..5,
            showSteps = true,
        )
    }
}