From a362cb1d5acdb4a1eef19033b2cc43503f9d826f Mon Sep 17 00:00:00 2001 From: Titouan Thibaud Date: Mon, 31 Jul 2023 12:19:03 +0200 Subject: [PATCH 1/2] Bug 1842203: Implement of the final UX of FedCM Provider and Account dialogs in Compose --- .../gecko/prompt/GeckoPromptDelegate.kt | 9 +- .../concept/engine/prompt/PromptRequest.kt | 2 + .../concept/identitycredential/Provider.kt | 1 + .../components/feature/prompts/build.gradle | 17 ++ .../feature/prompts/PromptFeature.kt | 1 + .../BasicAccountsAdapter.kt | 77 -------- .../BasicProvidersAdapter.kt | 67 ------- .../IdentityCredentialItem.kt | 111 ++++++++++++ .../identitycredential/SelectAccountDialog.kt | 165 ++++++++++++++++++ .../SelectAccountDialogFragment.kt | 52 ++++-- .../SelectProviderDialog.kt | 131 ++++++++++++++ .../SelectProviderDialogFragment.kt | 42 ++--- ...prompts_choose_identity_account_dialog.xml | 12 -- ...ompts_choose_identity_provider_dialogs.xml | 12 -- ...mpts_identity_crendential_account_item.xml | 40 ----- ...pts_identity_crendential_provider_item.xml | 20 --- .../prompts/src/main/res/values/strings.xml | 6 +- 17 files changed, 493 insertions(+), 272 deletions(-) delete mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicAccountsAdapter.kt delete mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicProvidersAdapter.kt create mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt create mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt create mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt delete mode 100644 android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_account_dialog.xml delete mode 100644 android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_provider_dialogs.xml delete mode 100644 android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_account_item.xml delete mode 100644 android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_provider_item.xml diff --git a/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index a60b0c05f347..d2d839ea55ca 100644 --- a/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -63,6 +63,7 @@ typealias GECKO_AUTH_LEVEL = PromptDelegate.AuthPrompt.AuthOptions.Level typealias GECKO_PROMPT_FILE_TYPE = PromptDelegate.FilePrompt.Type typealias GECKO_PROMPT_PROVIDER_SELECTOR = ProviderSelectorPrompt.Provider typealias GECKO_PROMPT_ACCOUNT_SELECTOR = AccountSelectorPrompt.Account +typealias GECKO_PROMPT_ACCOUNT_SELECTOR_PROVIDER = AccountSelectorPrompt.Provider typealias GECKO_PROMPT_CHOICE_TYPE = PromptDelegate.ChoicePrompt.Type typealias GECKO_PROMPT_FILE_CAPTURE = PromptDelegate.FilePrompt.Capture typealias GECKO_PROMPT_SHARE_RESULT = PromptDelegate.SharePrompt.Result @@ -132,6 +133,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe onPromptRequest( PromptRequest.IdentityCredential.SelectAccount( accounts = prompt.accounts.map { it.toAccount() }, + provider = prompt.provider.let { it.toProvider() }, onConfirm = onConfirm, onDismiss = onDismiss, ), @@ -912,10 +914,15 @@ internal fun PromptDelegate.BasePrompt.dismissSafely(geckoResult: GeckoResult, + val provider: Provider, val onConfirm: (Account) -> Unit, override val onDismiss: () -> Unit, ) : IdentityCredential(onDismiss), Dismissible diff --git a/android-components/components/concept/engine/src/main/java/mozilla/components/concept/identitycredential/Provider.kt b/android-components/components/concept/engine/src/main/java/mozilla/components/concept/identitycredential/Provider.kt index 72f89f7cfb38..7ebc8521b81d 100644 --- a/android-components/components/concept/engine/src/main/java/mozilla/components/concept/identitycredential/Provider.kt +++ b/android-components/components/concept/engine/src/main/java/mozilla/components/concept/identitycredential/Provider.kt @@ -20,4 +20,5 @@ data class Provider( val id: Int, val icon: String?, val name: String, + val domain: String, ) : Parcelable diff --git a/android-components/components/feature/prompts/build.gradle b/android-components/components/feature/prompts/build.gradle index e8cc442a27a6..0212157d958a 100644 --- a/android-components/components/feature/prompts/build.gradle +++ b/android-components/components/feature/prompts/build.gradle @@ -22,6 +22,14 @@ android { } } + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose_compiler + } + + buildFeatures { + compose true + } + namespace 'mozilla.components.feature.prompts' } @@ -39,11 +47,20 @@ dependencies { implementation project(':support-utils') implementation project(':ui-icons') implementation project(':ui-widgets') + implementation project(':ui-colors') implementation ComponentsDependencies.androidx_constraintlayout implementation ComponentsDependencies.androidx_core_ktx implementation ComponentsDependencies.google_material + implementation ComponentsDependencies.androidx_core_ktx + implementation ComponentsDependencies.androidx_compose_ui + implementation ComponentsDependencies.androidx_compose_ui_tooling_preview + implementation ComponentsDependencies.androidx_compose_foundation + implementation ComponentsDependencies.androidx_compose_material + + debugImplementation ComponentsDependencies.androidx_compose_ui_tooling + testImplementation ComponentsDependencies.androidx_test_core testImplementation ComponentsDependencies.androidx_test_junit testImplementation ComponentsDependencies.testing_coroutines diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt index 102a86153e51..73e0050f3b8c 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt @@ -921,6 +921,7 @@ class PromptFeature private constructor( promptRequestUID = promptRequest.uid, shouldDismissOnLoad = true, accounts = promptRequest.accounts, + provider = promptRequest.provider, ) } diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicAccountsAdapter.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicAccountsAdapter.kt deleted file mode 100644 index b9f175a52cb3..000000000000 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicAccountsAdapter.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* 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 mozilla.components.feature.prompts.identitycredential - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import mozilla.components.concept.identitycredential.Account -import mozilla.components.feature.prompts.R -import mozilla.components.support.ktx.kotlin.base64PngToBitmap - -private object AccountItemDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Account, newItem: Account) = - oldItem.id == newItem.id - - override fun areContentsTheSame(oldItem: Account, newItem: Account) = - oldItem == newItem -} - -/** - * RecyclerView adapter for displaying account items. - */ -internal class BasicAccountAdapter( - private val onAccountSelected: (Account) -> Unit, -) : ListAdapter(AccountItemDiffCallback) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { - val view = LayoutInflater - .from(parent.context) - .inflate( - R.layout.mozac_feature_prompts_identity_crendential_account_item, - parent, - false, - ) - return AccountViewHolder(view, onAccountSelected) - } - - override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { - holder.bind(getItem(position)) - } -} - -/** - * View holder for a account item. - */ -internal class AccountViewHolder( - itemView: View, - private val onAccountSelected: (Account) -> Unit, -) : RecyclerView.ViewHolder(itemView), View.OnClickListener { - internal lateinit var account: Account - - init { - itemView.setOnClickListener(this) - } - - fun bind(item: Account) { - account = item - - itemView.findViewById(R.id.fedcm_account_name).text = item.name - - account.icon?.let { - itemView.findViewById(R.id.fedcm_account_icon) - .setImageBitmap(it.base64PngToBitmap()) - } - } - - override fun onClick(v: View?) { - onAccountSelected(account) - } -} diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicProvidersAdapter.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicProvidersAdapter.kt deleted file mode 100644 index 6ecbc17f538f..000000000000 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/BasicProvidersAdapter.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* 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 mozilla.components.feature.prompts.identitycredential - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import mozilla.components.concept.identitycredential.Provider -import mozilla.components.feature.prompts.R - -private object ProviderItemDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Provider, newItem: Provider) = - oldItem.id == newItem.id - - override fun areContentsTheSame(oldItem: Provider, newItem: Provider) = - oldItem == newItem -} - -/** - * RecyclerView adapter for displaying [Provider] items. - */ -internal class BasicProviderAdapter( - private val onlProviderSelected: (Provider) -> Unit, -) : ListAdapter(ProviderItemDiffCallback) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProviderViewHolder { - val view = LayoutInflater - .from(parent.context) - .inflate(R.layout.mozac_feature_prompts_identity_crendential_provider_item, parent, false) - return ProviderViewHolder(view, onlProviderSelected) - } - - override fun onBindViewHolder(holder: ProviderViewHolder, position: Int) { - holder.bind(getItem(position)) - } -} - -/** - * View holder for a [Provider] item. - */ -internal class ProviderViewHolder( - itemView: View, - private val onProviderSelected: (Provider) -> Unit, -) : RecyclerView.ViewHolder(itemView), View.OnClickListener { - internal lateinit var provider: Provider - - init { - itemView.setOnClickListener(this) - } - - fun bind(item: Provider) { - provider = item - - (itemView as TextView) - .text = item.name - } - - override fun onClick(v: View?) { - onProviderSelected(provider) - } -} diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt new file mode 100644 index 000000000000..3a0f4f549327 --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt @@ -0,0 +1,111 @@ +/* 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 mozilla.components.feature.prompts.identitycredential + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Text +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mozilla.components.ui.colors.PhotonColors + +/** + * List item used to display an IdentityCredentials item that supports clicks + * + * @param title the Title of the item + * @param description The Description of the item. + * @param modifier The modifier to apply to this layout. + * @param onClick Invoked when the item is clicked. + * @param beforeItemContent An optional layout to display before the item. + * + */ +@Composable +internal fun IdentityCredentialItem( + title: String, + description: String, + modifier: Modifier = Modifier, + onClick: () -> Unit, + beforeItemContent: (@Composable () -> Unit)? = null, +) { + // Used to propagate the ripple effect to the whole row + val interactionSource = remember { MutableInteractionSource() } + + Row( + modifier = modifier + .fillMaxWidth() + .clickable( + onClick = onClick, + interactionSource = interactionSource, + indication = rememberRipple(color = PhotonColors.Black), + ) + .padding(horizontal = 16.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + beforeItemContent?.invoke() + + Column { + Text( + text = title, + style = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + color = PhotonColors.DarkGrey90, + letterSpacing = 0.15.sp, + ), + maxLines = 1, + ) + + Text( + text = description, + style = TextStyle( + fontSize = 14.sp, + lineHeight = 20.sp, + color = PhotonColors.DarkGrey05, + letterSpacing = 0.25.sp, + ), + maxLines = 1, + ) + } + } +} + +@Composable +@Preview(name = "Provider with no favicon") +private fun ProviderItemPreview() { + IdentityCredentialItem( + modifier = Modifier.background(Color.White), + title = "Title", + description = "Description", + onClick = {}, + ) +} + +@Composable +@Preview(name = "Provider with a start-spacer") +private fun ProviderItemPreviewWithSpacer() { + IdentityCredentialItem( + modifier = Modifier.background(Color.White), + title = "Title", + description = "Description", + onClick = {}, + ) { + Spacer(modifier = Modifier.size(24.dp)) + } +} diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt new file mode 100644 index 000000000000..c92fc1bf0891 --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt @@ -0,0 +1,165 @@ +/* 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 mozilla.components.feature.prompts.identitycredential + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.Text +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.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mozilla.components.concept.identitycredential.Account +import mozilla.components.concept.identitycredential.Provider +import mozilla.components.feature.prompts.R +import mozilla.components.support.ktx.kotlin.base64PngToBitmap +import mozilla.components.ui.colors.PhotonColors + +/** + * A Federated Credential Management dialog for selecting an account. + * + * @param provider The [Provider] on which the user is logging in. + * @param accounts The list of available accounts for this provider. + * @param modifier [Modifier] to be applied to the layout. + * @param onAccountClick Invoked when the user clicks on an item. + */ +@Composable +fun SelectAccountDialog( + provider: Provider, + accounts: List, + modifier: Modifier = Modifier, + onAccountClick: (Account) -> Unit, +) { + Column( + modifier = modifier.fillMaxWidth(), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(16.dp), + ) { + provider.icon?.base64PngToBitmap()?.asImageBitmap()?.let { + Image( + bitmap = it, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier + .size(16.dp), + ) + + Spacer(Modifier.width(4.dp)) + } + + Text( + text = stringResource( + id = R.string.mozac_feature_prompts_identity_credentials_choose_account_for_provider, + provider.name, + ), + style = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + color = PhotonColors.DarkGrey90, + letterSpacing = 0.15.sp, + ), + ) + } + + accounts.forEach { account -> + AccountItem(account = account, onClick = onAccountClick) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Composable +private fun AccountItem( + account: Account, + modifier: Modifier = Modifier, + onClick: (Account) -> Unit, +) { + IdentityCredentialItem( + title = account.name, + description = account.email, + modifier = modifier, + onClick = { onClick(account) }, + ) { + account.icon?.base64PngToBitmap()?.asImageBitmap()?.let { bitmap -> + Image( + bitmap = bitmap, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier + .padding(horizontal = 16.dp) + .size(32.dp), + ) + } ?: Spacer( + Modifier + .padding(horizontal = 16.dp) + .width(32.dp), + ) + } +} + +@Composable +@Preview(name = "Provider with no favicon") +private fun AccountItemPreview() { + AccountItem( + modifier = Modifier.background(Color.White), + account = Account( + 0, + "user@mozilla.com", + "User", + USER_PICTURE, + ), + onClick = {}, + ) +} + +@Composable +@Preview +private fun SelectAccountDialogPreview() { + SelectAccountDialog( + provider = Provider(0, GOOGLE_FAVICON, "Google", "google.com"), + accounts = listOf( + Account( + 0, + "user@mozilla.com", + "User", + USER_PICTURE, + ), + Account( + 1, + "user2@mozilla.com", + "Google", + null, + ), + ), + modifier = Modifier.background(Color.White), + onAccountClick = { }, + ) +} + +@Suppress("MaxLineLength") +private const val GOOGLE_FAVICON = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzEAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAH+SURBVHgBhVO/b9NQEL5z7Bi1lWJUCYRYHBaWDh6SKhspAYnRoVO3SqxIbiUGEEIRQ1kbxB9AM7VISJiNoSUeI6DCYmSpt1ZUak2U0GLXuZ7dOnUsp73l+e593/343hkhZYePKqp/EhhAoLOrRkEEmwgd/mrd3PpmJvGYdPbvl1cJYQkuMQI085KwfP1Lxwl9Mb74Uyu/J4BFuMIEoKp/HCixHyXYf1BqEF2QuS13gPQ2P1OyQt//ta0CYgOBFApg7ob13R5ij9rX1P67uzuDv/k45ASBMHfLOmsxabvVipqOo78pNZls/Nu8Df7vAgRBrphFHmfhCPeEggdT8zvg2dNrk8/2Rsi1N12dx1OyyIjghgnUOCBrB5/TIAJhNYkZuSNwBT4vsiNlVrrEFJHf3UE6q7DtTfO5N4Jg5W3u1Um0pPBza+eeE4nIH8aHozvQ7M+4J3JQtOumO65kbaU33BfWwOK9IPN5dzYkRy1JntAYR3640tOSy8YatKJVLm3Mt/moJrAWEL7+sfDRCp3qJ13peYIxsft0SezPxjo5X19OFaMEGgOk/7mflK12OM5QXPlAB/mwDjjA+tarSXP4M1XWdXVCLrS7Xi8ryUhCsV9a7jx5sRbpkL4trz9eJBQMnlBLExncypHU7CxsOHEQx5WJ5j4WoyQiiE6SlLRTu5e/Z+DrlXsAAAAASUVORK5CYII=" + +@Suppress("MaxLineLength") +private const val USER_PICTURE = + "data:image/png;base64,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" diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt index 163fd30d8827..696dc8304aec 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt @@ -8,37 +8,45 @@ import android.annotation.SuppressLint import android.app.Dialog import android.content.DialogInterface import android.os.Bundle -import android.view.LayoutInflater import android.view.View import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView import mozilla.components.concept.identitycredential.Account import mozilla.components.concept.identitycredential.Provider -import mozilla.components.feature.prompts.R +import mozilla.components.feature.prompts.dialog.AlertDialogFragment import mozilla.components.feature.prompts.dialog.KEY_PROMPT_UID import mozilla.components.feature.prompts.dialog.KEY_SESSION_ID import mozilla.components.feature.prompts.dialog.KEY_SHOULD_DISMISS_ON_LOAD import mozilla.components.feature.prompts.dialog.PromptDialogFragment import mozilla.components.support.utils.ext.getParcelableArrayListCompat +import mozilla.components.support.utils.ext.getParcelableCompat private const val KEY_ACCOUNTS = "KEY_ACCOUNTS" +private const val KEY_PROVIDER = "KEY_PROVIDER" /** * A Federated Credential Management dialog for selecting an account. */ internal class SelectAccountDialogFragment : PromptDialogFragment() { - private lateinit var listAdapter: BasicAccountAdapter internal val accounts: List by lazy { safeArguments.getParcelableArrayListCompat(KEY_ACCOUNTS, Account::class.java) ?: emptyList() } + internal val provider: Provider by lazy { + requireNotNull( + safeArguments.getParcelableCompat( + KEY_PROVIDER, + Provider::class.java, + ), + ) + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(requireContext()) .setCancelable(true) - .setTitle(R.string.mozac_feature_prompts_identity_credentials_choose_account) .setView(createDialogContentView()) .create() @@ -49,20 +57,15 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { @SuppressLint("InflateParams") internal fun createDialogContentView(): View { - val view = LayoutInflater.from(requireContext()) - .inflate(R.layout.mozac_feature_prompts_choose_identity_account_dialog, null) - - setupRecyclerView(view) - return view - } - - private fun setupRecyclerView(view: View) { - listAdapter = BasicAccountAdapter(this::onAccountChange) - view.findViewById(R.id.recyclerView).apply { - layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - adapter = listAdapter + return ComposeView(requireContext()).apply { + setContent { + SelectAccountDialog( + provider = provider, + accounts = accounts, + onAccountClick = ::onAccountChange, + ) + } } - listAdapter.submitList(accounts) } /** @@ -75,10 +78,20 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { } companion object { + + /** + * A builder method for creating a [SelectAccountDialogFragment] + * @param sessionId The id of the session for which this dialog will be created. + * @param promptRequestUID Identifier of the [PromptRequest] for which this dialog is shown. + * @param accounts The list of available accounts. + * @param provider The provider on which the user is logging in. + * @param shouldDismissOnLoad Whether or not the dialog should automatically be dismissed when a new page is loaded. + */ fun newInstance( sessionId: String, promptRequestUID: String, accounts: List, + provider: Provider, shouldDismissOnLoad: Boolean, ) = SelectAccountDialogFragment().apply { arguments = (arguments ?: Bundle()).apply { @@ -86,6 +99,7 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { putString(KEY_PROMPT_UID, promptRequestUID) putBoolean(KEY_SHOULD_DISMISS_ON_LOAD, shouldDismissOnLoad) putParcelableArrayList(KEY_ACCOUNTS, ArrayList(accounts)) + putParcelable(KEY_PROVIDER, provider) } } } diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt new file mode 100644 index 000000000000..69ce3bf4d53e --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt @@ -0,0 +1,131 @@ +/* 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 mozilla.components.feature.prompts.identitycredential + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +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.layout.width +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mozilla.components.concept.identitycredential.Provider +import mozilla.components.feature.prompts.R +import mozilla.components.support.ktx.kotlin.base64PngToBitmap +import mozilla.components.ui.colors.PhotonColors + +/** + * A Federated Credential Management dialog for selecting a provider. + */ +@Composable +fun SelectProviderDialog( + providers: List, + modifier: Modifier = Modifier, + onProviderClick: (Provider) -> Unit, +) { + Column( + modifier = modifier.fillMaxWidth(), + ) { + Text( + text = stringResource(id = R.string.mozac_feature_prompts_identity_credentials_choose_provider), + style = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + color = PhotonColors.DarkGrey90, + letterSpacing = 0.15.sp, + ), + modifier = Modifier.padding(16.dp), + ) + + providers.forEach { provider -> + ProviderItem(provider = provider, onClick = onProviderClick) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Composable +private fun ProviderItem( + provider: Provider, + modifier: Modifier = Modifier, + onClick: (Provider) -> Unit, +) { + IdentityCredentialItem( + title = provider.name, + description = provider.domain, + modifier = modifier, + onClick = { onClick(provider) }, + ) { + provider.icon?.base64PngToBitmap()?.asImageBitmap()?.let { bitmap -> + Image( + bitmap = bitmap, + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier + .padding(horizontal = 16.dp) + .size(24.dp), + ) + } ?: Spacer( + Modifier + .padding(horizontal = 16.dp) + .width(24.dp), + ) + } +} + +@Composable +@Preview(name = "Provider with no favicon") +private fun ProviderItemPreview() { + ProviderItem( + modifier = Modifier.background(Color.White), + provider = Provider( + 0, + null, + "Title", + "Description", + ), + onClick = {}, + ) +} + +@Composable +@Preview +private fun SelectProviderDialogPreview() { + SelectProviderDialog( + providers = listOf( + Provider( + 0, + null, + "Title", + "Description", + ), + Provider( + 0, + GOOGLE_FAVICON, + "Google", + "google.com", + ), + ), + modifier = Modifier.background(Color.White), + ) { } +} + +@Suppress("MaxLineLength") +private const val GOOGLE_FAVICON = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzEAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAH+SURBVHgBhVO/b9NQEL5z7Bi1lWJUCYRYHBaWDh6SKhspAYnRoVO3SqxIbiUGEEIRQ1kbxB9AM7VISJiNoSUeI6DCYmSpt1ZUak2U0GLXuZ7dOnUsp73l+e593/343hkhZYePKqp/EhhAoLOrRkEEmwgd/mrd3PpmJvGYdPbvl1cJYQkuMQI085KwfP1Lxwl9Mb74Uyu/J4BFuMIEoKp/HCixHyXYf1BqEF2QuS13gPQ2P1OyQt//ta0CYgOBFApg7ob13R5ij9rX1P67uzuDv/k45ASBMHfLOmsxabvVipqOo78pNZls/Nu8Df7vAgRBrphFHmfhCPeEggdT8zvg2dNrk8/2Rsi1N12dx1OyyIjghgnUOCBrB5/TIAJhNYkZuSNwBT4vsiNlVrrEFJHf3UE6q7DtTfO5N4Jg5W3u1Um0pPBza+eeE4nIH8aHozvQ7M+4J3JQtOumO65kbaU33BfWwOK9IPN5dzYkRy1JntAYR3640tOSy8YatKJVLm3Mt/moJrAWEL7+sfDRCp3qJ13peYIxsft0SezPxjo5X19OFaMEGgOk/7mflK12OM5QXPlAB/mwDjjA+tarSXP4M1XWdXVCLrS7Xi8ryUhCsV9a7jx5sRbpkL4trz9eJBQMnlBLExncypHU7CxsOHEQx5WJ5j4WoyQiiE6SlLRTu5e/Z+DrlXsAAAAASUVORK5CYII=" diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt index 6cec1297c24f..317d743c37f0 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt @@ -8,14 +8,11 @@ import android.annotation.SuppressLint import android.app.Dialog import android.content.DialogInterface import android.os.Bundle -import android.view.LayoutInflater import android.view.View import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.compose.ui.platform.ComposeView import mozilla.components.concept.identitycredential.Provider -import mozilla.components.feature.prompts.R import mozilla.components.feature.prompts.dialog.KEY_PROMPT_UID import mozilla.components.feature.prompts.dialog.KEY_SESSION_ID import mozilla.components.feature.prompts.dialog.KEY_SHOULD_DISMISS_ON_LOAD @@ -29,16 +26,14 @@ private const val KEY_PROVIDERS = "KEY_PROVIDERS" */ internal class SelectProviderDialogFragment : PromptDialogFragment() { - private lateinit var listAdapter: BasicProviderAdapter - - internal val providers: List by lazy { - safeArguments.getParcelableArrayListCompat(KEY_PROVIDERS, Provider::class.java) ?: emptyList() + private val providers: List by lazy { + safeArguments.getParcelableArrayListCompat(KEY_PROVIDERS, Provider::class.java) + ?: emptyList() } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(requireContext()) .setCancelable(true) - .setTitle(R.string.mozac_feature_prompts_identity_credentials_choose_provider) .setView(createDialogContentView()) .create() @@ -49,21 +44,14 @@ internal class SelectProviderDialogFragment : PromptDialogFragment() { @SuppressLint("InflateParams") internal fun createDialogContentView(): View { - val view = LayoutInflater - .from(requireContext()) - .inflate(R.layout.mozac_feature_prompts_choose_identity_provider_dialogs, null) - - setupRecyclerView(view) - return view - } - - private fun setupRecyclerView(view: View) { - listAdapter = BasicProviderAdapter(this::onProviderChange) - view.findViewById(R.id.recyclerView).apply { - layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - adapter = listAdapter + return ComposeView(requireContext()).apply { + setContent { + SelectProviderDialog( + providers = providers, + onProviderClick = ::onProviderChange, + ) + } } - listAdapter.submitList(providers) } /** @@ -76,6 +64,14 @@ internal class SelectProviderDialogFragment : PromptDialogFragment() { } companion object { + + /** + * A builder method for creating a [SelectAccountDialogFragment] + * @param sessionId The id of the session for which this dialog will be created. + * @param promptRequestUID Identifier of the [PromptRequest] for which this dialog is shown. + * @param providers The list of available providers. + * @param shouldDismissOnLoad Whether or not the dialog should automatically be dismissed when a new page is loaded. + */ fun newInstance( sessionId: String, promptRequestUID: String, diff --git a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_account_dialog.xml b/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_account_dialog.xml deleted file mode 100644 index 7c7e329fb887..000000000000 --- a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_account_dialog.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_provider_dialogs.xml b/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_provider_dialogs.xml deleted file mode 100644 index 7c7e329fb887..000000000000 --- a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_choose_identity_provider_dialogs.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_account_item.xml b/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_account_item.xml deleted file mode 100644 index 2ec4eae9d751..000000000000 --- a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_account_item.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_provider_item.xml b/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_provider_item.xml deleted file mode 100644 index e9e48f7ad6f6..000000000000 --- a/android-components/components/feature/prompts/src/main/res/layout/mozac_feature_prompts_identity_crendential_provider_item.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/android-components/components/feature/prompts/src/main/res/values/strings.xml b/android-components/components/feature/prompts/src/main/res/values/strings.xml index 0c03fc0694ad..8a5f28996ba4 100644 --- a/android-components/components/feature/prompts/src/main/res/values/strings.xml +++ b/android-components/components/feature/prompts/src/main/res/values/strings.xml @@ -135,9 +135,13 @@ Choose a login provider - Choose an account + Choose an account + + Sign in with a %1$s account Use %1$s as a login provider Privacy Policy and Terms of Service]]> + + Continue From eadef481fa343c0f2c54d47b6e8d2085da559f4c Mon Sep 17 00:00:00 2001 From: Titouan Thibaud Date: Wed, 30 Aug 2023 12:20:29 -0300 Subject: [PATCH 2/2] Bug 1847784: Provide a Theme to FedCM Provider Dialogs --- .../gecko/prompt/GeckoPromptDelegateTest.kt | 7 ++- .../feature/prompts/PromptFeature.kt | 26 +++++++++ .../identitycredential/DialogColors.kt | 57 +++++++++++++++++++ .../IdentityCredentialItem.kt | 39 ++++++------- .../identitycredential/SelectAccountDialog.kt | 51 ++++++++++------- .../SelectAccountDialogFragment.kt | 29 +++++++--- .../SelectProviderDialog.kt | 51 ++++++++++------- .../SelectProviderDialogFragment.kt | 24 ++++++-- .../previews/DialogPreviewMaterialTheme.kt | 24 ++++++++ .../previews/LightDarkPreview.kt | 16 ++++++ .../fenix/browser/BaseBrowserFragment.kt | 10 ++++ 11 files changed, 255 insertions(+), 79 deletions(-) create mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/DialogColors.kt create mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/DialogPreviewMaterialTheme.kt create mode 100644 android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/LightDarkPreview.kt diff --git a/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt b/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt index afbb0b857c53..e791750bcaf4 100644 --- a/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt +++ b/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt @@ -1254,8 +1254,9 @@ class GeckoPromptDelegateTest { ) val geckoAccount = GECKO_PROMPT_ACCOUNT_SELECTOR(0, "foo@mozilla.org", "foo", "icon") + val provider = GECKO_PROMPT_ACCOUNT_SELECTOR_PROVIDER("name", "domain", "favicon") val acAccount = geckoAccount.toAccount() - val geckoPrompt = geckoAccountSelectorPrompt(listOf(geckoAccount)) + val geckoPrompt = geckoAccountSelectorPrompt(listOf(geckoAccount), provider) var geckoResult = promptDelegate.onSelectIdentityCredentialAccount(mock(), geckoPrompt) geckoResult.accept { @@ -1280,7 +1281,7 @@ class GeckoPromptDelegateTest { } // Verifying we are handling the dismiss correctly. - geckoResult = promptDelegate.onSelectIdentityCredentialAccount(mock(), geckoAccountSelectorPrompt(listOf(geckoAccount))) + geckoResult = promptDelegate.onSelectIdentityCredentialAccount(mock(), geckoAccountSelectorPrompt(listOf(geckoAccount), provider)) geckoResult.accept { onDismissWasCalled = true } @@ -2012,9 +2013,11 @@ class GeckoPromptDelegateTest { private fun geckoAccountSelectorPrompt( accounts: List = emptyList(), + provider: GECKO_PROMPT_ACCOUNT_SELECTOR_PROVIDER, ): GeckoSession.PromptDelegate.IdentityCredential.AccountSelectorPrompt { val prompt: GeckoSession.PromptDelegate.IdentityCredential.AccountSelectorPrompt = mock() ReflectionUtils.setField(prompt, "accounts", accounts.toTypedArray()) + ReflectionUtils.setField(prompt, "provider", provider) return prompt } diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt index 73e0050f3b8c..a73b53f9d24f 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt @@ -76,6 +76,8 @@ import mozilla.components.feature.prompts.facts.emitPromptDisplayedFact import mozilla.components.feature.prompts.facts.emitSuccessfulAddressAutofillFormDetectedFact import mozilla.components.feature.prompts.facts.emitSuccessfulCreditCardAutofillFormDetectedFact import mozilla.components.feature.prompts.file.FilePicker +import mozilla.components.feature.prompts.identitycredential.DialogColors +import mozilla.components.feature.prompts.identitycredential.DialogColorsProvider import mozilla.components.feature.prompts.identitycredential.PrivacyPolicyDialogFragment import mozilla.components.feature.prompts.identitycredential.SelectAccountDialogFragment import mozilla.components.feature.prompts.identitycredential.SelectProviderDialogFragment @@ -154,6 +156,9 @@ class PromptFeature private constructor( private val store: BrowserStore, private var customTabId: String?, private val fragmentManager: FragmentManager, + private val identityCredentialColorsProvider: DialogColorsProvider = DialogColorsProvider { + DialogColors.default() + }, private val tabsUseCases: TabsUseCases, private val shareDelegate: ShareDelegate, private val exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen, @@ -197,6 +202,7 @@ class PromptFeature private constructor( customTabId: String? = null, fragmentManager: FragmentManager, tabsUseCases: TabsUseCases, + identityCredentialColorsProvider: DialogColorsProvider = DialogColorsProvider { DialogColors.default() }, shareDelegate: ShareDelegate = DefaultShareDelegate(), exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen, creditCardValidationDelegate: CreditCardValidationDelegate? = null, @@ -215,6 +221,7 @@ class PromptFeature private constructor( customTabId = customTabId, fragmentManager = fragmentManager, tabsUseCases = tabsUseCases, + identityCredentialColorsProvider = identityCredentialColorsProvider, shareDelegate = shareDelegate, exitFullscreenUsecase = exitFullscreenUsecase, creditCardValidationDelegate = creditCardValidationDelegate, @@ -335,22 +342,27 @@ class PromptFeature private constructor( is SelectLoginPrompt -> { loginPicker?.dismissCurrentLoginSelect(activePromptRequest as SelectLoginPrompt) } + is SaveLoginPrompt -> { (activePrompt?.get() as? SaveLoginDialogFragment)?.dismissAllowingStateLoss() } + is SaveCreditCard -> { (activePrompt?.get() as? CreditCardSaveDialogFragment)?.dismissAllowingStateLoss() } + is SelectCreditCard -> { creditCardPicker?.dismissSelectCreditCardRequest( activePromptRequest as SelectCreditCard, ) } + is SelectAddress -> { addressPicker?.dismissSelectAddressRequest( activePromptRequest as SelectAddress, ) } + is SingleChoice, is MultipleChoice, is MenuChoice, @@ -364,6 +376,7 @@ class PromptFeature private constructor( } } } + else -> { // no-op } @@ -493,6 +506,7 @@ class PromptFeature private constructor( emitPromptDisplayedFact(promptName = "FilePrompt") filePicker.handleFileRequest(promptRequest) } + is Share -> handleShareRequest(promptRequest, session) is SelectCreditCard -> { emitSuccessfulCreditCardAutofillFormDetectedFact() @@ -500,18 +514,21 @@ class PromptFeature private constructor( creditCardPicker?.handleSelectCreditCardRequest(promptRequest) } } + is SelectLoginPrompt -> { emitPromptDisplayedFact(promptName = "SelectLoginPrompt") if (promptRequest.logins.isNotEmpty()) { loginPicker?.handleSelectLoginRequest(promptRequest) } } + is SelectAddress -> { emitSuccessfulAddressAutofillFormDetectedFact() if (isAddressAutofillEnabled() && promptRequest.addresses.isNotEmpty()) { addressPicker?.handleSelectAddressRequest(promptRequest) } } + else -> handleDialogsRequest(promptRequest, session) } } @@ -535,6 +552,7 @@ class PromptFeature private constructor( promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs) it.onDeny() } + is Dismissible -> it.onDismiss() else -> { // no-op @@ -562,6 +580,7 @@ class PromptFeature private constructor( promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs) it.onConfirm(!shouldNotShowMoreDialogs) } + is SingleChoice -> it.onConfirm(value as Choice) is MenuChoice -> it.onConfirm(value as Choice) is BeforeUnload -> it.onLeave() @@ -570,6 +589,7 @@ class PromptFeature private constructor( promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs) it.onAllow() } + is MultipleChoice -> it.onConfirm(value as Array) is Authentication -> { @@ -596,8 +616,10 @@ class PromptFeature private constructor( when (buttonType) { MultiButtonDialogFragment.ButtonType.POSITIVE -> it.onConfirmPositiveButton(!isCheckBoxChecked) + MultiButtonDialogFragment.ButtonType.NEGATIVE -> it.onConfirmNegativeButton(!isCheckBoxChecked) + MultiButtonDialogFragment.ButtonType.NEUTRAL -> it.onConfirmNeutralButton(!isCheckBoxChecked) } @@ -838,6 +860,7 @@ class PromptFeature private constructor( shouldDismissOnLoad = true, ) } + is BeforeUnload -> { val title = container.getString(R.string.mozac_feature_prompt_before_unload_dialog_title) @@ -912,6 +935,7 @@ class PromptFeature private constructor( promptRequestUID = promptRequest.uid, shouldDismissOnLoad = true, providers = promptRequest.providers, + colorsProvider = identityCredentialColorsProvider, ) } @@ -922,6 +946,7 @@ class PromptFeature private constructor( shouldDismissOnLoad = true, accounts = promptRequest.accounts, provider = promptRequest.provider, + colorsProvider = identityCredentialColorsProvider, ) } @@ -1015,6 +1040,7 @@ class PromptFeature private constructor( is PromptRequest.IdentityCredential.SelectAccount, is PromptRequest.IdentityCredential.PrivacyPolicy, -> true + is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs } } diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/DialogColors.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/DialogColors.kt new file mode 100644 index 000000000000..246d8b2cb04c --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/DialogColors.kt @@ -0,0 +1,57 @@ +/* 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 mozilla.components.feature.prompts.identitycredential + +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +/** + * Represents the colors used by the dialogs. + */ +data class DialogColors( + val title: Color, + val description: Color, +) { + + companion object { + + /** + * Creates an [DialogColors] that represents the default colors used in an + * IdentityCredential dialog. + * + * @param title The text color for the title of a suggestion. + * @param description The text color for the description of a suggestion. + */ + @Composable + fun default( + title: Color = MaterialTheme.colors.onBackground, + description: Color = MaterialTheme.colors.onBackground.copy( + alpha = ContentAlpha.medium, + ), + ) = DialogColors( + title, + description, + ) + + /** + * Creates a provider that provides the default [DialogColors] + */ + fun defaultProvider() = DialogColorsProvider { default() } + } +} + +/** + * An [DialogColorsProvider] implementation can provide an [DialogColors] + */ +fun interface DialogColorsProvider { + + /** + * Provides [DialogColors] + */ + @Composable + fun provideColors(): DialogColors +} diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt index 3a0f4f549327..e89ce5425940 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt @@ -6,17 +6,15 @@ package mozilla.components.feature.prompts.identitycredential import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource 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.padding import androidx.compose.foundation.layout.size +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -24,10 +22,11 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import mozilla.components.ui.colors.PhotonColors +import mozilla.components.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme +import mozilla.components.feature.prompts.identitycredential.previews.LightDarkPreview /** - * List item used to display an IdentityCredentials item that supports clicks + * List item used to display an IdentityCredential item that supports clicks * * @param title the Title of the item * @param description The Description of the item. @@ -41,20 +40,14 @@ internal fun IdentityCredentialItem( title: String, description: String, modifier: Modifier = Modifier, + colors: DialogColors = DialogColors.default(), onClick: () -> Unit, beforeItemContent: (@Composable () -> Unit)? = null, ) { - // Used to propagate the ripple effect to the whole row - val interactionSource = remember { MutableInteractionSource() } - Row( modifier = modifier .fillMaxWidth() - .clickable( - onClick = onClick, - interactionSource = interactionSource, - indication = rememberRipple(color = PhotonColors.Black), - ) + .clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, ) { @@ -66,7 +59,7 @@ internal fun IdentityCredentialItem( style = TextStyle( fontSize = 16.sp, lineHeight = 24.sp, - color = PhotonColors.DarkGrey90, + color = colors.title, letterSpacing = 0.15.sp, ), maxLines = 1, @@ -77,7 +70,7 @@ internal fun IdentityCredentialItem( style = TextStyle( fontSize = 14.sp, lineHeight = 20.sp, - color = PhotonColors.DarkGrey05, + color = colors.description, letterSpacing = 0.25.sp, ), maxLines = 1, @@ -87,14 +80,16 @@ internal fun IdentityCredentialItem( } @Composable -@Preview(name = "Provider with no favicon") +@LightDarkPreview private fun ProviderItemPreview() { - IdentityCredentialItem( - modifier = Modifier.background(Color.White), - title = "Title", - description = "Description", - onClick = {}, - ) + DialogPreviewMaterialTheme { + IdentityCredentialItem( + modifier = Modifier.background(MaterialTheme.colors.background), + title = "Title", + description = "Description", + onClick = {}, + ) + } } @Composable diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt index c92fc1bf0891..ec8a67c3bf6b 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt @@ -29,13 +29,15 @@ import androidx.compose.ui.unit.sp import mozilla.components.concept.identitycredential.Account import mozilla.components.concept.identitycredential.Provider import mozilla.components.feature.prompts.R +import mozilla.components.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme +import mozilla.components.feature.prompts.identitycredential.previews.LightDarkPreview import mozilla.components.support.ktx.kotlin.base64PngToBitmap -import mozilla.components.ui.colors.PhotonColors /** * A Federated Credential Management dialog for selecting an account. * * @param provider The [Provider] on which the user is logging in. + * @param colors The colors of the dialog. * @param accounts The list of available accounts for this provider. * @param modifier [Modifier] to be applied to the layout. * @param onAccountClick Invoked when the user clicks on an item. @@ -45,6 +47,7 @@ fun SelectAccountDialog( provider: Provider, accounts: List, modifier: Modifier = Modifier, + colors: DialogColors = DialogColors.default(), onAccountClick: (Account) -> Unit, ) { Column( @@ -74,14 +77,14 @@ fun SelectAccountDialog( style = TextStyle( fontSize = 16.sp, lineHeight = 24.sp, - color = PhotonColors.DarkGrey90, + color = colors.title, letterSpacing = 0.15.sp, ), ) } accounts.forEach { account -> - AccountItem(account = account, onClick = onAccountClick) + AccountItem(account = account, colors = colors, onClick = onAccountClick) } Spacer(modifier = Modifier.height(16.dp)) @@ -92,11 +95,13 @@ fun SelectAccountDialog( private fun AccountItem( account: Account, modifier: Modifier = Modifier, + colors: DialogColors = DialogColors.default(), onClick: (Account) -> Unit, ) { IdentityCredentialItem( title = account.name, description = account.email, + colors = colors, modifier = modifier, onClick = { onClick(account) }, ) { @@ -133,27 +138,29 @@ private fun AccountItemPreview() { } @Composable -@Preview +@LightDarkPreview private fun SelectAccountDialogPreview() { - SelectAccountDialog( - provider = Provider(0, GOOGLE_FAVICON, "Google", "google.com"), - accounts = listOf( - Account( - 0, - "user@mozilla.com", - "User", - USER_PICTURE, - ), - Account( - 1, - "user2@mozilla.com", - "Google", - null, + DialogPreviewMaterialTheme { + SelectAccountDialog( + provider = Provider(0, GOOGLE_FAVICON, "Google", "google.com"), + accounts = listOf( + Account( + 0, + "user@mozilla.com", + "User", + USER_PICTURE, + ), + Account( + 1, + "user2@mozilla.com", + "Google", + null, + ), ), - ), - modifier = Modifier.background(Color.White), - onAccountClick = { }, - ) + modifier = Modifier.background(Color.White), + onAccountClick = { }, + ) + } } @Suppress("MaxLineLength") diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt index 696dc8304aec..f62b243276e4 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialogFragment.kt @@ -11,11 +11,13 @@ import android.os.Bundle import android.view.View import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog -import androidx.compose.ui.Modifier +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors import androidx.compose.ui.platform.ComposeView import mozilla.components.concept.identitycredential.Account import mozilla.components.concept.identitycredential.Provider -import mozilla.components.feature.prompts.dialog.AlertDialogFragment import mozilla.components.feature.prompts.dialog.KEY_PROMPT_UID import mozilla.components.feature.prompts.dialog.KEY_SESSION_ID import mozilla.components.feature.prompts.dialog.KEY_SHOULD_DISMISS_ON_LOAD @@ -35,6 +37,8 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { safeArguments.getParcelableArrayListCompat(KEY_ACCOUNTS, Account::class.java) ?: emptyList() } + private var colorsProvider: DialogColorsProvider = DialogColors.defaultProvider() + internal val provider: Provider by lazy { requireNotNull( safeArguments.getParcelableCompat( @@ -59,11 +63,15 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { internal fun createDialogContentView(): View { return ComposeView(requireContext()).apply { setContent { - SelectAccountDialog( - provider = provider, - accounts = accounts, - onAccountClick = ::onAccountChange, - ) + val colors = if (isSystemInDarkTheme()) darkColors() else lightColors() + MaterialTheme(colors) { + SelectAccountDialog( + provider = provider, + accounts = accounts, + colors = colorsProvider.provideColors(), + onAccountClick = ::onAccountChange, + ) + } } } } @@ -85,14 +93,18 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { * @param promptRequestUID Identifier of the [PromptRequest] for which this dialog is shown. * @param accounts The list of available accounts. * @param provider The provider on which the user is logging in. - * @param shouldDismissOnLoad Whether or not the dialog should automatically be dismissed when a new page is loaded. + * @param shouldDismissOnLoad Whether or not the dialog should automatically be dismissed + * when a new page is loaded. + * @param colorsProvider Provides [DialogColors] that define the colors in the Dialog */ + @Suppress("LongParameterList") fun newInstance( sessionId: String, promptRequestUID: String, accounts: List, provider: Provider, shouldDismissOnLoad: Boolean, + colorsProvider: DialogColorsProvider, ) = SelectAccountDialogFragment().apply { arguments = (arguments ?: Bundle()).apply { putString(KEY_SESSION_ID, sessionId) @@ -101,6 +113,7 @@ internal class SelectAccountDialogFragment : PromptDialogFragment() { putParcelableArrayList(KEY_ACCOUNTS, ArrayList(accounts)) putParcelable(KEY_PROVIDER, provider) } + this.colorsProvider = colorsProvider } } } diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt index 69ce3bf4d53e..e867077c0528 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -26,16 +27,22 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import mozilla.components.concept.identitycredential.Provider import mozilla.components.feature.prompts.R +import mozilla.components.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme +import mozilla.components.feature.prompts.identitycredential.previews.LightDarkPreview import mozilla.components.support.ktx.kotlin.base64PngToBitmap -import mozilla.components.ui.colors.PhotonColors /** * A Federated Credential Management dialog for selecting a provider. + * @param providers The list of available providers. + * @param colors The colors of the dialog. + * @param modifier [Modifier] to be applied to the layout. + * @param onProviderClick Called when the user clicks on an item. */ @Composable fun SelectProviderDialog( providers: List, modifier: Modifier = Modifier, + colors: DialogColors = DialogColors.default(), onProviderClick: (Provider) -> Unit, ) { Column( @@ -46,14 +53,14 @@ fun SelectProviderDialog( style = TextStyle( fontSize = 16.sp, lineHeight = 24.sp, - color = PhotonColors.DarkGrey90, + color = colors.title, letterSpacing = 0.15.sp, ), modifier = Modifier.padding(16.dp), ) providers.forEach { provider -> - ProviderItem(provider = provider, onClick = onProviderClick) + ProviderItem(provider = provider, onClick = onProviderClick, colors = colors) } Spacer(modifier = Modifier.height(16.dp)) @@ -64,12 +71,14 @@ fun SelectProviderDialog( private fun ProviderItem( provider: Provider, modifier: Modifier = Modifier, + colors: DialogColors = DialogColors.default(), onClick: (Provider) -> Unit, ) { IdentityCredentialItem( title = provider.name, description = provider.domain, modifier = modifier, + colors = colors, onClick = { onClick(provider) }, ) { provider.icon?.base64PngToBitmap()?.asImageBitmap()?.let { bitmap -> @@ -105,25 +114,27 @@ private fun ProviderItemPreview() { } @Composable -@Preview +@LightDarkPreview private fun SelectProviderDialogPreview() { - SelectProviderDialog( - providers = listOf( - Provider( - 0, - null, - "Title", - "Description", + DialogPreviewMaterialTheme { + SelectProviderDialog( + providers = listOf( + Provider( + 0, + null, + "Title", + "Description", + ), + Provider( + 0, + GOOGLE_FAVICON, + "Google", + "google.com", + ), ), - Provider( - 0, - GOOGLE_FAVICON, - "Google", - "google.com", - ), - ), - modifier = Modifier.background(Color.White), - ) { } + modifier = Modifier.background(MaterialTheme.colors.background), + ) { } + } } @Suppress("MaxLineLength") diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt index 317d743c37f0..db68fea6fe28 100644 --- a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialogFragment.kt @@ -11,6 +11,10 @@ import android.os.Bundle import android.view.View import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors import androidx.compose.ui.platform.ComposeView import mozilla.components.concept.identitycredential.Provider import mozilla.components.feature.prompts.dialog.KEY_PROMPT_UID @@ -31,6 +35,8 @@ internal class SelectProviderDialogFragment : PromptDialogFragment() { ?: emptyList() } + private var colorsProvider: DialogColorsProvider = DialogColors.defaultProvider() + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(requireContext()) .setCancelable(true) @@ -46,10 +52,14 @@ internal class SelectProviderDialogFragment : PromptDialogFragment() { internal fun createDialogContentView(): View { return ComposeView(requireContext()).apply { setContent { - SelectProviderDialog( - providers = providers, - onProviderClick = ::onProviderChange, - ) + val colors = if (isSystemInDarkTheme()) darkColors() else lightColors() + MaterialTheme(colors) { + SelectProviderDialog( + providers = providers, + onProviderClick = ::onProviderChange, + colors = DialogColors.default(), + ) + } } } } @@ -70,13 +80,16 @@ internal class SelectProviderDialogFragment : PromptDialogFragment() { * @param sessionId The id of the session for which this dialog will be created. * @param promptRequestUID Identifier of the [PromptRequest] for which this dialog is shown. * @param providers The list of available providers. - * @param shouldDismissOnLoad Whether or not the dialog should automatically be dismissed when a new page is loaded. + * @param shouldDismissOnLoad Whether or not the dialog should automatically be dismissed + * when a new page is loaded. + * @param colorsProvider Provides [DialogColors] that define the colors in the Dialog */ fun newInstance( sessionId: String, promptRequestUID: String, providers: List, shouldDismissOnLoad: Boolean, + colorsProvider: DialogColorsProvider, ) = SelectProviderDialogFragment().apply { arguments = (arguments ?: Bundle()).apply { putString(KEY_SESSION_ID, sessionId) @@ -84,6 +97,7 @@ internal class SelectProviderDialogFragment : PromptDialogFragment() { putBoolean(KEY_SHOULD_DISMISS_ON_LOAD, shouldDismissOnLoad) putParcelableArrayList(KEY_PROVIDERS, ArrayList(providers)) } + this.colorsProvider = colorsProvider } } } diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/DialogPreviewMaterialTheme.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/DialogPreviewMaterialTheme.kt new file mode 100644 index 000000000000..afae915e0d6a --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/DialogPreviewMaterialTheme.kt @@ -0,0 +1,24 @@ +/* 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 mozilla.components.feature.prompts.identitycredential.previews + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable +import mozilla.components.ui.colors.PhotonColors + +@Composable +internal fun DialogPreviewMaterialTheme(content: @Composable () -> Unit) { + val colors = if (!isSystemInDarkTheme()) { + lightColors() + } else { + darkColors(background = PhotonColors.DarkGrey30) + } + MaterialTheme(colors = colors) { + content() + } +} diff --git a/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/LightDarkPreview.kt b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/LightDarkPreview.kt new file mode 100644 index 000000000000..83c80f9448e5 --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/previews/LightDarkPreview.kt @@ -0,0 +1,16 @@ +/* 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 mozilla.components.feature.prompts.identitycredential.previews + +import android.content.res.Configuration +import androidx.compose.ui.tooling.preview.Preview + +/** + * A wrapper annotation for the two uiMode that are commonly used + * in Compose preview functions. + */ +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +annotation class LightDarkPreview diff --git a/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 93756465e368..58352e7cb4ac 100644 --- a/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -76,6 +76,8 @@ import mozilla.components.feature.prompts.PromptFeature import mozilla.components.feature.prompts.PromptFeature.Companion.PIN_REQUEST import mozilla.components.feature.prompts.address.AddressDelegate import mozilla.components.feature.prompts.creditcard.CreditCardDelegate +import mozilla.components.feature.prompts.identitycredential.DialogColors +import mozilla.components.feature.prompts.identitycredential.DialogColorsProvider import mozilla.components.feature.prompts.login.LoginDelegate import mozilla.components.feature.prompts.share.ShareDelegate import mozilla.components.feature.readerview.ReaderViewFeature @@ -669,12 +671,20 @@ abstract class BaseBrowserFragment : view = view, ) + val colorsProvider = DialogColorsProvider { + DialogColors( + title = ThemeManager.resolveAttributeColor(attribute = R.attr.textPrimary), + description = ThemeManager.resolveAttributeColor(attribute = R.attr.textSecondary), + ) + } + promptsFeature.set( feature = PromptFeature( activity = activity, store = store, customTabId = customTabSessionId, fragmentManager = parentFragmentManager, + identityCredentialColorsProvider = colorsProvider, tabsUseCases = requireComponents.useCases.tabsUseCases, creditCardValidationDelegate = DefaultCreditCardValidationDelegate( context.components.core.lazyAutofillStorage,