Merge "Add ListPreference2." into main
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.kt
new file mode 100644
index 0000000..8300ce8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.RadioButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.CategoryTitle
+import com.android.settingslib.spa.widget.ui.SettingsListItem
+
+@Composable
+fun RadioPreferences(model: ListPreferenceModel) {
+ CategoryTitle(title = model.title)
+ Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
+ Column(modifier = Modifier.selectableGroup()) {
+ for (option in model.options) {
+ Radio2(option, model.selectedId.intValue, model.enabled()) {
+ model.onIdSelected(it)
+ }
+ }
+ }
+}
+
+@Composable
+fun Radio2(
+ option: ListPreferenceOption,
+ selectedId: Int,
+ enabled: Boolean,
+ onIdSelected: (id: Int) -> Unit,
+) {
+ val selected = option.id == selectedId
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .selectable(
+ selected = selected,
+ enabled = enabled,
+ onClick = { onIdSelected(option.id) },
+ role = Role.RadioButton,
+ )
+ .padding(SettingsDimension.dialogItemPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ RadioButton(selected = selected, onClick = null, enabled = enabled)
+ Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
+ SettingsListItem(text = option.text, enabled = enabled)
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index b4a6a0d..a59b95a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -66,6 +66,19 @@
}
@Composable
+fun SettingsListItem(text: String, enabled: Boolean = true) {
+ Text(
+ text = text,
+ modifier = Modifier
+ .alphaForEnabled(enabled)
+ .padding(vertical = SettingsDimension.paddingTiny),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleMedium,
+ overflow = TextOverflow.Ellipsis,
+ )
+}
+
+@Composable
fun SettingsBody(
body: String,
maxLines: Int = Int.MAX_VALUE,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt
new file mode 100644
index 0000000..2f98b02
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 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.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelectable
+import androidx.compose.ui.test.assertIsSelected
+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 org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RadioPreferencesTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = emptyList<ListPreferenceOption>()
+ override val selectedId = mutableIntStateOf(0)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun item_displayed() {
+ val selectedId = mutableIntStateOf(1)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ composeTestRule.onNodeWithText("A").assertIsDisplayed()
+ composeTestRule.onNodeWithText("B").assertIsDisplayed()
+ }
+
+ @Test
+ fun item_selectable() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = { enabledState.value }
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ composeTestRule.onNodeWithText("A").assertIsSelectable()
+ composeTestRule.onNodeWithText("B").assertIsSelectable()
+ }
+
+ @Test
+ fun item_single_selected() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = { enabledState.value }
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ composeTestRule.onNodeWithText("B").assertIsSelectable()
+ composeTestRule.onNodeWithText("B").performClick()
+ composeTestRule.onNodeWithText("B").assertIsSelected()
+ composeTestRule.onNodeWithText("A").assertIsNotSelected()
+ composeTestRule.onNodeWithText("A").performClick()
+ composeTestRule.onNodeWithText("A").assertIsSelected()
+ composeTestRule.onNodeWithText("B").assertIsNotSelected()
+ }
+
+ @Test
+ fun select_itemDisabled() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = { enabledState.value }
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ enabledState.value = false
+ composeTestRule.onNodeWithText("A").assertIsDisplayed().assertIsNotEnabled()
+ composeTestRule.onNodeWithText("B").assertIsDisplayed().assertIsNotEnabled()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ }
+}
\ No newline at end of file