diff options
4 files changed, 301 insertions, 1 deletions
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt index 5adbc32c9ece..171a16118052 100644 --- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt +++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt @@ -52,6 +52,8 @@ private fun HomePage() { PreferencePageProvider.EntryItem() ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0) + + SliderPageProvider.EntryItem() } } diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt index da278d1f1e7a..c24541a903da 100644 --- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt +++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt @@ -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, ) diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt new file mode 100644 index 000000000000..67d53b7d3fa0 --- /dev/null +++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt @@ -0,0 +1,107 @@ +/* + * 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.api.SettingsPageProvider +import com.android.settingslib.spa.framework.navigator +import com.android.settingslib.spa.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.slider.SettingsSlider +import com.android.settingslib.spa.widget.slider.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() + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/slider/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/slider/SettingsSlider.kt new file mode 100644 index 000000000000..a69244a18532 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/slider/SettingsSlider.kt @@ -0,0 +1,185 @@ +/* + * 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.slider + +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.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, + ) + } +} |