diff --git a/lib/fido/views/add_fingerprint_dialog.dart b/lib/fido/views/add_fingerprint_dialog.dart index c4daafc5..31638b3f 100755 --- a/lib/fido/views/add_fingerprint_dialog.dart +++ b/lib/fido/views/add_fingerprint_dialog.dart @@ -256,7 +256,7 @@ class _AddFingerprintDialogState extends ConsumerState onFieldSubmitted: (_) { _submit(); }, - ), + ).init(), ) ] ], diff --git a/lib/fido/views/pin_dialog.dart b/lib/fido/views/pin_dialog.dart index 2c3e3556..1bbb496c 100755 --- a/lib/fido/views/pin_dialog.dart +++ b/lib/fido/views/pin_dialog.dart @@ -126,7 +126,7 @@ class _FidoPinDialogState extends ConsumerState { _currentIsWrong = false; }); }, - ), + ).init(), ], Text(l10n.p_enter_new_fido2_pin(minPinLength)), // TODO: Set max characters based on UTF-8 bytes @@ -161,7 +161,7 @@ class _FidoPinDialogState extends ConsumerState { _newPin = value; }); }, - ), + ).init(), AppTextFormField( key: confirmPin, initialValue: _confirmPin, @@ -200,7 +200,7 @@ class _FidoPinDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/fido/views/pin_entry_form.dart b/lib/fido/views/pin_entry_form.dart index 0754dda7..b4236fa8 100644 --- a/lib/fido/views/pin_entry_form.dart +++ b/lib/fido/views/pin_entry_form.dart @@ -128,7 +128,7 @@ class _PinEntryFormState extends ConsumerState { }); }, // Update state on change onSubmitted: (_) => _submit(), - ), + ).init(), ), ListTile( leading: noFingerprints diff --git a/lib/fido/views/rename_fingerprint_dialog.dart b/lib/fido/views/rename_fingerprint_dialog.dart index 023997ae..2c6c783c 100755 --- a/lib/fido/views/rename_fingerprint_dialog.dart +++ b/lib/fido/views/rename_fingerprint_dialog.dart @@ -112,7 +112,7 @@ class _RenameAccountDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 77540ed1..aca626c2 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -390,7 +390,7 @@ class _OathAddAccountPageState extends ConsumerState { onSubmitted: (_) { if (isValid) submit(); }, - ), + ).init(), AppTextField( key: keys.nameField, controller: _accountController, @@ -418,7 +418,7 @@ class _OathAddAccountPageState extends ConsumerState { onSubmitted: (_) { if (isValid) submit(); }, - ), + ).init(), AppTextField( key: keys.secretField, controller: _secretController, @@ -460,7 +460,7 @@ class _OathAddAccountPageState extends ConsumerState { onSubmitted: (_) { if (isValid) submit(); }, - ), + ).init(), const SizedBox(height: 8), Wrap( crossAxisAlignment: WrapCrossAlignment.center, diff --git a/lib/oath/views/manage_password_dialog.dart b/lib/oath/views/manage_password_dialog.dart index 0b80b4c0..24a7c823 100755 --- a/lib/oath/views/manage_password_dialog.dart +++ b/lib/oath/views/manage_password_dialog.dart @@ -137,7 +137,7 @@ class _ManagePasswordDialogState extends ConsumerState { _currentIsWrong = false; }); }, - ), + ).init(), Wrap( spacing: 4.0, runSpacing: 8.0, @@ -227,7 +227,7 @@ class _ManagePasswordDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), AppTextField( key: keys.confirmPasswordField, obscureText: _isObscureConfirm, @@ -268,7 +268,7 @@ class _ManagePasswordDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 946bd92a..2e634707 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -439,7 +439,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { Focus.of(context) .focusInDirection(TraversalDirection.down); }, - ), + ).init(), ); }), ), diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index 809870b6..bf6a26ce 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -193,7 +193,7 @@ class _RenameAccountDialogState extends ConsumerState { _issuer = value.trim(); }); }, - ), + ).init(), AppTextFormField( initialValue: _name, maxLength: nameRemaining, @@ -222,7 +222,7 @@ class _RenameAccountDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/oath/views/unlock_form.dart b/lib/oath/views/unlock_form.dart index 85bbf60d..92eddab1 100755 --- a/lib/oath/views/unlock_form.dart +++ b/lib/oath/views/unlock_form.dart @@ -110,7 +110,7 @@ class _UnlockFormState extends ConsumerState { _passwordIsWrong = false; }), // Update state on change onSubmitted: (_) => _submit(), - ), + ).init(), ), const SizedBox(height: 3.0), Column( diff --git a/lib/otp/views/configure_chalresp_dialog.dart b/lib/otp/views/configure_chalresp_dialog.dart index c2d5bdde..2170d54e 100644 --- a/lib/otp/views/configure_chalresp_dialog.dart +++ b/lib/otp/views/configure_chalresp_dialog.dart @@ -166,7 +166,7 @@ class _ConfigureChalrespDialogState _validateSecret = false; }); }, - ), + ).init(), FilterChip( label: Text(l10n.s_require_touch), selected: _requireTouch, diff --git a/lib/otp/views/configure_hotp_dialog.dart b/lib/otp/views/configure_hotp_dialog.dart index 79f36236..3c7b4910 100644 --- a/lib/otp/views/configure_hotp_dialog.dart +++ b/lib/otp/views/configure_hotp_dialog.dart @@ -159,7 +159,7 @@ class _ConfigureHotpDialogState extends ConsumerState { _validateSecret = false; }); }, - ), + ).init(), Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 4.0, diff --git a/lib/otp/views/configure_static_dialog.dart b/lib/otp/views/configure_static_dialog.dart index 931a5df3..2a1491dd 100644 --- a/lib/otp/views/configure_static_dialog.dart +++ b/lib/otp/views/configure_static_dialog.dart @@ -181,7 +181,7 @@ class _ConfigureStaticDialogState extends ConsumerState { _validatePassword = false; }); }, - ), + ).init(), Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 4.0, diff --git a/lib/otp/views/configure_yubiotp_dialog.dart b/lib/otp/views/configure_yubiotp_dialog.dart index 0cfa9c84..81643834 100644 --- a/lib/otp/views/configure_yubiotp_dialog.dart +++ b/lib/otp/views/configure_yubiotp_dialog.dart @@ -237,7 +237,7 @@ class _ConfigureYubiOtpDialogState _validatePublicIdFormat = false; }); }, - ), + ).init(), AppTextField( key: keys.privateIdField, controller: _privateIdController, @@ -274,7 +274,7 @@ class _ConfigureYubiOtpDialogState _validatePrivateIdFormat = false; }); }, - ), + ).init(), AppTextField( key: keys.secretField, controller: _secretController, @@ -311,7 +311,7 @@ class _ConfigureYubiOtpDialogState _validateSecretFormat = false; }); }, - ), + ).init(), Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 4.0, diff --git a/lib/piv/views/authentication_dialog.dart b/lib/piv/views/authentication_dialog.dart index 6ec07c9a..3f2ed339 100644 --- a/lib/piv/views/authentication_dialog.dart +++ b/lib/piv/views/authentication_dialog.dart @@ -160,7 +160,7 @@ class _AuthenticationDialogState extends ConsumerState { _keyFormatInvalid = false; }); }, - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/piv/views/generate_key_dialog.dart b/lib/piv/views/generate_key_dialog.dart index f4451452..11d85f04 100644 --- a/lib/piv/views/generate_key_dialog.dart +++ b/lib/piv/views/generate_key_dialog.dart @@ -174,7 +174,7 @@ class _GenerateKeyDialogState extends ConsumerState { _subject = value; }); }, - ), + ).init(), Text( l10n.rfc4514_examples, style: subtitleStyle, diff --git a/lib/piv/views/import_file_dialog.dart b/lib/piv/views/import_file_dialog.dart index 4c2327a8..f65a3e51 100644 --- a/lib/piv/views/import_file_dialog.dart +++ b/lib/piv/views/import_file_dialog.dart @@ -162,7 +162,7 @@ class _ImportFileDialogState extends ConsumerState { }); }, onSubmitted: (_) => _examine(), - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/piv/views/manage_key_dialog.dart b/lib/piv/views/manage_key_dialog.dart index 7407510d..45184c2f 100644 --- a/lib/piv/views/manage_key_dialog.dart +++ b/lib/piv/views/manage_key_dialog.dart @@ -226,7 +226,7 @@ class _ManageKeyDialogState extends ConsumerState { _currentInvalidFormat = false; }); }, - ), + ).init(), if (!_usesStoredKey) AppTextFormField( key: keys.managementKeyField, @@ -272,7 +272,7 @@ class _ManageKeyDialogState extends ConsumerState { _currentIsWrong = false; }); }, - ), + ).init(), AppTextField( key: keys.newPinPukField, autofocus: _defaultKeyUsed, @@ -320,7 +320,7 @@ class _ManageKeyDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 4.0, diff --git a/lib/piv/views/manage_pin_puk_dialog.dart b/lib/piv/views/manage_pin_puk_dialog.dart index eff0deb9..f88d0d5b 100644 --- a/lib/piv/views/manage_pin_puk_dialog.dart +++ b/lib/piv/views/manage_pin_puk_dialog.dart @@ -207,7 +207,7 @@ class _ManagePinPukDialogState extends ConsumerState { _currentIsWrong = false; }); }, - ), + ).init(), Text(l10n.p_enter_new_piv_pin_puk( widget.target == ManageTarget.puk ? l10n.s_puk : l10n.s_pin)), AppTextField( @@ -248,7 +248,7 @@ class _ManagePinPukDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), AppTextField( key: keys.confirmPinPukField, obscureText: _isObscureConfirm, @@ -293,7 +293,7 @@ class _ManagePinPukDialogState extends ConsumerState { _submit(); } }, - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/piv/views/pin_dialog.dart b/lib/piv/views/pin_dialog.dart index f7ebabb2..6e342d3d 100644 --- a/lib/piv/views/pin_dialog.dart +++ b/lib/piv/views/pin_dialog.dart @@ -128,7 +128,7 @@ class _PinDialogState extends ConsumerState { }); }, onSubmitted: (_) => _submit(), - ), + ).init(), ] .map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), diff --git a/lib/widgets/app_text_field.dart b/lib/widgets/app_text_field.dart index ff0c49e3..7c72d856 100644 --- a/lib/widgets/app_text_field.dart +++ b/lib/widgets/app_text_field.dart @@ -14,13 +14,17 @@ * limitations under the License. */ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'app_input_decoration.dart'; /// TextField without autocorrect and suggestions +// ignore: must_be_immutable class AppTextField extends TextField { - const AppTextField({ + bool _initialized = false; + AppTextField({ // default settings to turn off autocorrect super.autocorrect = false, super.enableSuggestions = false, @@ -85,5 +89,22 @@ class AppTextField extends TextField { super.canRequestFocus, super.spellCheckConfiguration, super.magnifierConfiguration, - }) : super(decoration: decoration); + }) : super(decoration: decoration) { + // TODO: Replace this with a custom lint check, if possible + Timer.run(() { + assert(_initialized, 'AppTextField not initialized!'); + }); + } + + Widget init() { + _initialized = true; + return Builder( + builder: (context) => DefaultSelectionStyle( + selectionColor: decoration?.errorText != null + ? Theme.of(context).colorScheme.error + : null, + child: this, + ), + ); + } } diff --git a/lib/widgets/app_text_form_field.dart b/lib/widgets/app_text_form_field.dart index 61e0540b..513894a3 100644 --- a/lib/widgets/app_text_form_field.dart +++ b/lib/widgets/app_text_form_field.dart @@ -14,12 +14,17 @@ * limitations under the License. */ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'app_input_decoration.dart'; /// TextFormField without autocorrect and suggestions +// ignore: must_be_immutable class AppTextFormField extends TextFormField { + final AppInputDecoration? decoration; + bool _initialized = false; AppTextFormField({ // default settings to turn off autocorrect super.autocorrect = false, @@ -30,7 +35,7 @@ class AppTextFormField extends TextFormField { super.controller, super.initialValue, super.focusNode, - AppInputDecoration? decoration, + this.decoration, super.textCapitalization, super.textInputAction, super.style, @@ -89,5 +94,22 @@ class AppTextFormField extends TextFormField { super.clipBehavior, super.scribbleEnabled, super.canRequestFocus, - }) : super(decoration: decoration); + }) : super(decoration: decoration) { + // TODO: Replace this with a custom lint check, if possible + Timer.run(() { + assert(_initialized, 'AppTextFormField not initialized!'); + }); + } + + Widget init() { + _initialized = true; + return Builder( + builder: (context) => DefaultSelectionStyle( + selectionColor: decoration?.errorText != null + ? Theme.of(context).colorScheme.error + : null, + child: this, + ), + ); + } }