Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d8465f2b authored by Yuchen's avatar Yuchen Committed by Yuchen Sun
Browse files

[expressive design] Rename Card to Banner.

Test: Existing tests passed
Bug: 360916599
Flag: EXEMPT bug fix
Change-Id: Id4fc7dbc5de398a324618295bee43727e1a9b340
parent 570e23fe
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -22,7 +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.SpaEnvironment
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
@@ -107,7 +107,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
                SettingsTextFieldPasswordPageProvider,
                SettingsTextFieldPasswordPageProvider,
                SearchScaffoldPageProvider,
                SearchScaffoldPageProvider,
                SuwScaffoldPageProvider,
                SuwScaffoldPageProvider,
                CardPageProvider,
                BannerPageProvider,
                CopyablePageProvider,
                CopyablePageProvider,
            ),
            ),
            rootPages = listOf(
            rootPages = listOf(
+35 −35
Original line number Original line Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


package com.android.settingslib.spa.gallery.card
package com.android.settingslib.spa.gallery.banner


import android.os.Bundle
import android.os.Bundle
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clickable
@@ -46,39 +46,39 @@ import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.widget.card.CardButton
import com.android.settingslib.spa.widget.banner.BannerButton
import com.android.settingslib.spa.widget.card.CardModel
import com.android.settingslib.spa.widget.banner.BannerModel
import com.android.settingslib.spa.widget.card.SettingsCard
import com.android.settingslib.spa.widget.banner.SettingsBanner
import com.android.settingslib.spa.widget.card.SettingsCardContent
import com.android.settingslib.spa.widget.banner.SettingsBannerContent
import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard
import com.android.settingslib.spa.widget.banner.SettingsCollapsibleBanner
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.scaffold.RegularScaffold


object CardPageProvider : SettingsPageProvider {
object BannerPageProvider : SettingsPageProvider {
    override val name = "Card"
    override val name = "Banner"


    override fun getTitle(arguments: Bundle?) = TITLE
    override fun getTitle(arguments: Bundle?) = TITLE


    @Composable
    @Composable
    override fun Page(arguments: Bundle?) {
    override fun Page(arguments: Bundle?) {
        RegularScaffold(title = TITLE) {
        RegularScaffold(title = TITLE) {
            SettingsCardWithIcon()
            SettingsBannerWithIcon()
            SettingsCardWithoutIcon()
            SettingsBannerWithoutIcon()
            SampleSettingsCollapsibleCard()
            SampleSettingsCollapsibleBanner()
            SampleSettingsCardContent()
            SampleSettingsBannerContent()
        }
        }
    }
    }


    @Composable
    @Composable
    private fun SettingsCardWithIcon() {
    private fun SettingsBannerWithIcon() {
        SettingsCard(
        SettingsBanner(
            CardModel(
            BannerModel(
                title = stringResource(R.string.sample_title),
                title = stringResource(R.string.sample_title),
                text = stringResource(R.string.sample_text),
                text = stringResource(R.string.sample_text),
                imageVector = Icons.Outlined.WarningAmber,
                imageVector = Icons.Outlined.WarningAmber,
                buttons = listOf(
                buttons = listOf(
                    CardButton(text = "Action") {},
                    BannerButton(text = "Action") {},
                ),
                ),
                tintColor = MaterialTheme.colorScheme.error,
                tintColor = MaterialTheme.colorScheme.error,
                containerColor = MaterialTheme.colorScheme.errorContainer,
                containerColor = MaterialTheme.colorScheme.errorContainer,
@@ -87,11 +87,11 @@ object CardPageProvider : SettingsPageProvider {
    }
    }


    @Composable
    @Composable
    private fun SettingsCardWithoutIcon() {
    private fun SettingsBannerWithoutIcon() {
        val sampleTitle = stringResource(R.string.sample_title)
        val sampleTitle = stringResource(R.string.sample_title)
        var title by remember { mutableStateOf(sampleTitle) }
        var title by remember { mutableStateOf(sampleTitle) }
        SettingsCard(
        SettingsBanner(
            CardModel(
            BannerModel(
                title = title,
                title = title,
                text = stringResource(R.string.sample_text),
                text = stringResource(R.string.sample_text),
            ) { title = "Clicked" }
            ) { title = "Clicked" }
@@ -99,46 +99,46 @@ object CardPageProvider : SettingsPageProvider {
    }
    }


    @Composable
    @Composable
    fun SampleSettingsCollapsibleCard() {
    fun SampleSettingsCollapsibleBanner() {
        val context = LocalContext.current
        val context = LocalContext.current
        var isVisible0 by rememberSaveable { mutableStateOf(true) }
        var isVisible0 by rememberSaveable { mutableStateOf(true) }
        var isVisible1 by rememberSaveable { mutableStateOf(true) }
        var isVisible1 by rememberSaveable { mutableStateOf(true) }
        val cards = remember {
        val banners = remember {
            mutableStateListOf(
            mutableStateListOf(
                CardModel(
                BannerModel(
                    title = context.getString(R.string.sample_title),
                    title = context.getString(R.string.sample_title),
                    text = context.getString(R.string.sample_text),
                    text = context.getString(R.string.sample_text),
                    imageVector = Icons.Outlined.PowerOff,
                    imageVector = Icons.Outlined.PowerOff,
                    isVisible = { isVisible0 },
                    isVisible = { isVisible0 },
                    onDismiss = { isVisible0 = false },
                    onDismiss = { isVisible0 = false },
                    buttons = listOf(
                    buttons = listOf(
                        CardButton(text = "Override") {},
                        BannerButton(text = "Override") {},
                        CardButton(text = "Learn more") {},
                        BannerButton(text = "Learn more") {},
                    ),
                    ),
                ),
                ),
                CardModel(
                BannerModel(
                    title = context.getString(R.string.sample_title),
                    title = context.getString(R.string.sample_title),
                    text = context.getString(R.string.sample_text),
                    text = context.getString(R.string.sample_text),
                    imageVector = Icons.Outlined.Shield,
                    imageVector = Icons.Outlined.Shield,
                    isVisible = { isVisible1 },
                    isVisible = { isVisible1 },
                    onDismiss = { isVisible1 = false },
                    onDismiss = { isVisible1 = false },
                    buttons = listOf(
                    buttons = listOf(
                        CardButton(text = "Action") {},
                        BannerButton(text = "Action") {},
                    ),
                    ),
                )
                )
            )
            )
        }
        }
        SettingsCollapsibleCard(
        SettingsCollapsibleBanner(
            title = "More alerts",
            title = "More alerts",
            imageVector = Icons.Outlined.Error,
            imageVector = Icons.Outlined.Error,
            models = cards.toList()
            models = banners.toList()
        )
        )
    }
    }


    @Composable
    @Composable
    fun SampleSettingsCardContent() {
    fun SampleSettingsBannerContent() {
        SettingsCard {
        SettingsBanner {
            SettingsCardContent {
            SettingsBannerContent {
                Box(
                Box(
                    Modifier
                    Modifier
                        .fillMaxWidth()
                        .fillMaxWidth()
@@ -148,7 +148,7 @@ object CardPageProvider : SettingsPageProvider {
                    Text(text = "Abc")
                    Text(text = "Abc")
                }
                }
            }
            }
            SettingsCardContent {
            SettingsBannerContent {
                Box(
                Box(
                    Modifier
                    Modifier
                        .fillMaxWidth()
                        .fillMaxWidth()
@@ -171,13 +171,13 @@ object CardPageProvider : SettingsPageProvider {
            }
            }
    }
    }


    private const val TITLE = "Sample Card"
    private const val TITLE = "Sample Banner"
}
}


@Preview
@Preview
@Composable
@Composable
private fun CardPagePreview() {
private fun BannerPagePreview() {
    SettingsTheme {
    SettingsTheme {
        CardPageProvider.Page(null)
        BannerPageProvider.Page(null)
    }
    }
}
}
+2 −2
Original line number Original line Diff line number Diff line
@@ -28,7 +28,7 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
@@ -73,7 +73,7 @@ object HomePageProvider : SettingsPageProvider {
            ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            BannerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
        )
        )
    }
    }
+50 −0
Original line number Original line Diff line number Diff line
/*
 * 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.banner

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector

data class BannerButton(
    val text: String,
    val contentDescription: String? = null,
    val onClick: () -> Unit,
)

data class BannerModel(
    val title: String,
    val text: String,
    val imageVector: ImageVector? = null,
    val isVisible: () -> Boolean = { true },

    /**
     * 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 buttons: List<BannerButton> = emptyList(),

    /** If specified, this color will be used to tint the icon and the buttons. */
    val tintColor: Color = Color.Unspecified,

    /** If specified, this color will be used to tint the icon and the buttons. */
    val containerColor: Color = Color.Unspecified,

    val onClick: (() -> Unit)? = null,
)
+214 −0
Original line number Original line Diff line number Diff line
/*
 * 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.banner

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
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.outlined.Close
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
import com.android.settingslib.spa.framework.compose.contentDescription
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle

@Composable
fun SettingsBanner(content: @Composable ColumnScope.() -> Unit) {
    Card(
        shape = CornerExtraLarge,
        colors = CardDefaults.cardColors(
            containerColor = Color.Transparent,
        ),
        modifier = Modifier
            .fillMaxWidth()
            .padding(
                horizontal = SettingsDimension.itemPaddingEnd,
                vertical = SettingsDimension.itemPaddingAround,
            ),
        content = content,
    )
}

@Composable
fun SettingsBannerContent(
    containerColor: Color = Color.Unspecified,
    content: @Composable ColumnScope.() -> Unit,
) {
    Card(
        shape = CornerExtraSmall,
        colors = CardDefaults.cardColors(
            containerColor = containerColor.takeOrElse { MaterialTheme.colorScheme.surface },
        ),
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 1.dp),
        content = content,
    )
}

@Composable
fun SettingsBanner(model: BannerModel) {
    SettingsBanner {
        SettingsBannerImpl(model)
    }
}

@Composable
internal fun SettingsBannerImpl(model: BannerModel) {
    AnimatedVisibility(visible = model.isVisible()) {
        SettingsBannerContent(containerColor = model.containerColor) {
            Column(
                modifier = (model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier)
                    .padding(
                        horizontal = SettingsDimension.dialogItemPaddingHorizontal,
                        vertical = SettingsDimension.itemPaddingAround,
                    ),
                verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
            ) {
                BannerHeader(model.imageVector, model.tintColor, model.onDismiss)
                SettingsTitle(model.title)
                SettingsBody(model.text)
                Buttons(model.buttons, model.tintColor)
            }
        }
    }
}

@Composable
fun BannerHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> Unit)? = null) {
    if (imageVector != null || onDismiss != null) {
        Spacer(Modifier.height(SettingsDimension.buttonPaddingVertical))
    }
    Row(Modifier.fillMaxWidth()) {
        BannerIcon(imageVector, iconColor)
        Spacer(modifier = Modifier.weight(1f))
        DismissButton(onDismiss)
    }
}

@Composable
private fun BannerIcon(imageVector: ImageVector?, color: Color) {
    if (imageVector != null) {
        Icon(
            imageVector = imageVector,
            contentDescription = null,
            modifier = Modifier.size(SettingsDimension.itemIconSize),
            tint = color.takeOrElse { MaterialTheme.colorScheme.primary },
        )
    }
}

@Composable
private fun DismissButton(onDismiss: (() -> Unit)?) {
    if (onDismiss == null) return
    Surface(
        shape = CircleShape,
        color = MaterialTheme.colorScheme.secondaryContainer,
    ) {
        IconButton(
            onClick = onDismiss,
            modifier = Modifier.size(SettingsDimension.itemIconSize)
        ) {
            Icon(
                imageVector = Icons.Outlined.Close,
                contentDescription = stringResource(
                    androidx.compose.material3.R.string.m3c_snackbar_dismiss
                ),
                modifier = Modifier.padding(SettingsDimension.paddingSmall),
            )
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Buttons(buttons: List<BannerButton>, color: Color) {
    if (buttons.isNotEmpty()) {
        FlowRow(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(
                space = SettingsDimension.itemPaddingEnd,
                alignment = Alignment.End,
            ),
        ) {
            for (button in buttons) {
                Button(button, color)
            }
        }
    } else {
        Spacer(Modifier.height(SettingsDimension.itemPaddingAround))
    }
}

@Composable
private fun Button(button: BannerButton, color: Color) {
    TextButton(
        onClick = button.onClick,
        modifier = Modifier.contentDescription(button.contentDescription),
    ) {
        Text(text = button.text, color = color)
    }
}

@UiModePreviews
@Composable
private fun SettingsBannerPreview() {
    SettingsTheme {
        SettingsBanner(
            BannerModel(
                title = "Lorem ipsum",
                text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                imageVector = Icons.Outlined.WarningAmber,
                buttons = listOf(
                    BannerButton(text = "Action") {},
                )
            )
        )
    }
}
Loading