diff options
| author | 2024-10-16 08:09:12 +0000 | |
|---|---|---|
| committer | 2024-10-17 06:06:05 +0000 | |
| commit | 61f1e4ba4a44d2645357157a625b4f8805ca512d (patch) | |
| tree | 6cf2241df590d330082d4f9829c2c2931df46449 | |
| parent | 81d5c9ceb6cc01419f14a17e897511f280e2a825 (diff) | |
[expressive design] Create SuggestionCard.
Test: visual
Bug: 360916599
Flag: EXEMPT bug fix
Change-Id: Ib8a905b99c909154371400ba9809b4c1c41edd6d
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" + } +} |