Add SwitchPreference for SpaLib
Temporarily use material3's Checkbox instead of Switch, before the androidx.compose.material3_material3 library is updated to date. Bug: 235727273 Test: Unit test & Manual with Codelab App Change-Id: I3121f9a389ff29f88b2cf2c4171dceee1aa03dbe
This commit is contained in:
@@ -50,7 +50,7 @@ private fun HomePage() {
|
||||
)
|
||||
|
||||
PreferencePageProvider.EntryItem()
|
||||
|
||||
SwitchPreferencePageProvider.EntryItem()
|
||||
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
|
||||
|
||||
SliderPageProvider.EntryItem()
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.android.settingslib.spa.framework.api.SettingsPageRepository
|
||||
object Destinations {
|
||||
const val Home = "Home"
|
||||
const val Preference = "Preference"
|
||||
const val SwitchPreference = "SwitchPreference"
|
||||
const val Argument = "Argument"
|
||||
const val Slider = "Slider"
|
||||
}
|
||||
@@ -29,6 +30,7 @@ val codelabPageRepository = SettingsPageRepository(
|
||||
allPages = listOf(
|
||||
HomePageProvider,
|
||||
PreferencePageProvider,
|
||||
SwitchPreferencePageProvider,
|
||||
ArgumentPageProvider,
|
||||
SliderPageProvider,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
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.compose.stateOf
|
||||
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.preference.SwitchPreference
|
||||
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
object SwitchPreferencePageProvider : SettingsPageProvider {
|
||||
override val name = Destinations.SwitchPreference
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
SwitchPreferencePage()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EntryItem() {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Sample SwitchPreference"
|
||||
override val onClick = navigator(Destinations.SwitchPreference)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwitchPreferencePage() {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
SampleSwitchPreference()
|
||||
SampleSwitchPreferenceWithSummary()
|
||||
SampleSwitchPreferenceWithAsyncSummary()
|
||||
SampleNotChangeableSwitchPreference()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SampleSwitchPreference() {
|
||||
val checked = rememberSaveable { mutableStateOf(false) }
|
||||
SwitchPreference(remember {
|
||||
object : SwitchPreferenceModel {
|
||||
override val title = "SwitchPreference"
|
||||
override val checked = checked
|
||||
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SampleSwitchPreferenceWithSummary() {
|
||||
val checked = rememberSaveable { mutableStateOf(true) }
|
||||
SwitchPreference(remember {
|
||||
object : SwitchPreferenceModel {
|
||||
override val title = "SwitchPreference"
|
||||
override val summary = stateOf("With summary")
|
||||
override val checked = checked
|
||||
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SampleSwitchPreferenceWithAsyncSummary() {
|
||||
val checked = rememberSaveable { mutableStateOf(true) }
|
||||
val summary = produceState(initialValue = " ") {
|
||||
delay(1000L)
|
||||
value = "Async summary"
|
||||
}
|
||||
SwitchPreference(remember {
|
||||
object : SwitchPreferenceModel {
|
||||
override val title = "SwitchPreference"
|
||||
override val summary = summary
|
||||
override val checked = checked
|
||||
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SampleNotChangeableSwitchPreference() {
|
||||
val checked = rememberSaveable { mutableStateOf(true) }
|
||||
SwitchPreference(remember {
|
||||
object : SwitchPreferenceModel {
|
||||
override val title = "SwitchPreference"
|
||||
override val summary = stateOf("Not changeable")
|
||||
override val changeable = stateOf(false)
|
||||
override val checked = checked
|
||||
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SwitchPreferencePagePreview() {
|
||||
SettingsTheme {
|
||||
SwitchPreferencePage()
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ android_library {
|
||||
"androidx.navigation_navigation-compose",
|
||||
"com.google.android.material_material",
|
||||
],
|
||||
kotlincflags: ["-Xjvm-default=all"],
|
||||
kotlincflags: [
|
||||
"-Xjvm-default=all",
|
||||
"-Xopt-in=kotlin.RequiresOptIn",
|
||||
],
|
||||
min_sdk_version: "31",
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ android {
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
freeCompilerArgs = ["-Xjvm-default=all"]
|
||||
freeCompilerArgs = ["-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn"]
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.preference
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.android.settingslib.spa.framework.compose.stateOf
|
||||
import com.android.settingslib.spa.framework.compose.toState
|
||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.widget.ui.SettingsSwitch
|
||||
|
||||
/**
|
||||
* The widget model for [SwitchPreference] widget.
|
||||
*/
|
||||
interface SwitchPreferenceModel {
|
||||
/**
|
||||
* The title of this [SwitchPreference].
|
||||
*/
|
||||
val title: String
|
||||
|
||||
/**
|
||||
* The summary of this [SwitchPreference].
|
||||
*/
|
||||
val summary: State<String>
|
||||
get() = stateOf("")
|
||||
|
||||
/**
|
||||
* Indicates whether this [SwitchPreference] is checked.
|
||||
*
|
||||
* This can be `null` during the data loading before the data is available.
|
||||
*/
|
||||
val checked: State<Boolean?>
|
||||
|
||||
/**
|
||||
* Indicates whether this [SwitchPreference] is changeable.
|
||||
*
|
||||
* Not changeable [SwitchPreference] will be displayed in disabled style.
|
||||
*/
|
||||
val changeable: State<Boolean>
|
||||
get() = stateOf(true)
|
||||
|
||||
/**
|
||||
* The switch change handler of this [SwitchPreference].
|
||||
*
|
||||
* If `null`, this [SwitchPreference] is not [toggleable].
|
||||
*/
|
||||
val onCheckedChange: ((newChecked: Boolean) -> Unit)?
|
||||
}
|
||||
|
||||
/**
|
||||
* SwitchPreference widget.
|
||||
*
|
||||
* Data is provided through [SwitchPreferenceModel].
|
||||
*/
|
||||
@Composable
|
||||
fun SwitchPreference(model: SwitchPreferenceModel) {
|
||||
InternalSwitchPreference(
|
||||
title = model.title,
|
||||
summary = model.summary,
|
||||
checked = model.checked,
|
||||
changeable = model.changeable,
|
||||
onCheckedChange = model.onCheckedChange,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun InternalSwitchPreference(
|
||||
title: String,
|
||||
summary: State<String> = "".toState(),
|
||||
checked: State<Boolean?>,
|
||||
changeable: State<Boolean> = true.toState(),
|
||||
paddingStart: Dp = SettingsDimension.itemPaddingStart,
|
||||
paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
|
||||
paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
|
||||
onCheckedChange: ((newChecked: Boolean) -> Unit)?,
|
||||
) {
|
||||
val checkedValue = checked.value
|
||||
val indication = LocalIndication.current
|
||||
val modifier = remember(checkedValue) {
|
||||
if (checkedValue != null && onCheckedChange != null) {
|
||||
Modifier.toggleable(
|
||||
value = checkedValue,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
indication = indication,
|
||||
enabled = changeable.value,
|
||||
role = Role.Switch,
|
||||
onValueChange = onCheckedChange,
|
||||
)
|
||||
} else Modifier
|
||||
}
|
||||
BasePreference(
|
||||
title = title,
|
||||
summary = summary,
|
||||
modifier = modifier,
|
||||
enabled = changeable,
|
||||
paddingStart = paddingStart,
|
||||
paddingEnd = paddingEnd,
|
||||
paddingVertical = paddingVertical,
|
||||
) {
|
||||
SettingsSwitch(checked = checked, changeable = changeable)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SwitchPreferencePreview() {
|
||||
SettingsTheme {
|
||||
Column {
|
||||
InternalSwitchPreference(
|
||||
title = "Use Dark theme",
|
||||
checked = true.toState(),
|
||||
onCheckedChange = {},
|
||||
)
|
||||
InternalSwitchPreference(
|
||||
title = "Use Dark theme",
|
||||
summary = "Summary".toState(),
|
||||
checked = false.toState(),
|
||||
onCheckedChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsSwitch(
|
||||
checked: State<Boolean?>,
|
||||
changeable: State<Boolean>,
|
||||
onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
|
||||
) {
|
||||
// TODO: Replace Checkbox with Switch when the androidx.compose.material3_material3 library is
|
||||
// updated to date.
|
||||
val checkedValue = checked.value
|
||||
if (checkedValue != null) {
|
||||
Checkbox(
|
||||
checked = checkedValue,
|
||||
onCheckedChange = onCheckedChange,
|
||||
enabled = changeable.value,
|
||||
)
|
||||
} else {
|
||||
Checkbox(
|
||||
checked = false,
|
||||
onCheckedChange = null,
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.preference
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsOff
|
||||
import androidx.compose.ui.test.assertIsOn
|
||||
import androidx.compose.ui.test.isToggleable
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spa.framework.compose.stateOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SwitchPreferenceTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun title_displayed() {
|
||||
composeTestRule.setContent {
|
||||
TestSwitchPreference(changeable = true)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("SwitchPreference").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toggleable_initialStateIsCorrect() {
|
||||
composeTestRule.setContent {
|
||||
TestSwitchPreference(changeable = true)
|
||||
}
|
||||
|
||||
composeTestRule.onNode(isToggleable()).assertIsOff()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun click_changeable_withEffect() {
|
||||
composeTestRule.setContent {
|
||||
TestSwitchPreference(changeable = true)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("SwitchPreference").performClick()
|
||||
composeTestRule.onNode(isToggleable()).assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun click_notChangeable_noEffect() {
|
||||
composeTestRule.setContent {
|
||||
TestSwitchPreference(changeable = false)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("SwitchPreference").performClick()
|
||||
composeTestRule.onNode(isToggleable()).assertIsOff()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TestSwitchPreference(changeable: Boolean) {
|
||||
val checked = rememberSaveable { mutableStateOf(false) }
|
||||
SwitchPreference(remember {
|
||||
object : SwitchPreferenceModel {
|
||||
override val title = "SwitchPreference"
|
||||
override val checked = checked
|
||||
override val changeable = stateOf(changeable)
|
||||
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user