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

Test: manually test on device.
Bug: 241857459
Change-Id: Ica8b10fcb9c12a1bab654452262ab765a187bafd
This commit is contained in:
Kelly
2022-08-09 11:22:46 +08:00
parent 4487864093
commit 9022ca4f44
4 changed files with 301 additions and 1 deletions

View File

@@ -52,6 +52,8 @@ private fun HomePage() {
PreferencePageProvider.EntryItem()
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
SliderPageProvider.EntryItem()
}
}

View File

@@ -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,
)

View File

@@ -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.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()
}
}

View File

@@ -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.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,
)
}
}