diff options
| author | 2023-10-17 12:39:50 +0800 | |
|---|---|---|
| committer | 2023-11-08 02:05:36 +0000 | |
| commit | 09d46e05278f35e38171962b025dd474e48354aa (patch) | |
| tree | 01deacc487527ae3d71bb0f0cc4e9da670328907 | |
| parent | 7c10e57ce5410b7d1c334512ac4394bd9557ba6f (diff) | |
[Spa] New Card widget
With icon, title, text and buttons.
Bug: 305856149
Test: Run Gallery
Test: unit test
Change-Id: Idd6ab553646f0f9e9307b3a517b39098577244a2
6 files changed, 361 insertions, 0 deletions
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml index ec60f8c55331..18a6db035070 100644 --- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml +++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml @@ -26,4 +26,9 @@ <string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string> <!-- Footer text with two links. [DO NOT TRANSLATE] --> <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string> + + <!-- Sample title --> + <string name="sample_title" translatable="false">Lorem ipsum</string> + <!-- Sample text --> + <string name="sample_text" translatable="false">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent a rhoncus tellus. Nulla facilisi. Pellentesque erat ex, maximus viae turpis</string> </resources> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 01596d2bc004..dd9335846e25 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -22,6 +22,7 @@ import com.android.settingslib.spa.framework.common.SettingsPageProviderReposito import com.android.settingslib.spa.framework.common.SpaEnvironment import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider +import com.android.settingslib.spa.gallery.card.CardPageProvider import com.android.settingslib.spa.gallery.chart.ChartPageProvider import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider @@ -96,6 +97,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsExposedDropdownMenuCheckBoxProvider, SettingsTextFieldPasswordPageProvider, SearchScaffoldPageProvider, + CardPageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt new file mode 100644 index 000000000000..e914d5ce5140 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 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.card + +import android.os.Bundle +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.WarningAmber +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.gallery.R +import com.android.settingslib.spa.widget.card.CardButton +import com.android.settingslib.spa.widget.card.SettingsCard +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold + +object CardPageProvider : SettingsPageProvider { + override val name = "ActionButton" + + override fun getTitle(arguments: Bundle?) = TITLE + + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = TITLE) { + SettingsCardWithIcon() + SettingsCardWithoutIcon() + } + } + + @Composable + private fun SettingsCardWithIcon() { + SettingsCard( + title = stringResource(R.string.sample_title), + text = stringResource(R.string.sample_text), + imageVector = Icons.Outlined.WarningAmber, + buttons = listOf( + CardButton(text = "Action") {}, + CardButton(text = "Action", isMain = true) {}, + ) + ) + } + + @Composable + private fun SettingsCardWithoutIcon() { + SettingsCard( + title = stringResource(R.string.sample_title), + text = stringResource(R.string.sample_text), + buttons = listOf( + CardButton(text = "Action") {}, + ) + ) + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = createSettingsPage()) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + private const val TITLE = "Sample Card" +} + +@Preview +@Composable +private fun CardPagePreview() { + SettingsTheme { + CardPageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index b339b4482137..f52ceec41253 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -28,6 +28,7 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider +import com.android.settingslib.spa.gallery.card.CardPageProvider import com.android.settingslib.spa.gallery.chart.ChartPageProvider import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider @@ -69,6 +70,7 @@ object HomePageProvider : SettingsPageProvider { ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt new file mode 100644 index 000000000000..98873e037df8 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2023 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.card + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.material.icons.Icons +import androidx.compose.material.icons.outlined.WarningAmber +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +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.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.ui.SettingsBody +import com.android.settingslib.spa.widget.ui.SettingsTitle + +data class CardButton( + val text: String, + val isMain: Boolean = false, + val onClick: () -> Unit, +) + +@Composable +fun SettingsCard( + title: String, + text: String, + imageVector: ImageVector? = null, + buttons: List<CardButton> = emptyList(), +) { + Card( + shape = CornerExtraLarge, + colors = CardDefaults.cardColors( + containerColor = SettingsTheme.colorScheme.surface, + ), + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = SettingsDimension.itemPaddingEnd, + vertical = SettingsDimension.itemPaddingAround, + ), + ) { + Column( + modifier = Modifier.padding(SettingsDimension.itemPaddingStart), + verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround) + ) { + CardIcon(imageVector) + SettingsTitle(title) + SettingsBody(text) + Buttons(buttons) + } + } +} + +@Composable +private fun CardIcon(imageVector: ImageVector?) { + if (imageVector != null) { + Icon( + imageVector = imageVector, + contentDescription = null, + modifier = Modifier.size(SettingsDimension.itemIconSize), + tint = MaterialTheme.colorScheme.primary, + ) + } +} + +@Composable +private fun Buttons(buttons: List<CardButton>) { + if (buttons.isNotEmpty()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = SettingsDimension.itemPaddingAround), + horizontalArrangement = Arrangement.spacedBy( + space = SettingsDimension.itemPaddingEnd, + alignment = Alignment.End, + ), + ) { + for (button in buttons) { + Button(button) + } + } + } +} + +@Composable +private fun Button(button: CardButton) { + if (button.isMain) { + Button( + onClick = button.onClick, + colors = ButtonDefaults.buttonColors( + containerColor = SettingsTheme.colorScheme.primaryContainer, + ), + ) { + Text( + text = button.text, + color = SettingsTheme.colorScheme.onPrimaryContainer, + ) + } + } else { + OutlinedButton(onClick = button.onClick) { + Text( + text = button.text, + color = MaterialTheme.colorScheme.onSurface, + ) + } + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +@Composable +private fun SettingsCardPreviewLight() { + SettingsCardPreview() +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun SettingsCardPreviewDark() { + SettingsCardPreview() +} + +@Composable +private fun SettingsCardPreview() { + SettingsTheme { + SettingsCard( + title = "Lorem ipsum", + text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + imageVector = Icons.Outlined.WarningAmber, + buttons = listOf( + CardButton(text = "Action") {}, + CardButton(text = "Action", isMain = true) {}, + ) + ) + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt new file mode 100644 index 000000000000..0ec8507cb88b --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 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.card + +import androidx.compose.ui.test.assertIsDisplayed +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.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsCardTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun settingsCard_titleDisplayed() { + composeTestRule.setContent { + SettingsCard( + title = TITLE, + text = "", + ) + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun settingsCard_textDisplayed() { + composeTestRule.setContent { + SettingsCard( + title = "", + text = TEXT, + ) + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun settingsCard_buttonDisplayed() { + composeTestRule.setContent { + SettingsCard( + title = "", + text = "", + buttons = listOf( + CardButton(text = TEXT) {} + ), + ) + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun settingsCard_buttonCanBeClicked() { + var buttonClicked = false + composeTestRule.setContent { + SettingsCard( + title = "", + text = "", + buttons = listOf( + CardButton(text = TEXT) { buttonClicked = true } + ), + ) + } + + composeTestRule.onNodeWithText(TEXT).performClick() + + assertThat(buttonClicked).isTrue() + } + + private companion object { + const val TITLE = "Title" + const val TEXT = "Text" + } +} |