Merge "Create TwoTargetSwitchPreference for SpaLib"

This commit is contained in:
Chaohui Wang
2022-09-01 11:21:28 +00:00
committed by Android (Google) Code Review
9 changed files with 364 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
val galleryPageProviders = SettingsPageProviderRepository(
@@ -33,6 +34,7 @@ val galleryPageProviders = SettingsPageProviderRepository(
PreferenceMainPageProvider,
PreferencePageProvider,
SwitchPreferencePageProvider,
TwoTargetSwitchPreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
SpinnerPageProvider,

View File

@@ -48,5 +48,6 @@ private fun PreferenceMain() {
RegularScaffold(title = TITLE) {
PreferencePageProvider.EntryItem()
SwitchPreferencePageProvider.EntryItem()
TwoTargetSwitchPreferencePageProvider.EntryItem()
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.gallery.preference
import android.os.Bundle
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.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.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.SwitchPreferenceModel
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import kotlinx.coroutines.delay
private const val TITLE = "Sample TwoTargetSwitchPreference"
object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "TwoTargetSwitchPreference"
@Composable
override fun Page(arguments: Bundle?) {
TwoTargetSwitchPreferencePage()
}
@Composable
fun EntryItem() {
Preference(object : PreferenceModel {
override val title = TITLE
override val onClick = navigator(name)
})
}
}
@Composable
private fun TwoTargetSwitchPreferencePage() {
RegularScaffold(title = TITLE) {
SampleTwoTargetSwitchPreference()
SampleTwoTargetSwitchPreferenceWithSummary()
SampleTwoTargetSwitchPreferenceWithAsyncSummary()
SampleNotChangeableTwoTargetSwitchPreference()
}
}
@Composable
private fun SampleTwoTargetSwitchPreference() {
val checked = rememberSaveable { mutableStateOf(false) }
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
}) {}
}
@Composable
private fun SampleTwoTargetSwitchPreferenceWithSummary() {
val checked = rememberSaveable { mutableStateOf(true) }
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
override val summary = stateOf("With summary")
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
}) {}
}
@Composable
private fun SampleTwoTargetSwitchPreferenceWithAsyncSummary() {
val checked = rememberSaveable { mutableStateOf(true) }
val summary = produceState(initialValue = " ") {
delay(1000L)
value = "Async summary"
}
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
override val summary = summary
override val checked = checked
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
}) {}
}
@Composable
private fun SampleNotChangeableTwoTargetSwitchPreference() {
val checked = rememberSaveable { mutableStateOf(true) }
TwoTargetSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
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 TwoTargetSwitchPreferencePagePreview() {
SettingsTheme {
TwoTargetSwitchPreferencePage()
}
}

View File

@@ -32,6 +32,7 @@ object SettingsDimension {
bottom = itemPaddingVertical,
)
val itemPaddingAround = 8.dp
val itemDividerHeight = 32.dp
/** The size when app icon is displayed in list. */
val appIconItemSize = 32.dp

View File

@@ -19,4 +19,5 @@ package com.android.settingslib.spa.framework.theme
object SettingsOpacity {
const val Full = 1f
const val Disabled = 0.38f
const val Divider = 0.2f
}

View File

@@ -0,0 +1,72 @@
/*
* 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.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity
@Composable
internal fun TwoTargetPreference(
title: String,
summary: State<String>,
onClick: () -> Unit,
icon: @Composable (() -> Unit)? = null,
widget: @Composable () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(end = SettingsDimension.itemPaddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.weight(1f)) {
Preference(remember {
object : PreferenceModel {
override val title = title
override val summary = summary
override val icon = icon
override val onClick = onClick
}
})
}
PreferenceDivider()
widget()
}
}
@Composable
private fun PreferenceDivider() {
Box(
Modifier
.padding(horizontal = SettingsDimension.itemPaddingEnd)
.size(width = 1.dp, height = SettingsDimension.itemDividerHeight)
.background(color = MaterialTheme.colorScheme.onSurface.copy(SettingsOpacity.Disabled))
)
}

View File

@@ -0,0 +1,40 @@
/*
* 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 com.android.settingslib.spa.widget.ui.SettingsSwitch
@Composable
fun TwoTargetSwitchPreference(
model: SwitchPreferenceModel,
icon: @Composable (() -> Unit)? = null,
onClick: () -> Unit,
) {
TwoTargetPreference(
title = model.title,
summary = model.summary,
onClick = onClick,
icon = icon,
) {
SettingsSwitch(
checked = model.checked,
changeable = model.changeable,
onCheckedChange = model.onCheckedChange,
)
}
}

View File

@@ -26,6 +26,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -47,6 +48,8 @@ fun SettingsScaffold(
Text(
text = title,
modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
},
navigationIcon = { NavigateBack() },

View File

@@ -0,0 +1,116 @@
/*
* 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.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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 com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TwoTargetSwitchPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
val checked = mutableStateOf(false)
composeTestRule.setContent {
TestTwoTargetSwitchPreference(checked = checked, changeable = true)
}
composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
}
@Test
fun toggleable_initialStateIsCorrect() {
val checked = mutableStateOf(false)
composeTestRule.setContent {
TestTwoTargetSwitchPreference(checked = checked, changeable = true)
}
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun toggleable_changeable_withEffect() {
val checked = mutableStateOf(false)
composeTestRule.setContent {
TestTwoTargetSwitchPreference(checked = checked, changeable = true)
}
composeTestRule.onNode(isToggleable()).performClick()
composeTestRule.onNode(isToggleable()).assertIsOn()
}
@Test
fun toggleable_notChangeable_noEffect() {
val checked = mutableStateOf(false)
composeTestRule.setContent {
TestTwoTargetSwitchPreference(checked = checked, changeable = false)
}
composeTestRule.onNode(isToggleable()).performClick()
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun clickable_canBeClick() {
val checked = mutableStateOf(false)
var clicked = false
composeTestRule.setContent {
TestTwoTargetSwitchPreference(checked = checked, changeable = false) {
clicked = true
}
}
composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
assertThat(clicked).isTrue()
}
}
@Composable
private fun TestTwoTargetSwitchPreference(
checked: MutableState<Boolean>,
changeable: Boolean,
onClick: () -> Unit = {},
) {
TwoTargetSwitchPreference(
model = remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
override val checked = checked
override val changeable = stateOf(changeable)
override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
}
},
onClick = onClick,
)
}