diff --git a/lib/fido/views/locked_page.dart b/lib/fido/views/locked_page.dart index 85e9c06a..204b5b9d 100755 --- a/lib/fido/views/locked_page.dart +++ b/lib/fido/views/locked_page.dart @@ -166,36 +166,23 @@ class _PinEntryFormState extends ConsumerState<_PinEntryForm> { obscureText: _isObscure, autofillHints: const [AutofillHints.password], controller: _pinController, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_pin, helperText: '', // Prevents dialog resizing errorText: _pinIsWrong ? _getErrorText() : null, errorMaxLines: 3, prefixIcon: const Icon(Icons.pin_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon( - _isObscure ? Icons.visibility : Icons.visibility_off, - color: !_pinIsWrong - ? IconTheme.of(context).color - : null), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin, - ), - if (_pinIsWrong) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Icons.visibility : Icons.visibility_off, + color: !_pinIsWrong ? IconTheme.of(context).color : null), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin, ), ), onChanged: (value) { diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 34be1744..f73ae139 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -363,7 +363,7 @@ class _OathAddAccountPageState extends ConsumerState { limitBytesLength(issuerRemaining), ], buildCounter: buildByteCounterFor(issuerText), - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_issuer_optional, helperText: @@ -395,7 +395,7 @@ class _OathAddAccountPageState extends ConsumerState { maxLength: nameMaxLength, buildCounter: buildByteCounterFor(nameText), inputFormatters: [limitBytesLength(nameRemaining)], - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_account_name, helperText: '', @@ -429,45 +429,33 @@ class _OathAddAccountPageState extends ConsumerState { // would hint to use saved passwords for this field autofillHints: isAndroid ? [] : const [AutofillHints.password], - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_secret_key, - errorText: _validateSecret && !secretLengthValid - ? l10n.s_invalid_length - : _validateSecret && !secretFormatValid - ? l10n.l_invalid_format_allowed_chars( - Format.base32.allowedCharacters) - : null, - prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon( - _isObscure - ? Icons.visibility - : Icons.visibility_off, - color: !_validateSecret - ? IconTheme.of(context).color - : null), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: _isObscure - ? l10n.s_show_secret_key - : l10n.s_hide_secret_key, - ), - if (_validateSecret) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_secret_key, + errorText: _validateSecret && !secretLengthValid + ? l10n.s_invalid_length + : _validateSecret && !secretFormatValid + ? l10n.l_invalid_format_allowed_chars( + Format.base32.allowedCharacters) + : null, + prefixIcon: const Icon(Icons.key_outlined), + suffixIcon: IconButton( + icon: Icon( + _isObscure + ? Icons.visibility + : Icons.visibility_off, + color: !_validateSecret + ? IconTheme.of(context).color + : null), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure + ? l10n.s_show_secret_key + : l10n.s_hide_secret_key, + )), readOnly: _dataLoaded, textInputAction: TextInputAction.done, onChanged: (value) { diff --git a/lib/oath/views/manage_password_dialog.dart b/lib/oath/views/manage_password_dialog.dart index 1ac00dae..d9d568b8 100755 --- a/lib/oath/views/manage_password_dialog.dart +++ b/lib/oath/views/manage_password_dialog.dart @@ -91,35 +91,25 @@ class _ManagePasswordDialogState extends ConsumerState { obscureText: _isObscureCurrent, autofillHints: const [AutofillHints.password], key: keys.currentPasswordField, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_current_password, - errorText: _currentIsWrong ? l10n.s_wrong_password : null, - errorMaxLines: 3, - prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscureCurrent - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscureCurrent = !_isObscureCurrent; - }); - }, - tooltip: _isObscureCurrent - ? l10n.s_show_password - : l10n.s_hide_password), - if (_currentIsWrong) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - )), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_current_password, + errorText: _currentIsWrong ? l10n.s_wrong_password : null, + errorMaxLines: 3, + prefixIcon: const Icon(Icons.password_outlined), + suffixIcon: IconButton( + icon: Icon(_isObscureCurrent + ? Icons.visibility + : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscureCurrent = !_isObscureCurrent; + }); + }, + tooltip: _isObscureCurrent + ? l10n.s_show_password + : l10n.s_hide_password), + ), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { @@ -173,26 +163,22 @@ class _ManagePasswordDialogState extends ConsumerState { autofocus: !widget.state.hasKey, obscureText: _isObscureNew, autofillHints: const [AutofillHints.newPassword], - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_new_password, prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscureNew - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscureNew = !_isObscureNew; - }); - }, - tooltip: _isObscureNew - ? l10n.s_show_password - : l10n.s_hide_password), - ]), + suffixIcon: IconButton( + icon: Icon(_isObscureNew + ? Icons.visibility + : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscureNew = !_isObscureNew; + }); + }, + tooltip: _isObscureNew + ? l10n.s_show_password + : l10n.s_hide_password), enabled: !widget.state.hasKey || _currentPassword.isNotEmpty, ), textInputAction: TextInputAction.next, @@ -211,27 +197,22 @@ class _ManagePasswordDialogState extends ConsumerState { key: keys.confirmPasswordField, obscureText: _isObscureConfirm, autofillHints: const [AutofillHints.newPassword], - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_confirm_password, prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscureConfirm - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscureConfirm = !_isObscureConfirm; - }); - }, - tooltip: _isObscureConfirm - ? l10n.s_show_password - : l10n.s_hide_password) - ], - ), + suffixIcon: IconButton( + icon: Icon(_isObscureConfirm + ? Icons.visibility + : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscureConfirm = !_isObscureConfirm; + }); + }, + tooltip: _isObscureConfirm + ? l10n.s_show_password + : l10n.s_hide_password), enabled: (!widget.state.hasKey || _currentPassword.isNotEmpty) && _newPassword.isNotEmpty, diff --git a/lib/oath/views/unlock_form.dart b/lib/oath/views/unlock_form.dart index f3cdde21..4bfdb69e 100755 --- a/lib/oath/views/unlock_form.dart +++ b/lib/oath/views/unlock_form.dart @@ -79,39 +79,26 @@ class _UnlockFormState extends ConsumerState { autofocus: true, obscureText: _isObscure, autofillHints: const [AutofillHints.password], - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_password, errorText: _passwordIsWrong ? l10n.s_wrong_password : null, helperText: '', // Prevents resizing when errorText shown prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon( - _isObscure - ? Icons.visibility - : Icons.visibility_off, - color: !_passwordIsWrong - ? IconTheme.of(context).color - : null), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: _isObscure - ? l10n.s_show_password - : l10n.s_hide_password, - ), - if (_passwordIsWrong) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Icons.visibility : Icons.visibility_off, + color: !_passwordIsWrong + ? IconTheme.of(context).color + : null), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure + ? l10n.s_show_password + : l10n.s_hide_password, ), ), onChanged: (_) => setState(() { diff --git a/lib/otp/views/configure_chalresp_dialog.dart b/lib/otp/views/configure_chalresp_dialog.dart index 47fdebc5..2e5421c4 100644 --- a/lib/otp/views/configure_chalresp_dialog.dart +++ b/lib/otp/views/configure_chalresp_dialog.dart @@ -129,46 +129,34 @@ class _ConfigureChalrespDialogState controller: _secretController, autofillHints: isAndroid ? [] : const [AutofillHints.password], maxLength: secretMaxLength, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_secret_key, - errorText: _validateSecret && !secretLengthValid - ? l10n.s_invalid_length - : _validateSecret && !secretFormatValid - ? l10n.l_invalid_format_allowed_chars( - Format.hex.allowedCharacters) - : null, - prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: const Icon(Icons.refresh), - onPressed: () { + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_secret_key, + errorText: _validateSecret && !secretLengthValid + ? l10n.s_invalid_length + : _validateSecret && !secretFormatValid + ? l10n.l_invalid_format_allowed_chars( + Format.hex.allowedCharacters) + : null, + prefixIcon: const Icon(Icons.key_outlined), + suffixIcon: IconButton( + icon: const Icon(Icons.refresh), + onPressed: () { + setState(() { + final random = Random.secure(); + final key = List.generate( + 20, + (_) => random + .nextInt(256) + .toRadixString(16) + .padLeft(2, '0')).join(); setState(() { - final random = Random.secure(); - final key = List.generate( - 20, - (_) => random - .nextInt(256) - .toRadixString(16) - .padLeft(2, '0')).join(); - setState(() { - _secretController.text = key; - }); + _secretController.text = key; }); - }, - tooltip: l10n.s_generate_random, - ), - if (_validateSecret) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + }); + }, + tooltip: l10n.s_generate_random, + )), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { diff --git a/lib/otp/views/configure_hotp_dialog.dart b/lib/otp/views/configure_hotp_dialog.dart index feca8a7a..8aea8eb2 100644 --- a/lib/otp/views/configure_hotp_dialog.dart +++ b/lib/otp/views/configure_hotp_dialog.dart @@ -126,44 +126,32 @@ class _ConfigureHotpDialogState extends ConsumerState { controller: _secretController, obscureText: _isObscure, autofillHints: isAndroid ? [] : const [AutofillHints.password], - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_secret_key, - helperText: '', // Prevents resizing when errorText shown - errorText: _validateSecret && !secretLengthValid - ? l10n.s_invalid_length - : _validateSecret && !secretFormatValid - ? l10n.l_invalid_format_allowed_chars( - Format.base32.allowedCharacters) - : null, - prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon( - _isObscure ? Icons.visibility : Icons.visibility_off, - color: !_validateSecret - ? IconTheme.of(context).color - : null), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: _isObscure - ? l10n.s_show_secret_key - : l10n.s_hide_secret_key, - ), - if (_validateSecret) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_secret_key, + helperText: '', // Prevents resizing when errorText shown + errorText: _validateSecret && !secretLengthValid + ? l10n.s_invalid_length + : _validateSecret && !secretFormatValid + ? l10n.l_invalid_format_allowed_chars( + Format.base32.allowedCharacters) + : null, + prefixIcon: const Icon(Icons.key_outlined), + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Icons.visibility : Icons.visibility_off, + color: !_validateSecret + ? IconTheme.of(context).color + : null), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure + ? l10n.s_show_secret_key + : l10n.s_hide_secret_key, + )), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { diff --git a/lib/otp/views/configure_static_dialog.dart b/lib/otp/views/configure_static_dialog.dart index 1b4020d2..6395c76d 100644 --- a/lib/otp/views/configure_static_dialog.dart +++ b/lib/otp/views/configure_static_dialog.dart @@ -149,41 +149,29 @@ class _ConfigureStaticDialogState extends ConsumerState { controller: _passwordController, autofillHints: isAndroid ? [] : const [AutofillHints.password], maxLength: passwordMaxLength, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_password, - errorText: _validatePassword && !passwordLengthValid - ? l10n.s_invalid_length - : _validatePassword && !passwordFormatValid - ? l10n.l_invalid_keyboard_character - : null, - prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - tooltip: l10n.s_generate_passowrd, - icon: const Icon(Icons.refresh), - onPressed: () async { - final password = await ref - .read(otpStateProvider(widget.devicePath).notifier) - .generateStaticPassword( - passwordMaxLength, _keyboardLayout); - setState(() { - _validatePassword = false; - _passwordController.text = password; - }); - }, - ), - if (_validatePassword) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_password, + errorText: _validatePassword && !passwordLengthValid + ? l10n.s_invalid_length + : _validatePassword && !passwordFormatValid + ? l10n.l_invalid_keyboard_character + : null, + prefixIcon: const Icon(Icons.key_outlined), + suffixIcon: IconButton( + tooltip: l10n.s_generate_passowrd, + icon: const Icon(Icons.refresh), + onPressed: () async { + final password = await ref + .read(otpStateProvider(widget.devicePath).notifier) + .generateStaticPassword( + passwordMaxLength, _keyboardLayout); + setState(() { + _validatePassword = false; + _passwordController.text = password; + }); + }, + )), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { diff --git a/lib/otp/views/configure_yubiotp_dialog.dart b/lib/otp/views/configure_yubiotp_dialog.dart index c5969fd6..4c089323 100644 --- a/lib/otp/views/configure_yubiotp_dialog.dart +++ b/lib/otp/views/configure_yubiotp_dialog.dart @@ -205,41 +205,29 @@ class _ConfigureYubiOtpDialogState controller: _publicIdController, autofillHints: isAndroid ? [] : const [AutofillHints.password], maxLength: publicIdLength, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_public_id, - errorText: _validatePublicIdFormat && !publicIdFormatValid - ? l10n.l_invalid_format_allowed_chars( - Format.modhex.allowedCharacters) - : null, - prefixIcon: const Icon(Icons.public_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - tooltip: l10n.s_use_serial, - icon: const Icon(Icons.auto_awesome_outlined), - onPressed: (info?.serial != null) - ? () async { - final publicId = await ref - .read(otpStateProvider(widget.devicePath) - .notifier) - .modhexEncodeSerial(info!.serial!); - setState(() { - _publicIdController.text = publicId; - }); - } - : null, - ), - if (_validatePublicIdFormat) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_public_id, + errorText: _validatePublicIdFormat && !publicIdFormatValid + ? l10n.l_invalid_format_allowed_chars( + Format.modhex.allowedCharacters) + : null, + prefixIcon: const Icon(Icons.public_outlined), + suffixIcon: IconButton( + tooltip: l10n.s_use_serial, + icon: const Icon(Icons.auto_awesome_outlined), + onPressed: (info?.serial != null) + ? () async { + final publicId = await ref + .read(otpStateProvider(widget.devicePath) + .notifier) + .modhexEncodeSerial(info!.serial!); + setState(() { + _publicIdController.text = publicId; + }); + } + : null, + )), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { @@ -252,42 +240,30 @@ class _ConfigureYubiOtpDialogState controller: _privateIdController, autofillHints: isAndroid ? [] : const [AutofillHints.password], maxLength: privateIdLength, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_private_id, - errorText: _validatePrivateIdFormat && !privatedIdFormatValid - ? l10n.l_invalid_format_allowed_chars( - Format.hex.allowedCharacters) - : null, - prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - tooltip: l10n.s_generate_random, - icon: const Icon(Icons.refresh), - onPressed: () { - final random = Random.secure(); - final key = List.generate( - 6, - (_) => random - .nextInt(256) - .toRadixString(16) - .padLeft(2, '0')).join(); - setState(() { - _privateIdController.text = key; - }); - }, - ), - if (_validatePrivateIdFormat) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_private_id, + errorText: _validatePrivateIdFormat && !privatedIdFormatValid + ? l10n.l_invalid_format_allowed_chars( + Format.hex.allowedCharacters) + : null, + prefixIcon: const Icon(Icons.key_outlined), + suffixIcon: IconButton( + tooltip: l10n.s_generate_random, + icon: const Icon(Icons.refresh), + onPressed: () { + final random = Random.secure(); + final key = List.generate( + 6, + (_) => random + .nextInt(256) + .toRadixString(16) + .padLeft(2, '0')).join(); + setState(() { + _privateIdController.text = key; + }); + }, + )), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { @@ -300,42 +276,30 @@ class _ConfigureYubiOtpDialogState controller: _secretController, autofillHints: isAndroid ? [] : const [AutofillHints.password], maxLength: secretLength, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: l10n.s_secret_key, - errorText: _validateSecretFormat && !secretFormatValid - ? l10n.l_invalid_format_allowed_chars( - Format.hex.allowedCharacters) - : null, - prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - tooltip: l10n.s_generate_random, - icon: const Icon(Icons.refresh), - onPressed: () { - final random = Random.secure(); - final key = List.generate( - 16, - (_) => random - .nextInt(256) - .toRadixString(16) - .padLeft(2, '0')).join(); - setState(() { - _secretController.text = key; - }); - }, - ), - if (_validateSecretFormat) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - ), - ), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_secret_key, + errorText: _validateSecretFormat && !secretFormatValid + ? l10n.l_invalid_format_allowed_chars( + Format.hex.allowedCharacters) + : null, + prefixIcon: const Icon(Icons.key_outlined), + suffixIcon: IconButton( + tooltip: l10n.s_generate_random, + icon: const Icon(Icons.refresh), + onPressed: () { + final random = Random.secure(); + final key = List.generate( + 16, + (_) => random + .nextInt(256) + .toRadixString(16) + .padLeft(2, '0')).join(); + setState(() { + _secretController.text = key; + }); + }, + )), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { diff --git a/lib/piv/views/authentication_dialog.dart b/lib/piv/views/authentication_dialog.dart index bce757e1..8ad9304a 100644 --- a/lib/piv/views/authentication_dialog.dart +++ b/lib/piv/views/authentication_dialog.dart @@ -109,7 +109,7 @@ class _AuthenticationDialogState extends ConsumerState { controller: _keyController, readOnly: _defaultKeyUsed, maxLength: !_defaultKeyUsed ? keyLen : null, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_management_key, helperText: _defaultKeyUsed ? l10n.l_default_key_used : null, @@ -125,34 +125,22 @@ class _AuthenticationDialogState extends ConsumerState { ? null : hasMetadata && (_keyIsWrong || _keyFormatInvalid) ? const Icon(Icons.error) - : Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_defaultKeyUsed - ? Icons.auto_awesome - : Icons.auto_awesome_outlined), - tooltip: l10n.s_use_default, - onPressed: () { - setState(() { - _keyFormatInvalid = false; - _defaultKeyUsed = !_defaultKeyUsed; - if (_defaultKeyUsed) { - _keyController.text = - defaultManagementKey; - } else { - _keyController.clear(); - } - }); - }, - ), - if (_keyIsWrong || _keyFormatInvalid) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], + : IconButton( + icon: Icon(_defaultKeyUsed + ? Icons.auto_awesome + : Icons.auto_awesome_outlined), + tooltip: l10n.s_use_default, + onPressed: () { + setState(() { + _keyFormatInvalid = false; + _defaultKeyUsed = !_defaultKeyUsed; + if (_defaultKeyUsed) { + _keyController.text = defaultManagementKey; + } else { + _keyController.clear(); + } + }); + }, ), ), textInputAction: TextInputAction.next, diff --git a/lib/piv/views/generate_key_dialog.dart b/lib/piv/views/generate_key_dialog.dart index b96e2d58..82c313ca 100644 --- a/lib/piv/views/generate_key_dialog.dart +++ b/lib/piv/views/generate_key_dialog.dart @@ -161,7 +161,7 @@ class _GenerateKeyDialogState extends ConsumerState { AppTextField( autofocus: true, key: keys.subjectField, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_subject, errorText: _subject.isNotEmpty && _invalidSubject diff --git a/lib/piv/views/import_file_dialog.dart b/lib/piv/views/import_file_dialog.dart index ebc02385..4dd52468 100644 --- a/lib/piv/views/import_file_dialog.dart +++ b/lib/piv/views/import_file_dialog.dart @@ -129,34 +129,23 @@ class _ImportFileDialogState extends ConsumerState { obscureText: _isObscure, autofillHints: const [AutofillHints.password], key: keys.managementKeyField, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_password, errorText: _passwordIsWrong ? l10n.s_wrong_password : null, errorMaxLines: 3, prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscure - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: _isObscure - ? l10n.s_show_password - : l10n.s_hide_password), - if (_passwordIsWrong) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ]), + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Icons.visibility : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure + ? l10n.s_show_password + : l10n.s_hide_password), ), textInputAction: TextInputAction.next, onChanged: (value) { diff --git a/lib/piv/views/manage_key_dialog.dart b/lib/piv/views/manage_key_dialog.dart index 6ba6d3cc..53cf57df 100644 --- a/lib/piv/views/manage_key_dialog.dart +++ b/lib/piv/views/manage_key_dialog.dart @@ -173,7 +173,7 @@ class _ManageKeyDialogState extends ConsumerState { key: keys.pinPukField, maxLength: 8, controller: _currentController, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_pin, errorText: _currentIsWrong @@ -184,27 +184,15 @@ class _ManageKeyDialogState extends ConsumerState { : null, errorMaxLines: 3, prefixIcon: const Icon(Icons.pin_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscure - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: - _isObscure ? l10n.s_show_pin : l10n.s_hide_pin), - if (_currentIsWrong || _currentInvalidFormat) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ]), + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Icons.visibility : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin), ), textInputAction: TextInputAction.next, onChanged: (value) { @@ -284,7 +272,7 @@ class _ManageKeyDialogState extends ConsumerState { autofillHints: const [AutofillHints.newPassword], maxLength: hexLength, controller: _keyController, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_new_management_key, errorText: _newInvalidFormat @@ -293,35 +281,24 @@ class _ManageKeyDialogState extends ConsumerState { : null, enabled: currentLenOk, prefixIcon: const Icon(Icons.key_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: const Icon(Icons.refresh), - tooltip: l10n.s_generate_random, - onPressed: currentLenOk - ? () { - final random = Random.secure(); - final key = List.generate( - _keyType.keyLength, - (_) => random - .nextInt(256) - .toRadixString(16) - .padLeft(2, '0')).join(); - setState(() { - _keyController.text = key; - _newInvalidFormat = false; - }); - } - : null, - ), - if (_newInvalidFormat) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], + suffixIcon: IconButton( + icon: const Icon(Icons.refresh), + tooltip: l10n.s_generate_random, + onPressed: currentLenOk + ? () { + final random = Random.secure(); + final key = List.generate( + _keyType.keyLength, + (_) => random + .nextInt(256) + .toRadixString(16) + .padLeft(2, '0')).join(); + setState(() { + _keyController.text = key; + _newInvalidFormat = false; + }); + } + : null, ), ), textInputAction: TextInputAction.next, diff --git a/lib/piv/views/manage_pin_puk_dialog.dart b/lib/piv/views/manage_pin_puk_dialog.dart index 09aa8d16..f0cae705 100644 --- a/lib/piv/views/manage_pin_puk_dialog.dart +++ b/lib/piv/views/manage_pin_puk_dialog.dart @@ -111,48 +111,34 @@ class _ManagePinPukDialogState extends ConsumerState { maxLength: 8, autofillHints: const [AutofillHints.password], key: keys.pinPukField, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: widget.target == ManageTarget.pin - ? l10n.s_current_pin - : l10n.s_current_puk, - errorText: _currentIsWrong - ? (widget.target == ManageTarget.pin - ? l10n.l_wrong_pin_attempts_remaining( - _attemptsRemaining) - : l10n.l_wrong_puk_attempts_remaining( - _attemptsRemaining)) - : null, - errorMaxLines: 3, - prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscureCurrent - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscureCurrent = !_isObscureCurrent; - }); - }, - tooltip: widget.target == ManageTarget.pin - ? (_isObscureCurrent - ? l10n.s_show_pin - : l10n.s_hide_pin) - : (_isObscureCurrent - ? l10n.s_show_puk - : l10n.s_hide_puk), - ), - if (_currentIsWrong) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], - )), + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: widget.target == ManageTarget.pin + ? l10n.s_current_pin + : l10n.s_current_puk, + errorText: _currentIsWrong + ? (widget.target == ManageTarget.pin + ? l10n + .l_wrong_pin_attempts_remaining(_attemptsRemaining) + : l10n + .l_wrong_puk_attempts_remaining(_attemptsRemaining)) + : null, + errorMaxLines: 3, + prefixIcon: const Icon(Icons.password_outlined), + suffixIcon: IconButton( + icon: Icon(_isObscureCurrent + ? Icons.visibility + : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscureCurrent = !_isObscureCurrent; + }); + }, + tooltip: widget.target == ManageTarget.pin + ? (_isObscureCurrent ? l10n.s_show_pin : l10n.s_hide_pin) + : (_isObscureCurrent ? l10n.s_show_puk : l10n.s_hide_puk), + ), + ), textInputAction: TextInputAction.next, onChanged: (value) { setState(() { @@ -168,33 +154,24 @@ class _ManagePinPukDialogState extends ConsumerState { obscureText: _isObscureNew, maxLength: 8, autofillHints: const [AutofillHints.newPassword], - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: widget.target == ManageTarget.puk ? l10n.s_new_puk : l10n.s_new_pin, prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscureNew - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscureNew = !_isObscureNew; - }); - }, - tooltip: widget.target == ManageTarget.pin - ? (_isObscureNew - ? l10n.s_show_pin - : l10n.s_hide_pin) - : (_isObscureNew - ? l10n.s_show_puk - : l10n.s_hide_puk), - ), - ]), + suffixIcon: IconButton( + icon: Icon( + _isObscureNew ? Icons.visibility : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscureNew = !_isObscureNew; + }); + }, + tooltip: widget.target == ManageTarget.pin + ? (_isObscureNew ? l10n.s_show_pin : l10n.s_hide_pin) + : (_isObscureNew ? l10n.s_show_puk : l10n.s_hide_puk), + ), // Old YubiKeys allowed a 4 digit PIN enabled: _currentPin.length >= 4, ), @@ -215,33 +192,25 @@ class _ManagePinPukDialogState extends ConsumerState { obscureText: _isObscureConfirm, maxLength: 8, autofillHints: const [AutofillHints.newPassword], - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: widget.target == ManageTarget.puk ? l10n.s_confirm_puk : l10n.s_confirm_pin, prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon(_isObscureConfirm - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - _isObscureConfirm = !_isObscureConfirm; - }); - }, - tooltip: widget.target == ManageTarget.pin - ? (_isObscureConfirm - ? l10n.s_show_pin - : l10n.s_hide_pin) - : (_isObscureConfirm - ? l10n.s_show_puk - : l10n.s_hide_puk), - ) - ]), + suffixIcon: IconButton( + icon: Icon(_isObscureConfirm + ? Icons.visibility + : Icons.visibility_off), + onPressed: () { + setState(() { + _isObscureConfirm = !_isObscureConfirm; + }); + }, + tooltip: widget.target == ManageTarget.pin + ? (_isObscureConfirm ? l10n.s_show_pin : l10n.s_hide_pin) + : (_isObscureConfirm ? l10n.s_show_puk : l10n.s_hide_puk), + ), enabled: _currentPin.length >= 4 && _newPin.length >= 6, ), textInputAction: TextInputAction.done, diff --git a/lib/piv/views/pin_dialog.dart b/lib/piv/views/pin_dialog.dart index dee6f074..1b5f6a25 100644 --- a/lib/piv/views/pin_dialog.dart +++ b/lib/piv/views/pin_dialog.dart @@ -93,7 +93,7 @@ class _PinDialogState extends ConsumerState { autofillHints: const [AutofillHints.password], key: keys.managementKeyField, controller: _pinController, - decoration: InputDecoration( + decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_pin, errorText: _pinIsWrong @@ -101,29 +101,16 @@ class _PinDialogState extends ConsumerState { : null, errorMaxLines: 3, prefixIcon: const Icon(Icons.pin_outlined), - suffixIcon: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - icon: Icon( - _isObscure ? Icons.visibility : Icons.visibility_off, - color: !_pinIsWrong - ? IconTheme.of(context).color - : null), - onPressed: () { - setState(() { - _isObscure = !_isObscure; - }); - }, - tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin, - ), - if (_pinIsWrong) ...[ - const Icon(Icons.error_outlined), - const SizedBox( - width: 8.0, - ) - ] - ], + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Icons.visibility : Icons.visibility_off, + color: !_pinIsWrong ? IconTheme.of(context).color : null), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin, ), ), textInputAction: TextInputAction.next, diff --git a/lib/widgets/app_text_field.dart b/lib/widgets/app_text_field.dart index a4f70d1f..bf395747 100644 --- a/lib/widgets/app_text_field.dart +++ b/lib/widgets/app_text_field.dart @@ -16,6 +16,91 @@ import 'package:flutter/material.dart'; +class AppInputDecoration extends InputDecoration { + final List? suffixIcons; + + const AppInputDecoration({ + // allow multiple suffixIcons + this.suffixIcons, + // forward other TextField parameters + super.icon, + super.iconColor, + super.label, + super.labelText, + super.labelStyle, + super.floatingLabelStyle, + super.helperText, + super.helperStyle, + super.helperMaxLines, + super.hintText, + super.hintStyle, + super.hintTextDirection, + super.hintMaxLines, + super.hintFadeDuration, + super.error, + super.errorText, + super.errorStyle, + super.errorMaxLines, + super.floatingLabelBehavior, + super.floatingLabelAlignment, + super.isCollapsed, + super.isDense, + super.contentPadding, + super.prefixIcon, + super.prefixIconConstraints, + super.prefix, + super.prefixText, + super.prefixStyle, + super.prefixIconColor, + super.suffixIcon, + super.suffix, + super.suffixText, + super.suffixStyle, + super.suffixIconColor, + super.suffixIconConstraints, + super.counter, + super.counterText, + super.counterStyle, + super.filled, + super.fillColor, + super.focusColor, + super.hoverColor, + super.errorBorder, + super.focusedBorder, + super.focusedErrorBorder, + super.disabledBorder, + super.enabledBorder, + super.border, + super.enabled = true, + super.semanticCounterText, + super.alignLabelWithHint, + super.constraints, + }) : assert(!(suffixIcon != null && suffixIcons != null), + 'Declaring both suffixIcon and suffixIcons is not supported.'); + + @override + Widget? get suffixIcon { + final hasError = errorText != null; + + if (hasError || suffixIcons != null) { + final errorIcon = hasError ? const Icon(Icons.error_outlined) : null; + + final existingSuffixIcon = super.suffixIcon; + + return Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (suffixIcons != null) ...suffixIcons!, + if (existingSuffixIcon != null) existingSuffixIcon, + if (errorIcon != null) ...[errorIcon, const SizedBox(width: 8.0)], + ], + ); + } + + return super.suffixIcon; + } +} + /// TextField without autocorrect and suggestions class AppTextField extends TextField { const AppTextField({ @@ -28,7 +113,7 @@ class AppTextField extends TextField { super.controller, super.focusNode, super.undoController, - super.decoration, + AppInputDecoration? decoration, super.textInputAction, super.textCapitalization, super.style, @@ -83,5 +168,5 @@ class AppTextField extends TextField { super.canRequestFocus, super.spellCheckConfiguration, super.magnifierConfiguration, - }); + }) : super(decoration: decoration); }