mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 09:54:42 +00:00
Bug 1895440: add new UX for strong password generator feature r=boek,android-reviewers,delphine,geckoview-reviewers,007
Differential Revision: https://phabricator.services.mozilla.com/D211003
This commit is contained in:
parent
f114f4f423
commit
a6c628a00d
@ -191,7 +191,7 @@ sealed class PromptRequest(
|
|||||||
* Value type that represents a request for a select login prompt.
|
* Value type that represents a request for a select login prompt.
|
||||||
* @property logins a list of logins that are associated with the current domain.
|
* @property logins a list of logins that are associated with the current domain.
|
||||||
* @property generatedPassword the suggested strong password that was generated.
|
* @property generatedPassword the suggested strong password that was generated.
|
||||||
* @property onConfirm callback that is called when the user wants to save the login.
|
* @property onConfirm callback that is called when the user wants to select the login.
|
||||||
* @property onDismiss callback to let the page know the user dismissed the dialog.
|
* @property onDismiss callback to let the page know the user dismissed the dialog.
|
||||||
*/
|
*/
|
||||||
data class SelectLoginPrompt(
|
data class SelectLoginPrompt(
|
||||||
|
@ -46,6 +46,7 @@ import mozilla.components.concept.identitycredential.Account
|
|||||||
import mozilla.components.concept.identitycredential.Provider
|
import mozilla.components.concept.identitycredential.Provider
|
||||||
import mozilla.components.concept.storage.CreditCardEntry
|
import mozilla.components.concept.storage.CreditCardEntry
|
||||||
import mozilla.components.concept.storage.CreditCardValidationDelegate
|
import mozilla.components.concept.storage.CreditCardValidationDelegate
|
||||||
|
import mozilla.components.concept.storage.Login
|
||||||
import mozilla.components.concept.storage.LoginEntry
|
import mozilla.components.concept.storage.LoginEntry
|
||||||
import mozilla.components.concept.storage.LoginValidationDelegate
|
import mozilla.components.concept.storage.LoginValidationDelegate
|
||||||
import mozilla.components.feature.prompts.address.AddressDelegate
|
import mozilla.components.feature.prompts.address.AddressDelegate
|
||||||
@ -86,6 +87,9 @@ import mozilla.components.feature.prompts.identitycredential.SelectProviderDialo
|
|||||||
import mozilla.components.feature.prompts.login.LoginDelegate
|
import mozilla.components.feature.prompts.login.LoginDelegate
|
||||||
import mozilla.components.feature.prompts.login.LoginExceptions
|
import mozilla.components.feature.prompts.login.LoginExceptions
|
||||||
import mozilla.components.feature.prompts.login.LoginPicker
|
import mozilla.components.feature.prompts.login.LoginPicker
|
||||||
|
import mozilla.components.feature.prompts.login.PasswordGeneratorDialogColors
|
||||||
|
import mozilla.components.feature.prompts.login.PasswordGeneratorDialogColorsProvider
|
||||||
|
import mozilla.components.feature.prompts.login.PasswordGeneratorDialogFragment
|
||||||
import mozilla.components.feature.prompts.login.StrongPasswordPromptViewListener
|
import mozilla.components.feature.prompts.login.StrongPasswordPromptViewListener
|
||||||
import mozilla.components.feature.prompts.login.SuggestStrongPasswordDelegate
|
import mozilla.components.feature.prompts.login.SuggestStrongPasswordDelegate
|
||||||
import mozilla.components.feature.prompts.share.DefaultShareDelegate
|
import mozilla.components.feature.prompts.share.DefaultShareDelegate
|
||||||
@ -154,6 +158,10 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
|
|||||||
* feature is enabled or not. If this resolves to 'false', the feature will be hidden.
|
* feature is enabled or not. If this resolves to 'false', the feature will be hidden.
|
||||||
* @property onSaveLoginWithStrongPassword A callback invoked to save a new login that uses the
|
* @property onSaveLoginWithStrongPassword A callback invoked to save a new login that uses the
|
||||||
* generated strong password
|
* generated strong password
|
||||||
|
* @property shouldAutomaticallyShowSuggestedPassword A callback invoked to check whether the user
|
||||||
|
* is engaging with signup for the first time.
|
||||||
|
* @property onFirstTimeEngagedWithSignup A callback invoked when user is engaged with signup for
|
||||||
|
* the first time.
|
||||||
* @property creditCardDelegate Delegate for credit card picker.
|
* @property creditCardDelegate Delegate for credit card picker.
|
||||||
* @property addressDelegate Delegate for address picker.
|
* @property addressDelegate Delegate for address picker.
|
||||||
* @property fileUploadsDirCleaner a [FileUploadsDirCleaner] to clean up temporary file uploads.
|
* @property fileUploadsDirCleaner a [FileUploadsDirCleaner] to clean up temporary file uploads.
|
||||||
@ -161,7 +169,7 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
|
|||||||
* need to be requested before a prompt (e.g. a file picker) can be displayed.
|
* need to be requested before a prompt (e.g. a file picker) can be displayed.
|
||||||
* Once the request is completed, [onPermissionsResult] needs to be invoked.
|
* Once the request is completed, [onPermissionsResult] needs to be invoked.
|
||||||
*/
|
*/
|
||||||
@Suppress("LargeClass", "LongParameterList")
|
@Suppress("LargeClass", "LongParameterList", "MaxLineLength")
|
||||||
class PromptFeature private constructor(
|
class PromptFeature private constructor(
|
||||||
private val container: PromptContainer,
|
private val container: PromptContainer,
|
||||||
private val store: BrowserStore,
|
private val store: BrowserStore,
|
||||||
@ -184,7 +192,13 @@ class PromptFeature private constructor(
|
|||||||
private val suggestStrongPasswordDelegate: SuggestStrongPasswordDelegate = object :
|
private val suggestStrongPasswordDelegate: SuggestStrongPasswordDelegate = object :
|
||||||
SuggestStrongPasswordDelegate {},
|
SuggestStrongPasswordDelegate {},
|
||||||
private val isSuggestStrongPasswordEnabled: Boolean = false,
|
private val isSuggestStrongPasswordEnabled: Boolean = false,
|
||||||
|
private var shouldAutomaticallyShowSuggestedPassword: () -> Boolean = { false },
|
||||||
|
private val onFirstTimeEngagedWithSignup: () -> Unit = {},
|
||||||
private val onSaveLoginWithStrongPassword: (String, String) -> Unit = { _, _ -> },
|
private val onSaveLoginWithStrongPassword: (String, String) -> Unit = { _, _ -> },
|
||||||
|
private val onSavedGeneratedPassword: () -> Unit = {},
|
||||||
|
private val passwordGeneratorColorsProvider: PasswordGeneratorDialogColorsProvider = PasswordGeneratorDialogColorsProvider {
|
||||||
|
PasswordGeneratorDialogColors.default()
|
||||||
|
},
|
||||||
private val creditCardDelegate: CreditCardDelegate = object : CreditCardDelegate {},
|
private val creditCardDelegate: CreditCardDelegate = object : CreditCardDelegate {},
|
||||||
private val addressDelegate: AddressDelegate = DefaultAddressDelegate(),
|
private val addressDelegate: AddressDelegate = DefaultAddressDelegate(),
|
||||||
private val fileUploadsDirCleaner: FileUploadsDirCleaner,
|
private val fileUploadsDirCleaner: FileUploadsDirCleaner,
|
||||||
@ -233,7 +247,13 @@ class PromptFeature private constructor(
|
|||||||
suggestStrongPasswordDelegate: SuggestStrongPasswordDelegate = object :
|
suggestStrongPasswordDelegate: SuggestStrongPasswordDelegate = object :
|
||||||
SuggestStrongPasswordDelegate {},
|
SuggestStrongPasswordDelegate {},
|
||||||
isSuggestStrongPasswordEnabled: Boolean = false,
|
isSuggestStrongPasswordEnabled: Boolean = false,
|
||||||
|
shouldAutomaticallyShowSuggestedPassword: () -> Boolean = { false },
|
||||||
|
onFirstTimeEngagedWithSignup: () -> Unit = {},
|
||||||
onSaveLoginWithStrongPassword: (String, String) -> Unit = { _, _ -> },
|
onSaveLoginWithStrongPassword: (String, String) -> Unit = { _, _ -> },
|
||||||
|
onSavedGeneratedPassword: () -> Unit = {},
|
||||||
|
passwordGeneratorColorsProvider: PasswordGeneratorDialogColorsProvider = PasswordGeneratorDialogColorsProvider {
|
||||||
|
PasswordGeneratorDialogColors.default()
|
||||||
|
},
|
||||||
creditCardDelegate: CreditCardDelegate = object : CreditCardDelegate {},
|
creditCardDelegate: CreditCardDelegate = object : CreditCardDelegate {},
|
||||||
addressDelegate: AddressDelegate = DefaultAddressDelegate(),
|
addressDelegate: AddressDelegate = DefaultAddressDelegate(),
|
||||||
fileUploadsDirCleaner: FileUploadsDirCleaner,
|
fileUploadsDirCleaner: FileUploadsDirCleaner,
|
||||||
@ -259,7 +279,11 @@ class PromptFeature private constructor(
|
|||||||
loginDelegate = loginDelegate,
|
loginDelegate = loginDelegate,
|
||||||
suggestStrongPasswordDelegate = suggestStrongPasswordDelegate,
|
suggestStrongPasswordDelegate = suggestStrongPasswordDelegate,
|
||||||
isSuggestStrongPasswordEnabled = isSuggestStrongPasswordEnabled,
|
isSuggestStrongPasswordEnabled = isSuggestStrongPasswordEnabled,
|
||||||
|
shouldAutomaticallyShowSuggestedPassword = shouldAutomaticallyShowSuggestedPassword,
|
||||||
|
onFirstTimeEngagedWithSignup = onFirstTimeEngagedWithSignup,
|
||||||
onSaveLoginWithStrongPassword = onSaveLoginWithStrongPassword,
|
onSaveLoginWithStrongPassword = onSaveLoginWithStrongPassword,
|
||||||
|
onSavedGeneratedPassword = onSavedGeneratedPassword,
|
||||||
|
passwordGeneratorColorsProvider = passwordGeneratorColorsProvider,
|
||||||
creditCardDelegate = creditCardDelegate,
|
creditCardDelegate = creditCardDelegate,
|
||||||
addressDelegate = addressDelegate,
|
addressDelegate = addressDelegate,
|
||||||
)
|
)
|
||||||
@ -283,7 +307,10 @@ class PromptFeature private constructor(
|
|||||||
suggestStrongPasswordDelegate: SuggestStrongPasswordDelegate = object :
|
suggestStrongPasswordDelegate: SuggestStrongPasswordDelegate = object :
|
||||||
SuggestStrongPasswordDelegate {},
|
SuggestStrongPasswordDelegate {},
|
||||||
isSuggestStrongPasswordEnabled: Boolean = false,
|
isSuggestStrongPasswordEnabled: Boolean = false,
|
||||||
|
shouldAutomaticallyShowSuggestedPassword: () -> Boolean = { false },
|
||||||
|
onFirstTimeEngagedWithSignup: () -> Unit = {},
|
||||||
onSaveLoginWithStrongPassword: (String, String) -> Unit = { _, _ -> },
|
onSaveLoginWithStrongPassword: (String, String) -> Unit = { _, _ -> },
|
||||||
|
onSavedGeneratedPassword: () -> Unit = {},
|
||||||
creditCardDelegate: CreditCardDelegate = object : CreditCardDelegate {},
|
creditCardDelegate: CreditCardDelegate = object : CreditCardDelegate {},
|
||||||
addressDelegate: AddressDelegate = DefaultAddressDelegate(),
|
addressDelegate: AddressDelegate = DefaultAddressDelegate(),
|
||||||
fileUploadsDirCleaner: FileUploadsDirCleaner,
|
fileUploadsDirCleaner: FileUploadsDirCleaner,
|
||||||
@ -308,7 +335,10 @@ class PromptFeature private constructor(
|
|||||||
loginDelegate = loginDelegate,
|
loginDelegate = loginDelegate,
|
||||||
suggestStrongPasswordDelegate = suggestStrongPasswordDelegate,
|
suggestStrongPasswordDelegate = suggestStrongPasswordDelegate,
|
||||||
isSuggestStrongPasswordEnabled = isSuggestStrongPasswordEnabled,
|
isSuggestStrongPasswordEnabled = isSuggestStrongPasswordEnabled,
|
||||||
|
shouldAutomaticallyShowSuggestedPassword = shouldAutomaticallyShowSuggestedPassword,
|
||||||
|
onFirstTimeEngagedWithSignup = onFirstTimeEngagedWithSignup,
|
||||||
onSaveLoginWithStrongPassword = onSaveLoginWithStrongPassword,
|
onSaveLoginWithStrongPassword = onSaveLoginWithStrongPassword,
|
||||||
|
onSavedGeneratedPassword = onSavedGeneratedPassword,
|
||||||
creditCardDelegate = creditCardDelegate,
|
creditCardDelegate = creditCardDelegate,
|
||||||
addressDelegate = addressDelegate,
|
addressDelegate = addressDelegate,
|
||||||
)
|
)
|
||||||
@ -572,14 +602,20 @@ class PromptFeature private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (promptRequest.generatedPassword != null && isSuggestStrongPasswordEnabled) {
|
if (promptRequest.generatedPassword != null && isSuggestStrongPasswordEnabled) {
|
||||||
val currentUrl =
|
if (shouldAutomaticallyShowSuggestedPassword.invoke()) {
|
||||||
store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.content?.url
|
onFirstTimeEngagedWithSignup.invoke()
|
||||||
if (currentUrl != null) {
|
handleDialogsRequest(
|
||||||
strongPasswordPromptViewListener?.handleSuggestStrongPasswordRequest(
|
|
||||||
promptRequest,
|
promptRequest,
|
||||||
currentUrl,
|
session,
|
||||||
onSaveLoginWithStrongPassword,
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
strongPasswordPromptViewListener?.onGeneratedPasswordPromptClick = {
|
||||||
|
handleDialogsRequest(
|
||||||
|
promptRequest,
|
||||||
|
session,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
strongPasswordPromptViewListener?.handleSuggestStrongPasswordRequest()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loginPicker?.handleSelectLoginRequest(promptRequest)
|
loginPicker?.handleSelectLoginRequest(promptRequest)
|
||||||
@ -694,6 +730,7 @@ class PromptFeature private constructor(
|
|||||||
is PromptRequest.IdentityCredential.SelectProvider -> it.onConfirm(value as Provider)
|
is PromptRequest.IdentityCredential.SelectProvider -> it.onConfirm(value as Provider)
|
||||||
is PromptRequest.IdentityCredential.SelectAccount -> it.onConfirm(value as Account)
|
is PromptRequest.IdentityCredential.SelectAccount -> it.onConfirm(value as Account)
|
||||||
is PromptRequest.IdentityCredential.PrivacyPolicy -> it.onConfirm(value as Boolean)
|
is PromptRequest.IdentityCredential.PrivacyPolicy -> it.onConfirm(value as Boolean)
|
||||||
|
is SelectLoginPrompt -> it.onConfirm(value as Login)
|
||||||
else -> {
|
else -> {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
@ -760,6 +797,29 @@ class PromptFeature private constructor(
|
|||||||
) {
|
) {
|
||||||
// Requests that are handled with dialogs
|
// Requests that are handled with dialogs
|
||||||
val dialog = when (promptRequest) {
|
val dialog = when (promptRequest) {
|
||||||
|
is SelectLoginPrompt -> {
|
||||||
|
val currentUrl =
|
||||||
|
store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.content?.url
|
||||||
|
val generatedPassword = promptRequest.generatedPassword
|
||||||
|
|
||||||
|
if (generatedPassword == null || currentUrl == null) {
|
||||||
|
logger.debug(
|
||||||
|
"Ignoring received SelectLogin.onGeneratedPasswordPromptClick" +
|
||||||
|
" when either the generated password or the currentUrl is null.",
|
||||||
|
)
|
||||||
|
dismissDialogRequest(promptRequest, session)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
PasswordGeneratorDialogFragment.newInstance(
|
||||||
|
sessionId = session.id,
|
||||||
|
promptRequestUID = promptRequest.uid,
|
||||||
|
generatedPassword = generatedPassword,
|
||||||
|
currentUrl = currentUrl,
|
||||||
|
onSavedGeneratedPassword = onSavedGeneratedPassword,
|
||||||
|
colorsProvider = passwordGeneratorColorsProvider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is SaveCreditCard -> {
|
is SaveCreditCard -> {
|
||||||
if (!isCreditCardAutofillEnabled.invoke() || creditCardValidationDelegate == null ||
|
if (!isCreditCardAutofillEnabled.invoke() || creditCardValidationDelegate == null ||
|
||||||
!promptRequest.creditCard.isValid
|
!promptRequest.creditCard.isValid
|
||||||
|
@ -12,13 +12,9 @@ interface PasswordPromptView {
|
|||||||
var listener: Listener?
|
var listener: Listener?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a simple prompt with the given [generatedPassword].
|
* Shows a simple prompt for using a generated password.
|
||||||
*/
|
*/
|
||||||
fun showPrompt(
|
fun showPrompt()
|
||||||
generatedPassword: String,
|
|
||||||
url: String,
|
|
||||||
onSaveLoginWithStrongPassword: (url: String, password: String) -> Unit,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the prompt.
|
* Hides the prompt.
|
||||||
@ -30,13 +26,8 @@ interface PasswordPromptView {
|
|||||||
*/
|
*/
|
||||||
interface Listener {
|
interface Listener {
|
||||||
/**
|
/**
|
||||||
* Called when a user wants to use a strong generated password.
|
* Called when a user clicks on the password generator prompt
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
fun onUseGeneratedPassword(
|
fun onGeneratedPasswordPromptClick()
|
||||||
generatedPassword: String,
|
|
||||||
url: String,
|
|
||||||
onSaveLoginWithStrongPassword: (url: String, password: String) -> Unit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
/* 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.login
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import mozilla.components.feature.prompts.R
|
||||||
|
import mozilla.components.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme
|
||||||
|
|
||||||
|
private val FONT_SIZE = 16.sp
|
||||||
|
private val LINE_HEIGHT = 24.sp
|
||||||
|
private val LETTER_SPACING = 0.15.sp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The password generator bottom sheet
|
||||||
|
*
|
||||||
|
* @param generatedStrongPassword The generated password.
|
||||||
|
* @param onUsePassword Invoked when the user clicks on the UsePassword button.
|
||||||
|
* @param onCancelDialog Invoked when the user clicks on the NotNow button.
|
||||||
|
* @param colors The colors of the dialog.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun PasswordGeneratorBottomSheet(
|
||||||
|
generatedStrongPassword: String,
|
||||||
|
onUsePassword: () -> Unit,
|
||||||
|
onCancelDialog: () -> Unit,
|
||||||
|
colors: PasswordGeneratorDialogColors = PasswordGeneratorDialogColors.default(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colors.background)
|
||||||
|
.padding(all = 8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
StrongPasswordBottomSheetTitle(colors = colors)
|
||||||
|
|
||||||
|
StrongPasswordBottomSheetDescription(colors = colors)
|
||||||
|
|
||||||
|
StrongPasswordBottomSheetPasswordBox(
|
||||||
|
generatedPassword = generatedStrongPassword,
|
||||||
|
colors = colors,
|
||||||
|
)
|
||||||
|
|
||||||
|
StrongPasswordBottomSheetButtons(
|
||||||
|
onUsePassword = { onUsePassword() },
|
||||||
|
onCancelDialog = { onCancelDialog() },
|
||||||
|
colors = colors,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StrongPasswordBottomSheetTitle(colors: PasswordGeneratorDialogColors) {
|
||||||
|
Row {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.mozac_ic_login_24),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
colorFilter = ColorFilter.tint(colors.title),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
text = stringResource(id = R.string.mozac_feature_prompts_suggest_strong_password_title),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = FONT_SIZE,
|
||||||
|
lineHeight = LINE_HEIGHT,
|
||||||
|
color = colors.title,
|
||||||
|
letterSpacing = LETTER_SPACING,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StrongPasswordBottomSheetDescription(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
colors: PasswordGeneratorDialogColors,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = modifier.padding(start = 40.dp, top = 0.dp, end = 12.dp, bottom = 16.dp),
|
||||||
|
text = stringResource(id = R.string.mozac_feature_prompts_suggest_strong_password_description),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = FONT_SIZE,
|
||||||
|
lineHeight = LINE_HEIGHT,
|
||||||
|
color = colors.description,
|
||||||
|
letterSpacing = LETTER_SPACING,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StrongPasswordBottomSheetPasswordBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
generatedPassword: String,
|
||||||
|
colors: PasswordGeneratorDialogColors,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 40.dp, top = 8.dp, end = 12.dp, bottom = 8.dp)
|
||||||
|
.background(colors.passwordBox)
|
||||||
|
.border(1.dp, colors.boxBorder)
|
||||||
|
.padding(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = modifier.padding(8.dp),
|
||||||
|
text = generatedPassword,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = FONT_SIZE,
|
||||||
|
lineHeight = LINE_HEIGHT,
|
||||||
|
color = colors.title,
|
||||||
|
letterSpacing = LETTER_SPACING,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StrongPasswordBottomSheetButtons(
|
||||||
|
onUsePassword: () -> Unit,
|
||||||
|
onCancelDialog: () -> Unit,
|
||||||
|
colors: PasswordGeneratorDialogColors,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.End),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp, vertical = 16.dp)
|
||||||
|
.height(48.dp),
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onCancelDialog() },
|
||||||
|
shape = RectangleShape,
|
||||||
|
colors = ButtonDefaults.buttonColors(backgroundColor = colors.background),
|
||||||
|
modifier = Modifier.height(48.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.mozac_feature_prompt_not_now),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
color = colors.confirmButton,
|
||||||
|
letterSpacing = 0.15.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { onUsePassword() },
|
||||||
|
shape = RectangleShape,
|
||||||
|
colors = ButtonDefaults.buttonColors(backgroundColor = colors.confirmButton),
|
||||||
|
modifier = Modifier.height(48.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.mozac_feature_prompts_suggest_strong_password_use_password),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
color = Color.White,
|
||||||
|
letterSpacing = 0.15.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
private fun GenerateStrongPasswordDialogPreview() {
|
||||||
|
DialogPreviewMaterialTheme {
|
||||||
|
PasswordGeneratorBottomSheet(
|
||||||
|
generatedStrongPassword = "StrongPassword123#",
|
||||||
|
onUsePassword = {},
|
||||||
|
onCancelDialog = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/* 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.login
|
||||||
|
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a [PasswordGeneratorDialogColors] that represents the default colors used in an
|
||||||
|
* Password Generator bottom sheet dialog.
|
||||||
|
*
|
||||||
|
* @param title The text color for the title of the dialog.
|
||||||
|
* @param description The text color for the description.
|
||||||
|
* @param background The background color of the dialog.
|
||||||
|
* @param confirmButton The color of the confirmation dialog.
|
||||||
|
* @param passwordBox The color of the box that contains the generated password.
|
||||||
|
* @param boxBorder The border color of the box that contains the generated password.
|
||||||
|
*/
|
||||||
|
data class PasswordGeneratorDialogColors(
|
||||||
|
val title: Color,
|
||||||
|
val description: Color,
|
||||||
|
val background: Color,
|
||||||
|
val confirmButton: Color,
|
||||||
|
val passwordBox: Color,
|
||||||
|
val boxBorder: Color,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [PasswordGeneratorDialogColors]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun default(
|
||||||
|
title: Color = MaterialTheme.colors.onBackground,
|
||||||
|
description: Color = MaterialTheme.colors.onBackground.copy(
|
||||||
|
alpha = ContentAlpha.medium,
|
||||||
|
),
|
||||||
|
background: Color = MaterialTheme.colors.primary,
|
||||||
|
confirmButton: Color = MaterialTheme.colors.primary,
|
||||||
|
passwordBox: Color = MaterialTheme.colors.primary,
|
||||||
|
boxBorder: Color = MaterialTheme.colors.primary,
|
||||||
|
) = PasswordGeneratorDialogColors(
|
||||||
|
title = title,
|
||||||
|
description = description,
|
||||||
|
background = background,
|
||||||
|
confirmButton = confirmButton,
|
||||||
|
passwordBox = passwordBox,
|
||||||
|
boxBorder = boxBorder,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a provider that provides the default [PasswordGeneratorDialogColors]
|
||||||
|
*/
|
||||||
|
fun defaultProvider() = PasswordGeneratorDialogColorsProvider { default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [PasswordGeneratorDialogColorsProvider] implementation can provide an [PasswordGeneratorDialogColors]
|
||||||
|
*/
|
||||||
|
fun interface PasswordGeneratorDialogColorsProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides [PasswordGeneratorDialogColors]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun provideColors(): PasswordGeneratorDialogColors
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
/* 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.login
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
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 androidx.compose.ui.platform.ViewCompositionStrategy
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import mozilla.components.concept.storage.Login
|
||||||
|
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.PromptDialogFragment
|
||||||
|
import mozilla.components.support.utils.ext.getParcelableCompat
|
||||||
|
|
||||||
|
private const val GENERATED_PASSWORD = "GENERATED_PASSWORD"
|
||||||
|
private const val URL = "URL"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a dialog for suggesting a strong generated password when creating a
|
||||||
|
* new account on a website
|
||||||
|
*/
|
||||||
|
internal class PasswordGeneratorDialogFragment : PromptDialogFragment() {
|
||||||
|
|
||||||
|
private val generatedPassword: String? by lazy {
|
||||||
|
safeArguments.getParcelableCompat(GENERATED_PASSWORD, String::class.java)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private val currentUrl: String? by lazy {
|
||||||
|
safeArguments.getParcelableCompat(URL, String::class.java)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private var onSavedGeneratedPassword: () -> Unit = {}
|
||||||
|
|
||||||
|
private var colorsProvider: PasswordGeneratorDialogColorsProvider =
|
||||||
|
PasswordGeneratorDialogColors.defaultProvider()
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return BottomSheetDialog(requireContext(), R.style.MozDialogStyle).apply {
|
||||||
|
setCancelable(true)
|
||||||
|
setOnShowListener {
|
||||||
|
val bottomSheet =
|
||||||
|
findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout
|
||||||
|
val behavior = BottomSheetBehavior.from(bottomSheet)
|
||||||
|
behavior.peekHeight = resources.displayMetrics.heightPixels
|
||||||
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?,
|
||||||
|
): View = ComposeView(requireContext()).apply {
|
||||||
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
|
setContent {
|
||||||
|
val colors = if (isSystemInDarkTheme()) darkColors() else lightColors()
|
||||||
|
MaterialTheme(colors) {
|
||||||
|
if (generatedPassword != null && currentUrl != null) {
|
||||||
|
PasswordGeneratorBottomSheet(
|
||||||
|
generatedStrongPassword = generatedPassword!!,
|
||||||
|
onUsePassword = {
|
||||||
|
onUsePassword(
|
||||||
|
generatedPassword = generatedPassword!!,
|
||||||
|
currentUrl = currentUrl!!,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onCancelDialog = { onCancelDialog() },
|
||||||
|
colors = colorsProvider.provideColors(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a generated password is being used when creating a new account on a website.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
internal fun onUsePassword(generatedPassword: String, currentUrl: String) {
|
||||||
|
val login = Login(
|
||||||
|
guid = "",
|
||||||
|
origin = currentUrl,
|
||||||
|
formActionOrigin = currentUrl,
|
||||||
|
httpRealm = currentUrl,
|
||||||
|
username = "",
|
||||||
|
password = generatedPassword,
|
||||||
|
)
|
||||||
|
feature?.onConfirm(sessionId, promptRequestUID, login)
|
||||||
|
dismiss()
|
||||||
|
|
||||||
|
onSavedGeneratedPassword.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
internal fun onCancelDialog() {
|
||||||
|
feature?.onCancel(sessionId, promptRequestUID)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder method for creating a [PasswordGeneratorDialogFragment]
|
||||||
|
* @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 generatedPassword The strong generated password.
|
||||||
|
* @param currentUrl The url for which the strong password is generated.
|
||||||
|
* @param colorsProvider The color provider for the password generator bottom sheet.
|
||||||
|
*/
|
||||||
|
fun newInstance(
|
||||||
|
sessionId: String,
|
||||||
|
promptRequestUID: String,
|
||||||
|
generatedPassword: String,
|
||||||
|
currentUrl: String,
|
||||||
|
onSavedGeneratedPassword: () -> Unit,
|
||||||
|
colorsProvider: PasswordGeneratorDialogColorsProvider,
|
||||||
|
) = PasswordGeneratorDialogFragment().apply {
|
||||||
|
arguments = (arguments ?: Bundle()).apply {
|
||||||
|
putString(KEY_SESSION_ID, sessionId)
|
||||||
|
putString(KEY_PROMPT_UID, promptRequestUID)
|
||||||
|
putString(GENERATED_PASSWORD, generatedPassword)
|
||||||
|
putString(URL, currentUrl)
|
||||||
|
}
|
||||||
|
this.onSavedGeneratedPassword = onSavedGeneratedPassword
|
||||||
|
this.colorsProvider = colorsProvider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
/* 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.login
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.width
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
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.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.content.withStyledAttributes
|
||||||
|
import mozilla.components.feature.prompts.R
|
||||||
|
import mozilla.components.feature.prompts.identitycredential.previews.DialogPreviewMaterialTheme
|
||||||
|
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
||||||
|
|
||||||
|
private val Context.primaryColor: Color
|
||||||
|
get() = Color(getColorFromAttr(android.R.attr.textColorPrimary))
|
||||||
|
|
||||||
|
private val Context.accentColor: Color
|
||||||
|
get() {
|
||||||
|
var color = Color.Unspecified
|
||||||
|
withStyledAttributes(null, R.styleable.LoginSelectBar) {
|
||||||
|
val resId = getResourceId(R.styleable.LoginSelectBar_mozacLoginSelectHeaderTextStyle, 0)
|
||||||
|
if (resId > 0) {
|
||||||
|
withStyledAttributes(resId, intArrayOf(android.R.attr.textColor)) {
|
||||||
|
color = Color(getColor(0, android.graphics.Color.BLACK))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colors used to theme [PasswordGeneratorPrompt]
|
||||||
|
*
|
||||||
|
* @param primary The color used for the text in [PasswordGeneratorPrompt].
|
||||||
|
* @param header The color used for the header in [PasswordGeneratorPrompt].
|
||||||
|
*/
|
||||||
|
data class PasswordGeneratorPromptColors(
|
||||||
|
val primary: Color,
|
||||||
|
val header: Color,
|
||||||
|
) {
|
||||||
|
constructor(context: Context) : this(
|
||||||
|
primary = context.primaryColor,
|
||||||
|
header = context.accentColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The password generator prompt
|
||||||
|
*
|
||||||
|
* @param onGeneratedPasswordPromptClick A callback invoked when the user clicks on the prompt.
|
||||||
|
* @param modifier The [Modifier] used for this view.
|
||||||
|
* @param colors The [PasswordGeneratorPromptColors] used for this view.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun PasswordGeneratorPrompt(
|
||||||
|
onGeneratedPasswordPromptClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
colors: PasswordGeneratorPromptColors,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.clickable { onGeneratedPasswordPromptClick() }
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(48.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.mozac_ic_login_24),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = colors.header,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.width(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.mozac_feature_prompts_suggest_strong_password_2),
|
||||||
|
color = colors.header,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
style = MaterialTheme.typography.subtitle2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PasswordGeneratorPromptPreview() {
|
||||||
|
DialogPreviewMaterialTheme {
|
||||||
|
PasswordGeneratorPrompt(
|
||||||
|
onGeneratedPasswordPromptClick = {},
|
||||||
|
colors = PasswordGeneratorPromptColors(
|
||||||
|
primary = MaterialTheme.colors.primary,
|
||||||
|
header = MaterialTheme.colors.onBackground,
|
||||||
|
),
|
||||||
|
modifier = Modifier.background(Color.White),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ package mozilla.components.feature.prompts.login
|
|||||||
import mozilla.components.browser.state.action.ContentAction
|
import mozilla.components.browser.state.action.ContentAction
|
||||||
import mozilla.components.browser.state.store.BrowserStore
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
import mozilla.components.concept.engine.prompt.PromptRequest
|
import mozilla.components.concept.engine.prompt.PromptRequest
|
||||||
import mozilla.components.concept.storage.Login
|
|
||||||
import mozilla.components.feature.prompts.concept.PasswordPromptView
|
import mozilla.components.feature.prompts.concept.PasswordPromptView
|
||||||
import mozilla.components.feature.prompts.consumePromptFrom
|
import mozilla.components.feature.prompts.consumePromptFrom
|
||||||
import mozilla.components.support.base.log.logger.Logger
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
@ -27,22 +26,14 @@ internal class StrongPasswordPromptViewListener(
|
|||||||
private var sessionId: String? = null,
|
private var sessionId: String? = null,
|
||||||
) : PasswordPromptView.Listener {
|
) : PasswordPromptView.Listener {
|
||||||
|
|
||||||
|
var onGeneratedPasswordPromptClick: () -> Unit = { }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
suggestStrongPasswordBar.listener = this
|
suggestStrongPasswordBar.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun handleSuggestStrongPasswordRequest(
|
internal fun handleSuggestStrongPasswordRequest() {
|
||||||
request: PromptRequest.SelectLoginPrompt,
|
suggestStrongPasswordBar.showPrompt()
|
||||||
currentUrl: String,
|
|
||||||
onSaveLoginWithStrongPassword: (url: String, password: String) -> Unit,
|
|
||||||
) {
|
|
||||||
request.generatedPassword?.let {
|
|
||||||
suggestStrongPasswordBar.showPrompt(
|
|
||||||
it,
|
|
||||||
currentUrl,
|
|
||||||
onSaveLoginWithStrongPassword,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
@ -71,22 +62,7 @@ internal class StrongPasswordPromptViewListener(
|
|||||||
suggestStrongPasswordBar.hidePrompt()
|
suggestStrongPasswordBar.hidePrompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUseGeneratedPassword(
|
override fun onGeneratedPasswordPromptClick() {
|
||||||
generatedPassword: String,
|
onGeneratedPasswordPromptClick.invoke()
|
||||||
url: String,
|
|
||||||
onSaveLoginWithStrongPassword: (url: String, password: String) -> Unit,
|
|
||||||
) {
|
|
||||||
browserStore.consumePromptFrom<PromptRequest.SelectLoginPrompt>(sessionId) {
|
|
||||||
// Create complete login entry: https://bugzilla.mozilla.org/show_bug.cgi?id=1869575
|
|
||||||
val createdLoginEntryWithPassword = Login(
|
|
||||||
guid = "",
|
|
||||||
origin = url,
|
|
||||||
username = "",
|
|
||||||
password = generatedPassword,
|
|
||||||
)
|
|
||||||
it.onConfirm(createdLoginEntryWithPassword)
|
|
||||||
}
|
|
||||||
onSaveLoginWithStrongPassword.invoke(url, generatedPassword)
|
|
||||||
suggestStrongPasswordBar.hidePrompt()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,10 @@
|
|||||||
package mozilla.components.feature.prompts.login
|
package mozilla.components.feature.prompts.login
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.compose.ui.platform.AbstractComposeView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.core.content.withStyledAttributes
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.TextViewCompat
|
|
||||||
import mozilla.components.feature.prompts.R
|
|
||||||
import mozilla.components.feature.prompts.concept.PasswordPromptView
|
import mozilla.components.feature.prompts.concept.PasswordPromptView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,88 +18,23 @@ class SuggestStrongPasswordBar @JvmOverloads constructor(
|
|||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0,
|
defStyleAttr: Int = 0,
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr), PasswordPromptView {
|
) : AbstractComposeView(context, attrs, defStyleAttr), PasswordPromptView {
|
||||||
|
|
||||||
private var headerTextStyle: Int? = null
|
|
||||||
private var suggestStrongPasswordView: View? = null
|
|
||||||
private var suggestStrongPasswordHeader: AppCompatTextView? = null
|
|
||||||
private var useStrongPasswordTitle: AppCompatTextView? = null
|
|
||||||
|
|
||||||
override var listener: PasswordPromptView.Listener? = null
|
override var listener: PasswordPromptView.Listener? = null
|
||||||
|
private val colors = PasswordGeneratorPromptColors(context)
|
||||||
|
|
||||||
init {
|
@Composable
|
||||||
context.withStyledAttributes(
|
override fun Content() {
|
||||||
attrs,
|
PasswordGeneratorPrompt(
|
||||||
R.styleable.LoginSelectBar,
|
onGeneratedPasswordPromptClick = { listener?.onGeneratedPasswordPromptClick() },
|
||||||
defStyleAttr,
|
colors = colors,
|
||||||
0,
|
)
|
||||||
) {
|
|
||||||
val textStyle =
|
|
||||||
getResourceId(R.styleable.LoginSelectBar_mozacLoginSelectHeaderTextStyle, 0)
|
|
||||||
if (textStyle > 0) {
|
|
||||||
headerTextStyle = textStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showPrompt(
|
override fun showPrompt() {
|
||||||
generatedPassword: String,
|
isVisible = true
|
||||||
url: String,
|
|
||||||
onSaveLoginWithStrongPassword: (url: String, password: String) -> Unit,
|
|
||||||
) {
|
|
||||||
if (suggestStrongPasswordView == null) {
|
|
||||||
suggestStrongPasswordView =
|
|
||||||
View.inflate(context, R.layout.mozac_feature_suggest_strong_password_view, this)
|
|
||||||
bindViews(generatedPassword, url, onSaveLoginWithStrongPassword)
|
|
||||||
}
|
|
||||||
suggestStrongPasswordView?.isVisible = true
|
|
||||||
useStrongPasswordTitle?.isVisible = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hidePrompt() {
|
override fun hidePrompt() {
|
||||||
isVisible = false
|
isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindViews(
|
|
||||||
strongPassword: String,
|
|
||||||
url: String,
|
|
||||||
onSaveLoginWithStrongPassword: (url: String, password: String) -> Unit,
|
|
||||||
) {
|
|
||||||
suggestStrongPasswordHeader =
|
|
||||||
findViewById<AppCompatTextView>(R.id.suggest_strong_password_header).apply {
|
|
||||||
headerTextStyle?.let {
|
|
||||||
TextViewCompat.setTextAppearance(this, it)
|
|
||||||
currentTextColor.let { textColor ->
|
|
||||||
TextViewCompat.setCompoundDrawableTintList(
|
|
||||||
this,
|
|
||||||
ColorStateList.valueOf(textColor),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setOnClickListener {
|
|
||||||
useStrongPasswordTitle?.let {
|
|
||||||
it.visibility = if (it.isVisible) {
|
|
||||||
GONE
|
|
||||||
} else {
|
|
||||||
VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useStrongPasswordTitle = findViewById<AppCompatTextView>(R.id.use_strong_password).apply {
|
|
||||||
text = context.getString(
|
|
||||||
R.string.mozac_feature_prompts_suggest_strong_password_message,
|
|
||||||
strongPassword,
|
|
||||||
)
|
|
||||||
visibility = GONE
|
|
||||||
setOnClickListener {
|
|
||||||
listener?.onUseGeneratedPassword(
|
|
||||||
generatedPassword = strongPassword,
|
|
||||||
url = url,
|
|
||||||
onSaveLoginWithStrongPassword = onSaveLoginWithStrongPassword,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/suggest_strong_password_header"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:contentDescription="@string/mozac_feature_prompts_suggest_strong_password_content_description"
|
|
||||||
android:drawablePadding="24dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="56dp"
|
|
||||||
android:text="@string/mozac_feature_prompts_suggest_strong_password"
|
|
||||||
android:textColor="?android:colorEdgeEffect"
|
|
||||||
android:textSize="16sp"
|
|
||||||
app:drawableStartCompat="@drawable/mozac_ic_login_24"
|
|
||||||
app:drawableTint="?android:colorEdgeEffect"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/use_strong_password"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:drawablePadding="24dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="0dp"
|
|
||||||
android:text="@string/mozac_feature_prompts_suggest_strong_password_message"
|
|
||||||
android:textColor="?android:textColorPrimary"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:drawableStartCompat="@drawable/mozac_ic_lock_24"
|
|
||||||
app:drawableTint="?android:textColorPrimary"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/suggest_strong_password_header" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</merge>
|
|
@ -118,10 +118,28 @@
|
|||||||
<!-- Content description for the suggest strong password prompt to allow users to fill a form with a suggested strong password -->
|
<!-- Content description for the suggest strong password prompt to allow users to fill a form with a suggested strong password -->
|
||||||
<string name="mozac_feature_prompts_suggest_strong_password_content_description">Suggest strong password</string>
|
<string name="mozac_feature_prompts_suggest_strong_password_content_description">Suggest strong password</string>
|
||||||
<!-- Header for the suggest strong password prompt to allow users to fill a form with a suggested strong password -->
|
<!-- Header for the suggest strong password prompt to allow users to fill a form with a suggested strong password -->
|
||||||
<string name="mozac_feature_prompts_suggest_strong_password">Suggest strong password</string>
|
<string name="mozac_feature_prompts_suggest_strong_password" moz:removedIn="128" tools:ignore="UnusedResources">Suggest strong password</string>
|
||||||
|
<!-- Header for the suggest strong password prompt to allow users to fill a form with a suggested strong password -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_2">Use strong password</string>
|
||||||
<!-- Title for using the suggest strong password confirmation dialog. %1$s will be replaced with the generated password -->
|
<!-- Title for using the suggest strong password confirmation dialog. %1$s will be replaced with the generated password -->
|
||||||
<string name="mozac_feature_prompts_suggest_strong_password_message">Use strong password: %1$s</string>
|
<string name="mozac_feature_prompts_suggest_strong_password_message">Use strong password: %1$s</string>
|
||||||
|
|
||||||
|
<!-- Title for using the suggest strong password confirmation dialog -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_title">Use strong password?</string>
|
||||||
|
<!-- Content description for the suggest strong password confirmation dialog -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_description">Protect your accounts by using a strong, randomly generated password. Get quick access to your passwords by saving it in your account.</string>
|
||||||
|
<!-- The actual suggested strong password. %1$s will be replaced with the generated password -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_generated_password">%1$s</string>
|
||||||
|
<!-- Pressing this will use the suggested strong password -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_use_password">Use password</string>
|
||||||
|
<!-- Pressing this will dismiss the suggested strong password dialog -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_dismiss">Not now</string>
|
||||||
|
<!-- Title for showing the suggest strong password saved confirmation snackbar -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_saved_snackbar_title">Password saved</string>
|
||||||
|
<!-- Title for showing the suggest strong password updated confirmation snackbar -->
|
||||||
|
<string name="mozac_feature_prompts_suggest_strong_password_updated_snackbar_title">Password updated</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Strings shown in a dialog that appear when users try to refresh a certain kind of webpages -->
|
<!-- Strings shown in a dialog that appear when users try to refresh a certain kind of webpages -->
|
||||||
<string name="mozac_feature_prompt_repost_title">Resend data to this site?</string>
|
<string name="mozac_feature_prompt_repost_title">Resend data to this site?</string>
|
||||||
<string name="mozac_feature_prompt_repost_message">Refreshing this page could duplicate recent actions, such as sending a payment or posting a comment twice.</string>
|
<string name="mozac_feature_prompt_repost_message">Refreshing this page could duplicate recent actions, such as sending a payment or posting a comment twice.</string>
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/* 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.login
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import mozilla.components.support.test.ext.appCompatContext
|
||||||
|
import mozilla.components.support.test.mock
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mockito.doReturn
|
||||||
|
import org.mockito.Mockito.spy
|
||||||
|
import org.mockito.MockitoAnnotations.openMocks
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class PasswordGeneratorDialogFragmentTest {
|
||||||
|
|
||||||
|
private lateinit var fragment: PasswordGeneratorDialogFragment
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
openMocks(this)
|
||||||
|
fragment = spy(
|
||||||
|
PasswordGeneratorDialogFragment.newInstance(
|
||||||
|
sessionId = "sessionId",
|
||||||
|
promptRequestUID = "uid",
|
||||||
|
generatedPassword = "StrongPassword123#",
|
||||||
|
currentUrl = "https://www.mozilla.org",
|
||||||
|
onSavedGeneratedPassword = {},
|
||||||
|
colorsProvider = mock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `build dialog`() {
|
||||||
|
doReturn(appCompatContext).`when`(fragment).requireContext()
|
||||||
|
val dialog = fragment.onCreateDialog(null)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
assertEquals(fragment.sessionId, "sessionId")
|
||||||
|
assertEquals(fragment.promptRequestUID, "uid")
|
||||||
|
|
||||||
|
assertEquals(dialog.isShowing, true)
|
||||||
|
}
|
||||||
|
}
|
@ -53,8 +53,7 @@ class StrongPasswordPromptViewListenerTest {
|
|||||||
private lateinit var suggestStrongPasswordPromptViewListener: StrongPasswordPromptViewListener
|
private lateinit var suggestStrongPasswordPromptViewListener: StrongPasswordPromptViewListener
|
||||||
private lateinit var suggestStrongPasswordBar: SuggestStrongPasswordBar
|
private lateinit var suggestStrongPasswordBar: SuggestStrongPasswordBar
|
||||||
|
|
||||||
private val onSaveLoginWithGeneratedPass: (String, String) -> Unit = mock()
|
private val onGeneratedPasswordPromptClick: () -> Unit = mock()
|
||||||
private val url = "https://www.mozilla.org"
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -79,25 +78,18 @@ class StrongPasswordPromptViewListenerTest {
|
|||||||
whenever(state.customTabs).thenReturn(listOf(customTab))
|
whenever(state.customTabs).thenReturn(listOf(customTab))
|
||||||
|
|
||||||
suggestStrongPasswordPromptViewListener = StrongPasswordPromptViewListener(store, suggestStrongPasswordBar)
|
suggestStrongPasswordPromptViewListener = StrongPasswordPromptViewListener(store, suggestStrongPasswordBar)
|
||||||
suggestStrongPasswordPromptViewListener.handleSuggestStrongPasswordRequest(request, url, onSaveLoginWithGeneratedPass)
|
suggestStrongPasswordPromptViewListener.onGeneratedPasswordPromptClick = onGeneratedPasswordPromptClick
|
||||||
Mockito.verify(suggestStrongPasswordBar).showPrompt(suggestedPassword, url, onSaveLoginWithGeneratedPass)
|
suggestStrongPasswordPromptViewListener.handleSuggestStrongPasswordRequest()
|
||||||
|
Mockito.verify(suggestStrongPasswordBar).showPrompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `StrongPasswordGenerator shows the suggest strong password bar on a selected tab`() {
|
fun `StrongPasswordGenerator shows the suggest strong password bar on a selected tab`() {
|
||||||
prepareSelectedSession(request)
|
prepareSelectedSession(request)
|
||||||
suggestStrongPasswordPromptViewListener = StrongPasswordPromptViewListener(store, suggestStrongPasswordBar)
|
suggestStrongPasswordPromptViewListener = StrongPasswordPromptViewListener(store, suggestStrongPasswordBar)
|
||||||
suggestStrongPasswordPromptViewListener.handleSuggestStrongPasswordRequest(request, url, onSaveLoginWithGeneratedPass)
|
suggestStrongPasswordPromptViewListener.onGeneratedPasswordPromptClick = onGeneratedPasswordPromptClick
|
||||||
Mockito.verify(suggestStrongPasswordBar).showPrompt(suggestedPassword, url, onSaveLoginWithGeneratedPass)
|
suggestStrongPasswordPromptViewListener.handleSuggestStrongPasswordRequest()
|
||||||
}
|
Mockito.verify(suggestStrongPasswordBar).showPrompt()
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `StrongPasswordGenerator invokes use the suggested password and hides view`() {
|
|
||||||
prepareSelectedSession(request)
|
|
||||||
suggestStrongPasswordPromptViewListener = StrongPasswordPromptViewListener(store, suggestStrongPasswordBar)
|
|
||||||
suggestStrongPasswordPromptViewListener.handleSuggestStrongPasswordRequest(request, "") { _, _ -> }
|
|
||||||
suggestStrongPasswordPromptViewListener.onUseGeneratedPassword(suggestedPassword, "") { _, _ -> }
|
|
||||||
Mockito.verify(suggestStrongPasswordBar).hidePrompt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -5,11 +5,8 @@
|
|||||||
package mozilla.components.feature.prompts.login
|
package mozilla.components.feature.prompts.login
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import mozilla.components.feature.prompts.R
|
|
||||||
import mozilla.components.feature.prompts.concept.PasswordPromptView
|
import mozilla.components.feature.prompts.concept.PasswordPromptView
|
||||||
import mozilla.components.support.test.ext.appCompatContext
|
import mozilla.components.support.test.ext.appCompatContext
|
||||||
import mozilla.components.support.test.mock
|
import mozilla.components.support.test.mock
|
||||||
@ -29,33 +26,14 @@ class SuggestStrongPasswordBarTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `listener is invoked when clicking use strong password option`() {
|
fun `show prompt updates visibility`() {
|
||||||
val bar = SuggestStrongPasswordBar(appCompatContext)
|
val bar = SuggestStrongPasswordBar(appCompatContext)
|
||||||
val listener: PasswordPromptView.Listener = mock()
|
val listener: PasswordPromptView.Listener = mock()
|
||||||
val suggestedPassword = "generatedPassword123#"
|
|
||||||
val url = "https://wwww.abc.com"
|
|
||||||
val onSaveLoginWithGeneratedPass: (String, String) -> Unit = mock()
|
|
||||||
Assert.assertNull(bar.listener)
|
Assert.assertNull(bar.listener)
|
||||||
bar.listener = listener
|
bar.listener = listener
|
||||||
bar.showPrompt(suggestedPassword, url, onSaveLoginWithGeneratedPass)
|
Assert.assertNotNull(bar.listener)
|
||||||
bar.findViewById<AppCompatTextView>(R.id.use_strong_password).performClick()
|
|
||||||
Mockito.verify(listener)
|
|
||||||
.onUseGeneratedPassword(suggestedPassword, url, onSaveLoginWithGeneratedPass)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
bar.showPrompt()
|
||||||
fun `view is expanded when clicking header`() {
|
Assert.assertTrue(bar.isVisible)
|
||||||
val bar = SuggestStrongPasswordBar(appCompatContext)
|
|
||||||
val suggestedPassword = "generatedPassword123#"
|
|
||||||
|
|
||||||
bar.showPrompt(suggestedPassword, "") { _, _ -> }
|
|
||||||
|
|
||||||
bar.findViewById<AppCompatTextView>(R.id.suggest_strong_password_header).performClick()
|
|
||||||
// Expanded
|
|
||||||
Assert.assertTrue(bar.findViewById<RecyclerView>(R.id.use_strong_password).isVisible)
|
|
||||||
|
|
||||||
bar.findViewById<AppCompatTextView>(R.id.suggest_strong_password_header).performClick()
|
|
||||||
// Hidden
|
|
||||||
Assert.assertFalse(bar.findViewById<RecyclerView>(R.id.use_strong_password).isVisible)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,8 @@ import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog
|
|||||||
import mozilla.components.feature.prompts.identitycredential.DialogColors
|
import mozilla.components.feature.prompts.identitycredential.DialogColors
|
||||||
import mozilla.components.feature.prompts.identitycredential.DialogColorsProvider
|
import mozilla.components.feature.prompts.identitycredential.DialogColorsProvider
|
||||||
import mozilla.components.feature.prompts.login.LoginDelegate
|
import mozilla.components.feature.prompts.login.LoginDelegate
|
||||||
|
import mozilla.components.feature.prompts.login.PasswordGeneratorDialogColors
|
||||||
|
import mozilla.components.feature.prompts.login.PasswordGeneratorDialogColorsProvider
|
||||||
import mozilla.components.feature.prompts.login.SuggestStrongPasswordDelegate
|
import mozilla.components.feature.prompts.login.SuggestStrongPasswordDelegate
|
||||||
import mozilla.components.feature.prompts.share.ShareDelegate
|
import mozilla.components.feature.prompts.share.ShareDelegate
|
||||||
import mozilla.components.feature.readerview.ReaderViewFeature
|
import mozilla.components.feature.readerview.ReaderViewFeature
|
||||||
@ -684,6 +686,17 @@ abstract class BaseBrowserFragment :
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val passwordGeneratorColorsProvider = PasswordGeneratorDialogColorsProvider {
|
||||||
|
PasswordGeneratorDialogColors(
|
||||||
|
title = ThemeManager.resolveAttributeColor(attribute = R.attr.textPrimary),
|
||||||
|
description = ThemeManager.resolveAttributeColor(attribute = R.attr.textSecondary),
|
||||||
|
background = ThemeManager.resolveAttributeColor(attribute = R.attr.layer1),
|
||||||
|
confirmButton = ThemeManager.resolveAttributeColor(attribute = R.attr.actionPrimary),
|
||||||
|
passwordBox = ThemeManager.resolveAttributeColor(attribute = R.attr.layer2),
|
||||||
|
boxBorder = ThemeManager.resolveAttributeColor(attribute = R.attr.textDisabled),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val bottomToolbarHeight = context.settings().getBottomToolbarHeight()
|
val bottomToolbarHeight = context.settings().getBottomToolbarHeight()
|
||||||
|
|
||||||
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
|
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
|
||||||
@ -829,6 +842,10 @@ abstract class BaseBrowserFragment :
|
|||||||
get() = binding.suggestStrongPasswordBar
|
get() = binding.suggestStrongPasswordBar
|
||||||
},
|
},
|
||||||
isSuggestStrongPasswordEnabled = context.settings().enableSuggestStrongPassword,
|
isSuggestStrongPasswordEnabled = context.settings().enableSuggestStrongPassword,
|
||||||
|
shouldAutomaticallyShowSuggestedPassword = { context.settings().isFirstTimeEngagingWithSignup },
|
||||||
|
onFirstTimeEngagedWithSignup = {
|
||||||
|
context.settings().isFirstTimeEngagingWithSignup = false
|
||||||
|
},
|
||||||
onSaveLoginWithStrongPassword = { url, password ->
|
onSaveLoginWithStrongPassword = { url, password ->
|
||||||
handleOnSaveLoginWithGeneratedStrongPassword(
|
handleOnSaveLoginWithGeneratedStrongPassword(
|
||||||
passwordsStorage = context.components.core.passwordsStorage,
|
passwordsStorage = context.components.core.passwordsStorage,
|
||||||
@ -836,6 +853,10 @@ abstract class BaseBrowserFragment :
|
|||||||
password = password,
|
password = password,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onSavedGeneratedPassword = {
|
||||||
|
showSnackbarAfterUsingTheGeneratedPassword()
|
||||||
|
},
|
||||||
|
passwordGeneratorColorsProvider = passwordGeneratorColorsProvider,
|
||||||
creditCardDelegate = object : CreditCardDelegate {
|
creditCardDelegate = object : CreditCardDelegate {
|
||||||
override val creditCardPickerView
|
override val creditCardPickerView
|
||||||
get() = binding.creditCardSelectBar
|
get() = binding.creditCardSelectBar
|
||||||
@ -1071,6 +1092,17 @@ abstract class BaseBrowserFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a [Snackbar] when credentials are saved using the generated password.
|
||||||
|
*/
|
||||||
|
private fun showSnackbarAfterUsingTheGeneratedPassword() {
|
||||||
|
FenixSnackbarDelegate(binding.dynamicSnackbarContainer).show(
|
||||||
|
snackBarParentView = binding.dynamicSnackbarContainer,
|
||||||
|
text = R.string.mozac_feature_prompts_suggest_strong_password_saved_snackbar_title,
|
||||||
|
duration = Snackbar.LENGTH_LONG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a biometric prompt and fallback to prompting for the password.
|
* Shows a biometric prompt and fallback to prompting for the password.
|
||||||
*/
|
*/
|
||||||
|
@ -1952,6 +1952,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
|
|||||||
featureFlag = FeatureFlags.suggestStrongPassword,
|
featureFlag = FeatureFlags.suggestStrongPassword,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates first time engaging with signup
|
||||||
|
*/
|
||||||
|
var isFirstTimeEngagingWithSignup: Boolean by booleanPreference(
|
||||||
|
appContext.getPreferenceKey(R.string.pref_key_first_time_engage_with_signup),
|
||||||
|
default = true,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the user has chosen to show sponsored search suggestions in the awesomebar.
|
* Indicates if the user has chosen to show sponsored search suggestions in the awesomebar.
|
||||||
* The default value is computed lazily, and based on whether Firefox Suggest is enabled.
|
* The default value is computed lazily, and based on whether Firefox Suggest is enabled.
|
||||||
|
@ -108,6 +108,8 @@
|
|||||||
<string name="pref_key_sync_credit_cards" translatable="false">pref_key_sync_credit_cards</string>
|
<string name="pref_key_sync_credit_cards" translatable="false">pref_key_sync_credit_cards</string>
|
||||||
<!-- Key for Address sync preference in the account settings fragment -->
|
<!-- Key for Address sync preference in the account settings fragment -->
|
||||||
<string name="pref_key_sync_address" translatable="false">pref_key_sync_address</string>
|
<string name="pref_key_sync_address" translatable="false">pref_key_sync_address</string>
|
||||||
|
<!-- Key for engaging first time with signup -->
|
||||||
|
<string name="pref_key_first_time_engage_with_signup" translatable="false">pref_key_first_time_engage_with_signup</string>
|
||||||
|
|
||||||
<!-- Search Settings -->
|
<!-- Search Settings -->
|
||||||
<string name="pref_key_search_engine_list" translatable="false">pref_key_search_engine_list</string>
|
<string name="pref_key_search_engine_list" translatable="false">pref_key_search_engine_list</string>
|
||||||
|
@ -690,6 +690,10 @@ export const GeckoViewAutocomplete = {
|
|||||||
|
|
||||||
debug`delegateSelection - filling form`;
|
debug`delegateSelection - filling form`;
|
||||||
|
|
||||||
|
if (selectedOption.hint & SelectOption.Hint.GENERATED) {
|
||||||
|
this.onLoginSave(selectedLogin);
|
||||||
|
}
|
||||||
|
|
||||||
const actor =
|
const actor =
|
||||||
browsingContext.currentWindowGlobal.getActor("LoginManager");
|
browsingContext.currentWindowGlobal.getActor("LoginManager");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user