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 = 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/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt b/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt index d86f5bf57215..163f2e8b3674 100644 --- a/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt +++ b/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/prompt/PromptRequest.kt @@ -129,11 +129,13 @@ sealed class PromptRequest( /** * Value type that represents Identity Credential request for selecting an [Account] prompt. * @property accounts A list of accounts which the user could select from. + * @property providerName The name of the provider that will be used for the login * @property onConfirm callback to let the page know the user selected an account. * @property onDismiss callback to let the page know the user dismissed the dialog. */ data class SelectAccount( val accounts: List, + 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..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, ) } @@ -921,6 +945,8 @@ class PromptFeature private constructor( promptRequestUID = promptRequest.uid, shouldDismissOnLoad = true, accounts = promptRequest.accounts, + provider = promptRequest.provider, + colorsProvider = identityCredentialColorsProvider, ) } @@ -1014,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/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/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 new file mode 100644 index 000000000000..e89ce5425940 --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/IdentityCredentialItem.kt @@ -0,0 +1,106 @@ +/* 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.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.runtime.Composable +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.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme +import mozilla.components.feature.prompts.identitycredential.previews.LightDarkPreview + +/** + * 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. + * @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, + colors: DialogColors = DialogColors.default(), + onClick: () -> Unit, + beforeItemContent: (@Composable () -> Unit)? = null, +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .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 = colors.title, + letterSpacing = 0.15.sp, + ), + maxLines = 1, + ) + + Text( + text = description, + style = TextStyle( + fontSize = 14.sp, + lineHeight = 20.sp, + color = colors.description, + letterSpacing = 0.25.sp, + ), + maxLines = 1, + ) + } + } +} + +@Composable +@LightDarkPreview +private fun ProviderItemPreview() { + DialogPreviewMaterialTheme { + IdentityCredentialItem( + modifier = Modifier.background(MaterialTheme.colors.background), + 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..ec8a67c3bf6b --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectAccountDialog.kt @@ -0,0 +1,172 @@ +/* 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.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme +import mozilla.components.feature.prompts.identitycredential.previews.LightDarkPreview +import mozilla.components.support.ktx.kotlin.base64PngToBitmap + +/** + * 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. + */ +@Composable +fun SelectAccountDialog( + provider: Provider, + accounts: List, + modifier: Modifier = Modifier, + colors: DialogColors = DialogColors.default(), + 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 = colors.title, + letterSpacing = 0.15.sp, + ), + ) + } + + accounts.forEach { account -> + AccountItem(account = account, colors = colors, onClick = onAccountClick) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Composable +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) }, + ) { + 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 +@LightDarkPreview +private fun SelectAccountDialogPreview() { + 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 = { }, + ) + } +} + +@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..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 @@ -8,37 +8,49 @@ 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.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.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 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() } + private var colorsProvider: DialogColorsProvider = DialogColors.defaultProvider() + + 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 +61,19 @@ 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 { + val colors = if (isSystemInDarkTheme()) darkColors() else lightColors() + MaterialTheme(colors) { + SelectAccountDialog( + provider = provider, + accounts = accounts, + colors = colorsProvider.provideColors(), + onAccountClick = ::onAccountChange, + ) + } + } } - listAdapter.submitList(accounts) } /** @@ -75,18 +86,34 @@ 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. + * @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) putString(KEY_PROMPT_UID, promptRequestUID) putBoolean(KEY_SHOULD_DISMISS_ON_LOAD, shouldDismissOnLoad) 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 new file mode 100644 index 000000000000..e867077c0528 --- /dev/null +++ b/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/identitycredential/SelectProviderDialog.kt @@ -0,0 +1,142 @@ +/* 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.MaterialTheme +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.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme +import mozilla.components.feature.prompts.identitycredential.previews.LightDarkPreview +import mozilla.components.support.ktx.kotlin.base64PngToBitmap + +/** + * 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( + 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 = colors.title, + letterSpacing = 0.15.sp, + ), + modifier = Modifier.padding(16.dp), + ) + + providers.forEach { provider -> + ProviderItem(provider = provider, onClick = onProviderClick, colors = colors) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Composable +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 -> + 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 +@LightDarkPreview +private fun SelectProviderDialogPreview() { + DialogPreviewMaterialTheme { + SelectProviderDialog( + providers = listOf( + Provider( + 0, + null, + "Title", + "Description", + ), + Provider( + 0, + GOOGLE_FAVICON, + "Google", + "google.com", + ), + ), + modifier = Modifier.background(MaterialTheme.colors.background), + ) { } + } +} + +@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..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 @@ -8,14 +8,15 @@ 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.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.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 +30,16 @@ 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() } + private var colorsProvider: DialogColorsProvider = DialogColors.defaultProvider() + 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 +50,18 @@ 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 { + val colors = if (isSystemInDarkTheme()) darkColors() else lightColors() + MaterialTheme(colors) { + SelectProviderDialog( + providers = providers, + onProviderClick = ::onProviderChange, + colors = DialogColors.default(), + ) + } + } } - listAdapter.submitList(providers) } /** @@ -76,11 +74,22 @@ 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. + * @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) @@ -88,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/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 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,