summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yuchen <kirain@google.com> 2024-10-16 08:09:12 +0000
committer Yuchen <kirain@google.com> 2024-10-17 06:06:05 +0000
commit61f1e4ba4a44d2645357157a625b4f8805ca512d (patch)
tree6cf2241df590d330082d4f9829c2c2931df46449
parent81d5c9ceb6cc01419f14a17e897511f280e2a825 (diff)
[expressive design] Create SuggestionCard.
Test: visual Bug: 360916599 Flag: EXEMPT bug fix Change-Id: Ib8a905b99c909154371400ba9809b4c1c41edd6d
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt104
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt166
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt)0
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt)0
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt84
9 files changed, 361 insertions, 0 deletions
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 dfd296fe006f..8636524ed23c 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
@@ -23,6 +23,7 @@ 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.banner.BannerPageProvider
+import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
@@ -109,6 +110,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
TopIntroPreferencePageProvider,
CheckBoxPreferencePageProvider,
TwoTargetButtonPreferencePageProvider,
+ 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..5659e2f33156
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.filled.Stars
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+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.theme.SettingsTheme
+import com.android.settingslib.spa.widget.card.SuggestionCard
+import com.android.settingslib.spa.widget.card.SuggestionCardModel
+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 = "Card"
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = TITLE) {
+ SuggestionCard()
+ SuggestionCardWithLongTitle()
+ SuggestionCardDismissible()
+ }
+ }
+
+ @Composable
+ private fun SuggestionCard() {
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Suggestion card",
+ description = "Suggestion card description",
+ imageVector = Icons.Filled.Stars,
+ )
+ )
+ }
+
+ @Composable
+ private fun SuggestionCardWithLongTitle() {
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Top level suggestion card with a really, really long title",
+ imageVector = Icons.Filled.Stars,
+ onClick = {},
+ )
+ )
+ }
+
+ @Composable
+ private fun SuggestionCardDismissible() {
+ var isVisible by rememberSaveable { mutableStateOf(true) }
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Suggestion card",
+ description = "Suggestion card description",
+ imageVector = Icons.Filled.Stars,
+ onDismiss = { isVisible = false },
+ isVisible = isVisible,
+ )
+ )
+ }
+
+ @Composable
+ fun Entry() {
+ 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 4d77ea173a85..ebfc0c536868 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
@@ -27,6 +27,7 @@ import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
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.DialogMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
@@ -80,6 +81,7 @@ object HomePageProvider : SettingsPageProvider {
DialogMainPageProvider.Entry()
EditorMainPageProvider.Entry()
BannerPageProvider.Entry()
+ CardPageProvider.Entry()
CopyablePageProvider.Entry()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 395748384b85..8ae0b613ec66 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,6 +37,7 @@ object SettingsDimension {
val actionIconPadding = 4.dp
val itemIconSize = 24.dp
+ val itemIconContainerSizeSmall = 40.dp
val itemIconContainerSize = 72.dp
val itemPaddingStart = if (isSpaExpressiveEnabled) paddingLarge else paddingExtraLarge
val itemPaddingEnd = paddingLarge
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 86ba6864574c..61607bc8ae8a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -29,4 +29,6 @@ object SettingsShape {
val CornerLarge = RoundedCornerShape(24.dp)
val CornerExtraLarge = RoundedCornerShape(28.dp)
+
+ val CornerExtraLarge1 = RoundedCornerShape(40.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt
new file mode 100644
index 000000000000..2126634ebd4d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.card
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+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.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Stars
+import androidx.compose.material.icons.outlined.Stars
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
+
+data class SuggestionCardModel(
+ val title: String,
+ val description: String? = null,
+ val imageVector: ImageVector,
+
+ /**
+ * A dismiss button will be displayed if this is not null.
+ *
+ * And this callback will be called when user clicks the button.
+ */
+ val onDismiss: (() -> Unit)? = null,
+ val isVisible: Boolean = true,
+ val onClick: (() -> Unit)? = null,
+)
+
+@Composable
+fun SuggestionCard(model: SuggestionCardModel) {
+ AnimatedVisibility(visible = model.isVisible) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ .fillMaxWidth()
+ .heightIn(min = SettingsDimension.preferenceMinHeight)
+ .clip(SettingsShape.CornerExtraLarge1)
+ .background(MaterialTheme.colorScheme.secondaryContainer)
+ .then(model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier)
+ .padding(SettingsDimension.paddingExtraSmall6),
+ ) {
+ SuggestionCardIcon(model.imageVector)
+ Spacer(Modifier.padding(SettingsDimension.paddingSmall))
+ Column(modifier = Modifier.weight(1f).semantics(mergeDescendants = true) {}) {
+ SuggestionCardTitle(model.title)
+ if (model.description != null) SuggestionCardDescription(model.description)
+ }
+ if (model.onDismiss != null) {
+ Spacer(Modifier.padding(SettingsDimension.paddingSmall))
+ SuggestionCardDismissButton(model.onDismiss)
+ }
+ }
+ }
+}
+
+@Composable
+private fun SuggestionCardIcon(imageVector: ImageVector) {
+ Box(
+ modifier =
+ Modifier.padding(SettingsDimension.paddingSmall)
+ .size(SettingsDimension.itemIconContainerSizeSmall)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.secondary),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSecondary,
+ )
+ }
+}
+
+@Composable
+private fun SuggestionCardTitle(title: String) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleMedium.toSemiBoldWeight(),
+ modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
+ color = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun SuggestionCardDescription(description: String) {
+ Text(
+ text = description,
+ style = MaterialTheme.typography.bodySmallEmphasized,
+ modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
+ color = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+}
+
+@Composable
+private fun SuggestionCardDismissButton(onDismiss: () -> Unit) {
+ IconButton(shape = CircleShape, onClick = onDismiss) {
+ Icon(
+ imageVector = Icons.Filled.Close,
+ contentDescription =
+ stringResource(androidx.compose.material3.R.string.m3c_snackbar_dismiss),
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SuggestionCardPreview() {
+ SettingsTheme {
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Suggestion card",
+ description = "Suggestion card description",
+ imageVector = Icons.Outlined.Stars,
+ onDismiss = {},
+ onClick = {},
+ )
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt
index a8479b01a861..a8479b01a861 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt
index 1080fdea9455..1080fdea9455 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt
new file mode 100644
index 000000000000..96bfb3d71642
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.Context
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Star
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.isNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SuggestionCardTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun suggestionCard_contentDisplayed() {
+ setContent()
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+ }
+
+ @Test
+ fun suggestionCard_dismiss() {
+ setContent()
+ composeTestRule
+ .onNodeWithContentDescription(
+ context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss)
+ )
+ .performClick()
+
+ composeTestRule.onNodeWithText(TITLE).isNotDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION).isNotDisplayed()
+ }
+
+ private fun setContent() {
+ composeTestRule.setContent {
+ var isVisible by rememberSaveable { mutableStateOf(true) }
+ SuggestionCard(
+ SuggestionCardModel(
+ title = TITLE,
+ description = DESCRIPTION,
+ imageVector = Icons.Outlined.Star,
+ isVisible = isVisible,
+ onDismiss = { isVisible = false },
+ )
+ )
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val DESCRIPTION = "Description"
+ }
+}