Add AppInputDecoration.

This commit is contained in:
Elias Bonnici 2023-12-14 16:38:10 +01:00
parent ce5b8abe81
commit dc5d3ce9dc
No known key found for this signature in database
GPG Key ID: 5EAC28EA3F980CCF
15 changed files with 458 additions and 592 deletions

View File

@ -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) {

View File

@ -363,7 +363,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
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<OathAddAccountPage> {
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<OathAddAccountPage> {
// 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) {

View File

@ -91,35 +91,25 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
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<ManagePasswordDialog> {
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<ManagePasswordDialog> {
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,

View File

@ -79,39 +79,26 @@ class _UnlockFormState extends ConsumerState<UnlockForm> {
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(() {

View File

@ -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(() {

View File

@ -126,44 +126,32 @@ class _ConfigureHotpDialogState extends ConsumerState<ConfigureHotpDialog> {
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(() {

View File

@ -149,41 +149,29 @@ class _ConfigureStaticDialogState extends ConsumerState<ConfigureStaticDialog> {
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(() {

View File

@ -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(() {

View File

@ -109,7 +109,7 @@ class _AuthenticationDialogState extends ConsumerState<AuthenticationDialog> {
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<AuthenticationDialog> {
? 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,

View File

@ -161,7 +161,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
AppTextField(
autofocus: true,
key: keys.subjectField,
decoration: InputDecoration(
decoration: AppInputDecoration(
border: const OutlineInputBorder(),
labelText: l10n.s_subject,
errorText: _subject.isNotEmpty && _invalidSubject

View File

@ -129,34 +129,23 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
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) {

View File

@ -173,7 +173,7 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
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<ManageKeyDialog> {
: 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<ManageKeyDialog> {
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<ManageKeyDialog> {
: 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,

View File

@ -111,48 +111,34 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
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<ManagePinPukDialog> {
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<ManagePinPukDialog> {
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,

View File

@ -93,7 +93,7 @@ class _PinDialogState extends ConsumerState<PinDialog> {
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<PinDialog> {
: 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,

View File

@ -16,6 +16,91 @@
import 'package:flutter/material.dart';
class AppInputDecoration extends InputDecoration {
final List<Widget>? 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);
}