From ec029fd6be005ad533c22c6cdf19d9165f72e4c5 Mon Sep 17 00:00:00 2001 From: Samuele Domenico Ruffino Date: Thu, 3 Jul 2025 14:14:36 +0200 Subject: [PATCH 1/5] feat(designsystem): enhance PasswordDialog validation with detailed error messages - Add formatted length validation error with min/max values - Add confirm password mismatch error message - Add custom validation error with optional hint parameter - Use string interpolation for dynamic error messages - Maintain backward compatibility with existing usage patterns All backup screens now show more specific validation feedback to users. --- .../designsystem/dialog/PasswordDialog.kt | 43 +++++++++++++++++-- .../main/java/com/twofasapp/locale/Strings.kt | 6 ++- core/locale/src/main/res/values/strings.xml | 5 +++ .../backup/ui/export/BackupExportScreen.kt | 3 +- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/dialog/PasswordDialog.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/dialog/PasswordDialog.kt index 24e6773e..1fe0aaeb 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/dialog/PasswordDialog.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/dialog/PasswordDialog.kt @@ -22,6 +22,13 @@ import com.twofasapp.designsystem.common.TwOutlinedTextFieldPassword import com.twofasapp.locale.TwLocale import kotlinx.coroutines.android.awaitFrame +sealed class ValidationState { + object Valid : ValidationState() + data class LengthError(val message: String) : ValidationState() + data class ConfirmError(val message: String) : ValidationState() + data class ValidationError(val message: String) : ValidationState() +} + @Composable fun PasswordDialog( onDismissRequest: () -> Unit, @@ -36,6 +43,7 @@ fun PasswordDialog( onPositive: ((String) -> Unit)? = null, onNegative: (() -> Unit)? = null, validation: ((String) -> Boolean)? = null, + validationHint: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, minLength: Int = 3, maxLength: Int = Int.MAX_VALUE, @@ -45,6 +53,24 @@ fun PasswordDialog( var password by remember { mutableStateOf("") } var passwordConfirm by remember { mutableStateOf("") } + val validationState by remember { + derivedStateOf { + when { + password.trim().length !in minLength..maxLength -> + ValidationState.LengthError( + TwLocale.strings.backupPasswordLengthError.format(minLength, maxLength) + ) + confirmRequired && password != passwordConfirm -> + ValidationState.ConfirmError(TwLocale.strings.backupPasswordConfirmError) + validation != null && !validation.invoke(password) -> + ValidationState.ValidationError( + validationHint ?: TwLocale.strings.backupPasswordValidationError + ) + else -> ValidationState.Valid + } + } + } + val positiveEnabledState by remember { derivedStateOf { when { @@ -79,11 +105,16 @@ fun PasswordDialog( .padding(horizontal = DialogPadding) .focusRequester(focusRequester), labelText = TwLocale.strings.password, - isError = error.isNullOrBlank().not(), + isError = validationState !is ValidationState.Valid, keyboardOptions = keyboardOptions, maxLines = 1, enabled = enabled, - supportingText = if (error.isNullOrBlank()) null else error, + supportingText = when (validationState) { + is ValidationState.LengthError -> validationState.message + is ValidationState.ValidationError -> validationState.message + ValidationState.Valid -> null + else -> null + }, ) if (confirmRequired) { @@ -94,10 +125,14 @@ fun PasswordDialog( modifier = Modifier .padding(horizontal = DialogPadding), labelText = TwLocale.strings.passwordConfirm, - isError = error.isNullOrBlank().not(), + isError = validationState is ValidationState.ConfirmError, keyboardOptions = keyboardOptions, maxLines = 1, enabled = enabled, + supportingText = when (validationState) { + is ValidationState.ConfirmError -> validationState.message + else -> null + }, ) } @@ -119,4 +154,4 @@ private fun Preview() { title = "Password", body = TwLocale.strings.placeholderMedium, ) -} \ No newline at end of file +} diff --git a/core/locale/src/main/java/com/twofasapp/locale/Strings.kt b/core/locale/src/main/java/com/twofasapp/locale/Strings.kt index 82203495..49519917 100644 --- a/core/locale/src/main/java/com/twofasapp/locale/Strings.kt +++ b/core/locale/src/main/java/com/twofasapp/locale/Strings.kt @@ -291,6 +291,10 @@ class Strings(c: Context) { val backupSetPasswordDescription = c.getString(R.string.backup__set_password_title) val backupEnterPassword = c.getString(R.string.backup__enter_password_dialog_title) val backupEnterPasswordDescription = c.getString(R.string.backup__enter_password_title) + val backupPasswordLengthError = c.getString(R.string.backup__password_length_error) + val backupPasswordConfirmError = c.getString(R.string.backup__password_confirm_error) + val backupPasswordValidationError = c.getString(R.string.backup__password_validation_error) + val backupPasswordValidationHint = c.getString(R.string.backup__password_validation_hint) val backupShareError = c.getString(R.string.backup__share_result_failure) val backupDownloadError = c.getString(R.string.commons__unknown_error) val backupDownloadSuccess = c.getString(R.string.backup__export_result_success) @@ -354,4 +358,4 @@ class Strings(c: Context) { val widgetSelectMsg = c.getString(R.string.widgets_select_msg) val widgetNoServices = c.getString(R.string.widgets_empty_msg) -} \ No newline at end of file +} diff --git a/core/locale/src/main/res/values/strings.xml b/core/locale/src/main/res/values/strings.xml index b9d61c32..55cacb8f 100644 --- a/core/locale/src/main/res/values/strings.xml +++ b/core/locale/src/main/res/values/strings.xml @@ -361,6 +361,11 @@ To increase the protection of your backup file, please set the password Type in a password for this backup file to proceed with the import process Set a password for this backup file + + Password must be between %1$d and %2$d characters + Passwords don\'t match + Password contains invalid characters + Password can only contain letters, numbers, and these symbols: _/!#$%&+*~@?=^.,(){}[]<>|- Incorrect Password %d new services diff --git a/feature/backup/src/main/java/com/twofasapp/feature/backup/ui/export/BackupExportScreen.kt b/feature/backup/src/main/java/com/twofasapp/feature/backup/ui/export/BackupExportScreen.kt index 113951a0..2976e59a 100644 --- a/feature/backup/src/main/java/com/twofasapp/feature/backup/ui/export/BackupExportScreen.kt +++ b/feature/backup/src/main/java/com/twofasapp/feature/backup/ui/export/BackupExportScreen.kt @@ -204,6 +204,7 @@ private fun ScreenContent( title = TwLocale.strings.backupSetPassword, body = TwLocale.strings.backupSetPasswordDescription, validation = { text -> ExportPasswordRegex.matches(text) }, + validationHint = TwLocale.strings.backupPasswordValidationHint, onPositive = { onPasswordConfirm(it) @@ -263,4 +264,4 @@ private fun Preview() { ScreenContent( uiState = BackupExportUiState(), ) -} \ No newline at end of file +} From aa67709dd29becfc714a7868283dce8708cc91bc Mon Sep 17 00:00:00 2001 From: Samuele Domenico Ruffino Date: Thu, 3 Jul 2025 14:42:54 +0200 Subject: [PATCH 2/5] feat(locale): add Italian translations for password validation messages --- core/locale/src/main/res/values-it-rIT/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/locale/src/main/res/values-it-rIT/strings.xml b/core/locale/src/main/res/values-it-rIT/strings.xml index 260105d8..3ef70904 100644 --- a/core/locale/src/main/res/values-it-rIT/strings.xml +++ b/core/locale/src/main/res/values-it-rIT/strings.xml @@ -361,6 +361,11 @@ Per aumentare la protezione del tuo file di backup imposta la password Digita la password per questo file di backup per procedere con il processo di importazione Imposta una password per questo file di backup + + La password deve essere compresa tra %1$d e %2$d caratteri + Le password non corrispondono + La password contiene caratteri non validi + La password può contenere solo lettere, numeri e i simboli: _/!#$%&+*~@?=^.,(){}[]<>|- Password errata %d nuovi servizi From a8bcde67ea18709441dcc65566e5bcfc20eb2229 Mon Sep 17 00:00:00 2001 From: Samuele Domenico Ruffino Date: Thu, 3 Jul 2025 14:43:25 +0200 Subject: [PATCH 3/5] feat(locale): add French translations for password validation messages --- core/locale/src/main/res/values-fr-rFR/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/locale/src/main/res/values-fr-rFR/strings.xml b/core/locale/src/main/res/values-fr-rFR/strings.xml index 9a23c053..b43f39fb 100644 --- a/core/locale/src/main/res/values-fr-rFR/strings.xml +++ b/core/locale/src/main/res/values-fr-rFR/strings.xml @@ -361,6 +361,11 @@ Pour renforcer la protection de votre fichier de sauvegarde, veuillez définir un mot de passe. Saisissez un mot de passe pour ce fichier de sauvegarde afin de poursuivre le processus d\'importation Définir un mot de passe pour le fichier de sauvegarde + + Le mot de passe doit contenir entre %1$d et %2$d caractères + Les mots de passe ne correspondent pas + Le mot de passe contient des caractères non valides + Le mot de passe ne peut contenir que des lettres, des chiffres et ces symboles : _/!#$%&+*~@?=^.,(){}[]<>|- Mot de passe incorrect %d nouveaux services From 7efa027cfe0defb3d69aedb9cbbff64526b9e9a4 Mon Sep 17 00:00:00 2001 From: Samuele Domenico Ruffino Date: Thu, 3 Jul 2025 14:43:55 +0200 Subject: [PATCH 4/5] feat(locale): add Spanish translations for password validation messages --- core/locale/src/main/res/values-es-rES/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/locale/src/main/res/values-es-rES/strings.xml b/core/locale/src/main/res/values-es-rES/strings.xml index aa0e9bab..ca448213 100644 --- a/core/locale/src/main/res/values-es-rES/strings.xml +++ b/core/locale/src/main/res/values-es-rES/strings.xml @@ -361,6 +361,11 @@ Para aumentar la protección de su archivo de copia de seguridad, establezca una contraseña. Escriba una contraseña para este archivo de copia de seguridad para continuar con el proceso de importación Establecer una contraseña para este archivo de copia de seguridad + + La contraseña debe tener entre %1$d y %2$d caracteres + Las contraseñas no coinciden + La contraseña contiene caracteres no válidos + La contraseña solo puede contener letras, números y estos símbolos: _/!#$%&+*~@?=^.,(){}[]<>|- Contraseña incorrecta %d nuevos servicios From 8b84ff0a42a9681cc344287bfbe0ff7b1260f22b Mon Sep 17 00:00:00 2001 From: Samuele Domenico Ruffino Date: Thu, 3 Jul 2025 14:44:30 +0200 Subject: [PATCH 5/5] feat(locale): add German translations for password validation messages --- core/locale/src/main/res/values-de-rDE/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/locale/src/main/res/values-de-rDE/strings.xml b/core/locale/src/main/res/values-de-rDE/strings.xml index 22c0c1c4..d087b122 100644 --- a/core/locale/src/main/res/values-de-rDE/strings.xml +++ b/core/locale/src/main/res/values-de-rDE/strings.xml @@ -361,6 +361,11 @@ Um den Schutz deiner Sicherungsdatei zu erhöhen, lege bitte das Passwort fest Gib ein Passwort für diese Sicherungsdatei ein, um mit dem Importvorgang fortzufahren Ein Passwort für diese Sicherungsdatei festlegen + + Das Passwort muss zwischen %1$d und %2$d Zeichen lang sein + Die Passwörter stimmen nicht überein + Das Passwort enthält ungültige Zeichen + Das Passwort darf nur Buchstaben, Zahlen und diese Symbole enthalten: _/!#$%&+*~@?=^.,(){}[]<>|- Falsches Passwort %d neue Dienste