commit 0416100db68ca75f883c93615e294587c25d0077
parent 8ae0ce3e2f6171b34375001f209d584dd563b41a
Author: Segun Famisa <sfamisa@mozilla.com>
Date: Fri, 21 Nov 2025 14:33:27 +0000
Bug 1998194 - Add compose UI for credit card editor screen r=android-reviewers,android-l10n-reviewers,delphine,007
This patch introduces the Jetpack Compose UI for the "Add/Edit Credit Card" screen, which was previously a native Android view.
The following new files have been added:
- `CreditCardEditorScreen.kt`: The main composable for the screen, which handles both adding a new card and editing an existing one. It includes the top app bar, text input fields for card number and name, dropdowns for expiration date, and action buttons.
- `CreditCardEditorState.kt`: A new `State` data class that defines the model for the editor screen, holding properties like card details, expiry date options, and UI state flags (`inEditMode`, `showDeleteDialog`).
- `DeleteCreditCardDialog.kt`: A reusable composable for the confirmation dialog shown when deleting a credit card.
A new string resource (`credit_cards_navigate_back_button_content_description`) was also added for the back button's content description.
Differential Revision: https://phabricator.services.mozilla.com/D271087
Diffstat:
5 files changed, 625 insertions(+), 0 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorScreen.kt
@@ -0,0 +1,471 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.ui
+
+import androidx.compose.foundation.layout.Arrangement
+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.WindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.autofill.ContentType
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentType
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import mozilla.components.compose.base.Dropdown
+import mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview
+import mozilla.components.compose.base.button.DestructiveButton
+import mozilla.components.compose.base.button.FilledButton
+import mozilla.components.compose.base.button.OutlinedButton
+import mozilla.components.compose.base.menu.MenuItem
+import mozilla.components.compose.base.text.Text
+import mozilla.components.compose.base.textfield.TextField
+import mozilla.components.compose.base.textfield.TextFieldColors
+import mozilla.components.lib.state.ext.observeAsState
+import org.mozilla.fenix.R
+import org.mozilla.fenix.settings.creditcards.ui.CreditCardEditorAction.DeleteDialogAction
+import org.mozilla.fenix.settings.creditcards.ui.CreditCardEditorAction.FieldChanged
+import org.mozilla.fenix.theme.FirefoxTheme
+import org.mozilla.fenix.theme.Theme
+import mozilla.components.ui.icons.R as iconsR
+
+/**
+ * Weight for the expiration month dropdown.
+ */
+private const val EXPIRATION_MONTH_WEIGHT = 5f
+
+/**
+ * Weight for the expiration year dropdown.
+ */
+private const val EXPIRATION_YEAR_WEIGHT = 4f
+
+/**
+ * Composable for the credit card editor screen.
+ *
+ * @param store The [CreditCardEditorStore] that manages the state in the screen
+ */
+@Composable
+fun CreditCardEditorScreen(store: CreditCardEditorStore) {
+ val state by store.observeAsState(store.state) { it }
+
+ Scaffold(
+ modifier = Modifier.semantics {
+ testTagsAsResourceId = true
+ },
+ topBar = {
+ EditorTopBar(
+ inEditMode = state.inEditMode,
+ onBackClicked = {
+ store.dispatch(CreditCardEditorAction.NavigateBack)
+ },
+ onSaveActionClicked = {
+ store.dispatch(CreditCardEditorAction.Save)
+ },
+ onDeleteActionClicked = {
+ store.dispatch(CreditCardEditorAction.DeleteClicked)
+ },
+ )
+ },
+ containerColor = FirefoxTheme.colors.layer1,
+ ) {
+ if (state.showDeleteDialog) {
+ DeleteCreditCardDialog(
+ onCancel = {
+ store.dispatch(DeleteDialogAction.Cancel)
+ },
+ onConfirm = {
+ store.dispatch(DeleteDialogAction.Confirm)
+ },
+ )
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(it),
+ contentAlignment = Alignment.TopCenter,
+ ) {
+ EditorContent(
+ state = state,
+ onCardNumberChanged = { cardNumber ->
+ store.dispatch(FieldChanged.CardNumberChanged(cardNumber))
+ },
+ onNameOnCardChanged = { nameOnCard ->
+ store.dispatch(FieldChanged.NameOnCardChanged(nameOnCard))
+ },
+ onMonthSelected = { month ->
+ store.dispatch(FieldChanged.MonthSelected(month))
+ },
+ onYearSelected = { year ->
+ store.dispatch(FieldChanged.YearSelected(year))
+ },
+ onDeleteClicked = {
+ store.dispatch(CreditCardEditorAction.DeleteClicked)
+ },
+ onCancelClicked = {
+ store.dispatch(CreditCardEditorAction.Cancel)
+ },
+ onSaveClicked = {
+ store.dispatch(CreditCardEditorAction.Save)
+ },
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class) // TopAppBar
+@Composable
+private fun EditorTopBar(
+ modifier: Modifier = Modifier,
+ inEditMode: Boolean,
+ onDeleteActionClicked: () -> Unit,
+ onSaveActionClicked: () -> Unit,
+ onBackClicked: () -> Unit,
+) {
+ TopAppBar(
+ modifier = modifier,
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = FirefoxTheme.colors.layer1,
+ titleContentColor = FirefoxTheme.colors.textPrimary,
+ actionIconContentColor = FirefoxTheme.colors.iconPrimary,
+ ),
+ title = {
+ Text(
+ text = if (inEditMode) {
+ stringResource(R.string.credit_cards_edit_card)
+ } else {
+ stringResource(R.string.credit_cards_add_card)
+ },
+ style = FirefoxTheme.typography.headline6,
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = onBackClicked) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_back_24),
+ contentDescription = stringResource(R.string.credit_cards_navigate_back_button_content_description),
+ )
+ }
+ },
+ actions = {
+ if (inEditMode) {
+ IconButton(
+ modifier = Modifier.testTag(CreditCardEditorTestTags.TOPBAR_DELETE_BUTTON),
+ onClick = onDeleteActionClicked,
+ ) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_delete_24),
+ contentDescription = stringResource(R.string.credit_cards_menu_delete_card),
+ )
+ }
+ }
+ IconButton(
+ modifier = Modifier.testTag(CreditCardEditorTestTags.TOPBAR_SAVE_BUTTON),
+ onClick = onSaveActionClicked,
+ ) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_checkmark_24),
+ contentDescription = stringResource(R.string.credit_cards_menu_save),
+ )
+ }
+ },
+ windowInsets = WindowInsets(
+ top = 0.dp,
+ bottom = 0.dp,
+ ),
+ )
+}
+
+@Composable
+private fun EditorContent(
+ state: CreditCardEditorState,
+ onCardNumberChanged: (String) -> Unit = {},
+ onNameOnCardChanged: (String) -> Unit = {},
+ onMonthSelected: (Int) -> Unit = {},
+ onYearSelected: (Int) -> Unit = {},
+ onDeleteClicked: () -> Unit = {},
+ onCancelClicked: () -> Unit = {},
+ onSaveClicked: () -> Unit = {},
+) {
+ Column(
+ modifier = Modifier
+ .widthIn(max = FirefoxTheme.layout.size.containerMaxWidth)
+ .verticalScroll(rememberScrollState())
+ .padding(FirefoxTheme.layout.space.static200)
+ .imePadding(),
+ verticalArrangement = Arrangement.spacedBy(FirefoxTheme.layout.space.static200),
+ ) {
+ val focusRequester = remember { FocusRequester() }
+
+ LaunchedEffect(Unit) {
+ focusRequester.requestFocus()
+ }
+
+ TextInput(
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag(CreditCardEditorTestTags.CARD_NUMBER_FIELD)
+ .semantics { contentType = ContentType.CreditCardNumber }
+ .focusRequester(focusRequester),
+ errorText = stringResource(R.string.credit_cards_number_validation_error_message_2),
+ label = stringResource(R.string.credit_cards_card_number),
+ isError = state.showCardNumberError,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ imeAction = ImeAction.Next,
+ ),
+ value = state.cardNumber,
+ onValueChange = onCardNumberChanged,
+ )
+
+ TextInput(
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag(CreditCardEditorTestTags.NAME_ON_CARD_FIELD)
+ .semantics { contentType = ContentType.PersonFullName },
+ errorText = stringResource(R.string.credit_cards_name_on_card_validation_error_message_2),
+ label = stringResource(R.string.credit_cards_name_on_card),
+ isError = state.showNameOnCardError,
+ value = state.nameOnCard,
+ onValueChange = onNameOnCardChanged,
+ )
+
+ ExpirationDateRow(
+ months = state.expiryMonths,
+ years = state.expiryYears,
+ selectedMonthIndex = state.selectedExpiryMonthIndex,
+ selectedYearIndex = state.selectedExpiryYearIndex,
+ onMonthSelected = onMonthSelected,
+ onYearSelected = onYearSelected,
+ )
+
+ ButtonsRow(
+ inEditMode = state.inEditMode,
+ onDeleteClicked = onDeleteClicked,
+ onCancelClicked = onCancelClicked,
+ onSaveClicked = onSaveClicked,
+ )
+ }
+}
+
+@Composable
+private fun ExpirationDateRow(
+ months: List<String>,
+ selectedMonthIndex: Int,
+ years: List<String>,
+ selectedYearIndex: Int,
+ onMonthSelected: (Int) -> Unit,
+ onYearSelected: (Int) -> Unit,
+) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(FirefoxTheme.layout.space.static200),
+ ) {
+ ExpirationDateDropdown(
+ modifier = Modifier
+ .weight(EXPIRATION_MONTH_WEIGHT)
+ .testTag(CreditCardEditorTestTags.EXPIRATION_MONTH_FIELD)
+ .semantics { contentType = ContentType.CreditCardExpirationMonth },
+ label = stringResource(R.string.credit_cards_expiration_date_month),
+ items = months,
+ selectedIndex = selectedMonthIndex,
+ onItemSelected = onMonthSelected,
+ )
+
+ ExpirationDateDropdown(
+ modifier = Modifier
+ .weight(EXPIRATION_YEAR_WEIGHT)
+ .testTag(CreditCardEditorTestTags.EXPIRATION_YEAR_FIELD)
+ .semantics { contentType = ContentType.CreditCardExpirationYear },
+ label = stringResource(R.string.credit_cards_expiration_date_year),
+ items = years,
+ selectedIndex = selectedYearIndex,
+ onItemSelected = onYearSelected,
+ )
+ }
+}
+
+@Composable
+private fun ButtonsRow(
+ inEditMode: Boolean,
+ onDeleteClicked: () -> Unit,
+ onCancelClicked: () -> Unit,
+ onSaveClicked: () -> Unit,
+) {
+ Row {
+ if (inEditMode) {
+ DestructiveButton(
+ text = stringResource(R.string.credit_cards_delete_card_button),
+ modifier = Modifier.testTag(CreditCardEditorTestTags.DELETE_BUTTON),
+ onClick = onDeleteClicked,
+ )
+ }
+
+ Spacer(Modifier.weight(1f))
+
+ OutlinedButton(
+ modifier = Modifier.testTag(CreditCardEditorTestTags.CANCEL_BUTTON),
+ text = stringResource(R.string.credit_cards_cancel_button),
+ onClick = onCancelClicked,
+ )
+
+ Spacer(Modifier.width(FirefoxTheme.layout.space.static200))
+
+ FilledButton(
+ modifier = Modifier.testTag(CreditCardEditorTestTags.SAVE_BUTTON),
+ text = stringResource(R.string.credit_cards_save_button),
+ onClick = onSaveClicked,
+ )
+ }
+}
+
+@Composable
+private fun ExpirationDateDropdown(
+ label: String,
+ selectedIndex: Int,
+ items: List<String>,
+ modifier: Modifier = Modifier,
+ onItemSelected: (Int) -> Unit,
+) {
+ Dropdown(
+ modifier = modifier,
+ label = label,
+ dropdownItems = items.mapIndexed { index, itemLabel ->
+ MenuItem.CheckableItem(
+ text = Text.String(value = itemLabel),
+ isChecked = index == selectedIndex,
+ onClick = {
+ onItemSelected(index)
+ },
+ )
+ },
+ placeholder = "",
+ )
+}
+
+@Composable
+private fun TextInput(
+ modifier: Modifier = Modifier,
+ label: String,
+ value: String,
+ isError: Boolean,
+ errorText: String,
+ keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+ onValueChange: (String) -> Unit = {},
+) {
+ TextField(
+ value = value,
+ onValueChange = onValueChange,
+ modifier = modifier,
+ label = label,
+ placeholder = "",
+ isError = isError,
+ errorText = errorText,
+ keyboardOptions = keyboardOptions,
+ colors = TextFieldColors.default(),
+ )
+}
+
+@FlexibleWindowLightDarkPreview
+@Composable
+@Preview
+private fun CreditCardEditorScreenPreview() = FirefoxTheme {
+ val state by remember {
+ mutableStateOf(
+ CreditCardEditorState(
+ guid = "1234",
+ cardNumber = "5555444433331111",
+ nameOnCard = "Jane Doe",
+ expiryMonths = listOf("January (01)", "February (02)"),
+ selectedExpiryMonthIndex = 1,
+ expiryYears = listOf("2025", "2026", "2027"),
+ selectedExpiryYearIndex = 2,
+ inEditMode = false,
+ showDeleteDialog = false,
+ ),
+ )
+ }
+ CreditCardEditorScreen(
+ store = CreditCardEditorStore(initialState = state),
+ )
+}
+
+@Composable
+@Preview
+private fun CreditCardEditorScreenPrivateThemePreview() = FirefoxTheme(theme = Theme.Private) {
+ val state by remember {
+ mutableStateOf(
+ CreditCardEditorState(
+ guid = "1234",
+ cardNumber = "5555444433331111",
+ nameOnCard = "Jane Doe",
+ expiryMonths = listOf("January (01)", "February (02)"),
+ selectedExpiryMonthIndex = 1,
+ expiryYears = listOf("2025", "2026", "2027"),
+ selectedExpiryYearIndex = 2,
+ inEditMode = false,
+ showDeleteDialog = false,
+ ),
+ )
+ }
+ CreditCardEditorScreen(
+ store = CreditCardEditorStore(initialState = state),
+ )
+}
+
+@FlexibleWindowLightDarkPreview
+@Composable
+@Preview
+private fun CreditCardEditorScreenDeleteDialogPreview() = FirefoxTheme {
+ val state by remember {
+ mutableStateOf(
+ CreditCardEditorState(
+ guid = "1234",
+ cardNumber = "5555444433331111",
+ nameOnCard = "Jane Doe",
+ expiryMonths = listOf("January (01)", "February (02)"),
+ selectedExpiryMonthIndex = 1,
+ expiryYears = listOf("2025", "2026", "2027"),
+ selectedExpiryYearIndex = 2,
+ inEditMode = false,
+ showDeleteDialog = true,
+ ),
+ )
+ }
+ CreditCardEditorScreen(
+ store = CreditCardEditorStore(initialState = state),
+ )
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorState.kt
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.ui
+
+import mozilla.components.lib.state.State
+
+/**
+ * State defining the "Edit Credit Card" screen
+ *
+ * @property guid The unique identifier for the edited credit card.
+ * @property cardNumber The credit card number of the card being edited.
+ * @property showCardNumberError Indicates whether or not to show an error on the "card number" field.
+ * @property nameOnCard The credit card name.
+ * @property showNameOnCardError Indicates whether or not to show an error on the "name on card" field.
+ * @property expiryMonths The non-empty list of expiry month options for display.
+ * @property selectedExpiryMonthIndex The index of the selected expiry month.
+ * @property expiryYears The non-empty list of expiry year options for display.
+ * @property selectedExpiryYearIndex The index of the selected expiry year.
+ * @property inEditMode Indicates whether or not the state is in edit more, or create mode.
+ * @property showDeleteDialog Indicates whether or not to show the delete dialog.
+ */
+data class CreditCardEditorState(
+ val guid: String,
+ val cardNumber: String,
+ val showCardNumberError: Boolean = false,
+ val nameOnCard: String,
+ val showNameOnCardError: Boolean = false,
+ val expiryMonths: List<String>,
+ val selectedExpiryMonthIndex: Int,
+ val expiryYears: List<String>,
+ val selectedExpiryYearIndex: Int,
+ val inEditMode: Boolean,
+ val showDeleteDialog: Boolean = false,
+) : State
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorTestTags.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorTestTags.kt
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.ui
+
+/**
+ * Test tags for the credit card editor screen.
+ */
+object CreditCardEditorTestTags {
+
+ /** Test tag for the card number input field. */
+ const val CARD_NUMBER_FIELD = "credit_card.edit.field.card_number"
+
+ /** Test tag for the name on card input field. */
+ const val NAME_ON_CARD_FIELD = "credit_card.edit.field.name_on_card"
+
+ /** Test tag for the expiration month input field. */
+ const val EXPIRATION_MONTH_FIELD = "credit_card.edit.field.expiration_month"
+
+ /** Test tag for the expiration year input field. */
+ const val EXPIRATION_YEAR_FIELD = "credit_card.edit.field.expiration_year"
+
+ /** Test tag for the save button. */
+ const val SAVE_BUTTON = "credit_card.edit.button.save"
+
+ /** Test tag for the cancel button. */
+ const val CANCEL_BUTTON = "credit_card.edit.button.cancel"
+
+ /** Test tag for the delete button. */
+ const val DELETE_BUTTON = "credit_card.edit.button.delete"
+
+ /** Test tag for the cancel button in the delete confirmation dialog. */
+ const val DELETE_DIALOG_CANCEL_BUTTON = "credit_card.edit.dialog.button.cancel"
+
+ /** Test tag for the delete button in the delete confirmation dialog. */
+ const val DELETE_DIALOG_DELETE_BUTTON = "credit_card.edit.dialog.button.delete"
+
+ /** Test tag for the delete button in the top bar. */
+ const val TOPBAR_DELETE_BUTTON = "credit_card.edit.topbar.button.delete"
+
+ /** Test tag for the save button in the top bar. */
+ const val TOPBAR_SAVE_BUTTON = "credit_card.edit.topbar.button.save"
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/DeleteCreditCardDialog.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/creditcards/ui/DeleteCreditCardDialog.kt
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.ui
+
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import mozilla.components.compose.base.button.TextButton
+import org.mozilla.fenix.R
+import org.mozilla.fenix.theme.FirefoxTheme
+import org.mozilla.fenix.theme.Theme
+
+/**
+ * Composable for the delete credit card dialog.
+ *
+ * @param modifier The [Modifier] to be applied to the dialog.
+ * @param onCancel The callback to be invoked when the cancel button is clicked.
+ * @param onConfirm The callback to be invoked when the confirm button is clicked.
+ */
+@Composable
+internal fun DeleteCreditCardDialog(
+ modifier: Modifier = Modifier,
+ onCancel: () -> Unit = {},
+ onConfirm: () -> Unit = {},
+) {
+ AlertDialog(
+ modifier = modifier,
+ title = {
+ Text(
+ text = stringResource(R.string.credit_cards_delete_dialog_confirmation_2),
+ color = FirefoxTheme.colors.textPrimary,
+ style = FirefoxTheme.typography.headline5,
+ )
+ },
+ text = null,
+ onDismissRequest = onCancel,
+ confirmButton = {
+ TextButton(
+ modifier = Modifier.testTag(CreditCardEditorTestTags.DELETE_DIALOG_DELETE_BUTTON),
+ text = stringResource(R.string.credit_cards_delete_dialog_button),
+ onClick = onConfirm,
+ )
+ },
+ dismissButton = {
+ TextButton(
+ modifier = Modifier.testTag(CreditCardEditorTestTags.DELETE_DIALOG_CANCEL_BUTTON),
+ text = stringResource(R.string.credit_cards_cancel_button),
+ onClick = onCancel,
+ )
+ },
+ )
+}
+
+private class ThemePreviewParameterProvider(
+ override val values: Sequence<Theme> = Theme.entries.asSequence(),
+) : PreviewParameterProvider<Theme>
+
+@Composable
+@Preview
+private fun PreviewDeleteCreditCardDialog(
+ @PreviewParameter(ThemePreviewParameterProvider::class) theme: Theme,
+) = FirefoxTheme(theme) {
+ DeleteCreditCardDialog()
+}
diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml
@@ -2529,6 +2529,8 @@
<!-- Title of the "Add card" screen -->
<string name="credit_cards_add_card">Add card</string>
+ <!-- Content description for the "credit card feature" top bar back button -->
+ <string name="credit_cards_navigate_back_button_content_description">Navigate back</string>
<!-- Title of the "Edit card" screen -->
<string name="credit_cards_edit_card">Edit card</string>
<!-- The header for the card number of a credit card -->