mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-26 10:33:15 +03:00
Add error suffixIcon.
This commit is contained in:
parent
63bb18b2be
commit
fa92927f13
@ -173,17 +173,29 @@ class _PinEntryFormState extends ConsumerState<_PinEntryForm> {
|
||||
errorText: _pinIsWrong ? _getErrorText() : null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.pin_outlined),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_isObscure ? Icons.visibility : Icons.visibility_off,
|
||||
color: IconTheme.of(context).color,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isObscure = !_isObscure;
|
||||
});
|
||||
},
|
||||
tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin,
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
|
@ -84,6 +84,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
|
||||
errorText: _currentIsWrong ? _currentPinError : null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.pin_outlined),
|
||||
suffixIcon: _currentIsWrong ? const Icon(Icons.error) : null,
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@ -107,6 +108,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
|
||||
errorText: _newIsWrong ? _newPinError : null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.pin_outlined),
|
||||
suffixIcon: _newIsWrong ? const Icon(Icons.error) : null,
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
|
@ -19,10 +19,10 @@ import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:yubico_authenticator/core/models.dart';
|
||||
|
||||
import '../../android/oath/state.dart';
|
||||
import '../../app/logging.dart';
|
||||
@ -49,9 +49,6 @@ import 'utils.dart';
|
||||
|
||||
final _log = Logger('oath.view.add_account_page');
|
||||
|
||||
final _secretFormatterPattern =
|
||||
RegExp('[abcdefghijklmnopqrstuvwxyz234567 ]', caseSensitive: false);
|
||||
|
||||
class OathAddAccountPage extends ConsumerStatefulWidget {
|
||||
final DevicePath? devicePath;
|
||||
final OathState? state;
|
||||
@ -83,7 +80,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
HashAlgorithm _hashAlgorithm = defaultHashAlgorithm;
|
||||
int _digits = defaultDigits;
|
||||
int _counter = defaultCounter;
|
||||
bool _validateSecretLength = false;
|
||||
bool _validateSecret = false;
|
||||
bool _dataLoaded = false;
|
||||
bool _isObscure = true;
|
||||
List<int> _periodValues = [20, 30, 45, 60];
|
||||
@ -235,6 +232,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
|
||||
final secret = _secretController.text.replaceAll(' ', '');
|
||||
final secretLengthValid = secret.length * 5 % 8 < 5;
|
||||
final secretFormatValid = Format.base32.isValid(secret);
|
||||
|
||||
// is this credentials name/issuer pair different from all other?
|
||||
final isUnique = _credentials
|
||||
@ -271,7 +269,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
}
|
||||
|
||||
void submit() async {
|
||||
if (secretLengthValid) {
|
||||
if (secretLengthValid && secretFormatValid) {
|
||||
final cred = CredentialData(
|
||||
issuer: issuerText.isEmpty ? null : issuerText,
|
||||
name: nameText,
|
||||
@ -304,7 +302,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_validateSecretLength = true;
|
||||
_validateSecret = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -368,14 +366,18 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_issuer_optional,
|
||||
helperText: '',
|
||||
// Prevents dialog resizing when disabled
|
||||
prefixIcon: const Icon(Icons.business_outlined),
|
||||
helperText:
|
||||
'', // Prevents dialog resizing when disabled
|
||||
errorText: (byteLength(issuerText) > issuerMaxLength)
|
||||
? '' // needs empty string to render as error
|
||||
: issuerNoColon
|
||||
? null
|
||||
: l10n.l_invalid_character_issuer,
|
||||
prefixIcon: const Icon(Icons.business_outlined),
|
||||
suffixIcon: (!issuerNoColon ||
|
||||
byteLength(issuerText) > issuerMaxLength)
|
||||
? const Icon(Icons.error)
|
||||
: null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
@ -395,7 +397,6 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
labelText: l10n.s_account_name,
|
||||
helperText: '',
|
||||
// Prevents dialog resizing when disabled
|
||||
@ -404,6 +405,11 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
: isUnique
|
||||
? null
|
||||
: l10n.l_name_already_exists,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
suffixIcon:
|
||||
(!isUnique || byteLength(nameText) > nameMaxLength)
|
||||
? const Icon(Icons.error)
|
||||
: null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
@ -423,38 +429,50 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
// would hint to use saved passwords for this field
|
||||
autofillHints:
|
||||
isAndroid ? [] : const [AutofillHints.password],
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.allow(
|
||||
_secretFormatterPattern)
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_isObscure
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: IconTheme.of(context).color,
|
||||
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,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isObscure = !_isObscure;
|
||||
});
|
||||
},
|
||||
tooltip: _isObscure
|
||||
? l10n.s_show_secret_key
|
||||
: l10n.s_hide_secret_key,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
labelText: l10n.s_secret_key,
|
||||
errorText: _validateSecretLength && !secretLengthValid
|
||||
? l10n.s_invalid_length
|
||||
: null),
|
||||
if (_validateSecret) ...[
|
||||
const Icon(Icons.error_outlined),
|
||||
const SizedBox(
|
||||
width: 8.0,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
readOnly: _dataLoaded,
|
||||
textInputAction: TextInputAction.done,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_validateSecretLength = false;
|
||||
_validateSecret = false;
|
||||
});
|
||||
},
|
||||
onSubmitted: (_) {
|
||||
|
@ -89,11 +89,13 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
|
||||
autofillHints: const [AutofillHints.password],
|
||||
key: keys.currentPasswordField,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_current_password,
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
errorText: _currentIsWrong ? l10n.s_wrong_password : null,
|
||||
errorMaxLines: 3),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_current_password,
|
||||
errorText: _currentIsWrong ? l10n.s_wrong_password : null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
suffixIcon: _currentIsWrong ? const Icon(Icons.error) : null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
|
@ -208,6 +208,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
? l10n.l_name_already_exists
|
||||
: null,
|
||||
prefixIcon: const Icon(Icons.people_alt_outlined),
|
||||
suffixIcon:
|
||||
!nameNotEmpty || !isUnique ? const Icon(Icons.error) : null,
|
||||
),
|
||||
textInputAction: TextInputAction.done,
|
||||
onChanged: (value) {
|
||||
|
@ -85,19 +85,33 @@ class _UnlockFormState extends ConsumerState<UnlockForm> {
|
||||
errorText: _passwordIsWrong ? l10n.s_wrong_password : null,
|
||||
helperText: '', // Prevents resizing when errorText shown
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_isObscure ? Icons.visibility : Icons.visibility_off,
|
||||
color: IconTheme.of(context).color,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isObscure = !_isObscure;
|
||||
});
|
||||
},
|
||||
tooltip: _isObscure
|
||||
? l10n.s_show_password
|
||||
: l10n.s_hide_password,
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
onChanged: (_) => setState(() {
|
||||
|
@ -48,8 +48,7 @@ class ConfigureChalrespDialog extends ConsumerStatefulWidget {
|
||||
class _ConfigureChalrespDialogState
|
||||
extends ConsumerState<ConfigureChalrespDialog> {
|
||||
final _secretController = TextEditingController();
|
||||
bool _validateSecretLength = false;
|
||||
bool _validateSecretFormat = false;
|
||||
bool _validateSecret = false;
|
||||
bool _requireTouch = false;
|
||||
final int secretMaxLength = 40;
|
||||
|
||||
@ -74,17 +73,11 @@ class _ConfigureChalrespDialogState
|
||||
actions: [
|
||||
TextButton(
|
||||
key: keys.saveButton,
|
||||
onPressed: !_validateSecretLength
|
||||
onPressed: !_validateSecret
|
||||
? () async {
|
||||
if (!secretLengthValid) {
|
||||
if (!secretLengthValid || !secretFormatValid) {
|
||||
setState(() {
|
||||
_validateSecretLength = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!secretFormatValid) {
|
||||
setState(() {
|
||||
_validateSecretFormat = true;
|
||||
_validateSecret = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -136,48 +129,49 @@ class _ConfigureChalrespDialogState
|
||||
autofillHints: isAndroid ? [] : const [AutofillHints.password],
|
||||
maxLength: secretMaxLength,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
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: () {
|
||||
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;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_validateSecretLength || _validateSecretFormat) ...[
|
||||
const Icon(Icons.error_outlined),
|
||||
const SizedBox(
|
||||
width: 8.0,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
labelText: l10n.s_secret_key,
|
||||
errorText: _validateSecretLength && !secretLengthValid
|
||||
? l10n.s_invalid_length
|
||||
: _validateSecretFormat && !secretFormatValid
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null),
|
||||
});
|
||||
},
|
||||
tooltip: l10n.s_generate_random,
|
||||
),
|
||||
if (_validateSecret) ...[
|
||||
const Icon(Icons.error_outlined),
|
||||
const SizedBox(
|
||||
width: 8.0,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_validateSecretLength = false;
|
||||
_validateSecretFormat = false;
|
||||
_validateSecret = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -47,8 +47,7 @@ class ConfigureHotpDialog extends ConsumerStatefulWidget {
|
||||
|
||||
class _ConfigureHotpDialogState extends ConsumerState<ConfigureHotpDialog> {
|
||||
final _secretController = TextEditingController();
|
||||
bool _validateSecretLength = false;
|
||||
bool _validateSecretFormat = false;
|
||||
bool _validateSecret = false;
|
||||
int _digits = defaultDigits;
|
||||
final List<int> _digitsValues = [6, 8];
|
||||
bool _appendEnter = true;
|
||||
@ -73,17 +72,11 @@ class _ConfigureHotpDialogState extends ConsumerState<ConfigureHotpDialog> {
|
||||
actions: [
|
||||
TextButton(
|
||||
key: keys.saveButton,
|
||||
onPressed: !_validateSecretLength
|
||||
onPressed: !_validateSecret
|
||||
? () async {
|
||||
if (!secretLengthValid) {
|
||||
if (!secretLengthValid || !secretFormatValid) {
|
||||
setState(() {
|
||||
_validateSecretLength = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!secretFormatValid) {
|
||||
setState(() {
|
||||
_validateSecretFormat = true;
|
||||
_validateSecret = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -133,47 +126,47 @@ class _ConfigureHotpDialogState extends ConsumerState<ConfigureHotpDialog> {
|
||||
obscureText: _isObscure,
|
||||
autofillHints: isAndroid ? [] : const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_isObscure
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: !(_validateSecretLength ||
|
||||
_validateSecretFormat)
|
||||
? IconTheme.of(context).color
|
||||
: null),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isObscure = !_isObscure;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_validateSecretLength || _validateSecretFormat) ...[
|
||||
const Icon(Icons.error_outlined),
|
||||
const SizedBox(
|
||||
width: 8.0,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
labelText: l10n.s_secret_key,
|
||||
helperText: '', // Prevents resizing when errorText shown
|
||||
errorText: _validateSecretLength && !secretLengthValid
|
||||
? l10n.s_invalid_length
|
||||
: _validateSecretFormat && !secretFormatValid
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.base32.allowedCharacters)
|
||||
: null),
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_validateSecretLength = false;
|
||||
_validateSecretFormat = false;
|
||||
_validateSecret = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -149,44 +149,40 @@ class _ConfigureStaticDialogState extends ConsumerState<ConfigureStaticDialog> {
|
||||
autofillHints: isAndroid ? [] : const [AutofillHints.password],
|
||||
maxLength: passwordMaxLength,
|
||||
decoration: InputDecoration(
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
labelText: l10n.s_password,
|
||||
errorText: _validatePassword &&
|
||||
!passwordLengthValid &&
|
||||
passwordFormatValid
|
||||
? l10n.s_invalid_length
|
||||
: _validatePassword &&
|
||||
passwordLengthValid &&
|
||||
!passwordFormatValid
|
||||
? l10n.l_invalid_keyboard_character
|
||||
: null),
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
|
@ -205,39 +205,40 @@ class _ConfigureYubiOtpDialogState
|
||||
autofillHints: isAndroid ? [] : const [AutofillHints.password],
|
||||
maxLength: publicIdLength,
|
||||
decoration: InputDecoration(
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.public_outlined),
|
||||
errorText: _validatePublicIdFormat && !publicIdFormatValid
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.modhex.allowedCharacters)
|
||||
: null,
|
||||
labelText: l10n.s_public_id),
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@ -251,40 +252,41 @@ class _ConfigureYubiOtpDialogState
|
||||
autofillHints: isAndroid ? [] : const [AutofillHints.password],
|
||||
maxLength: privateIdLength,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: l10n.s_generate_private_id,
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
errorText: _validatePrivateIdFormat && !privatedIdFormatValid
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null,
|
||||
labelText: l10n.s_private_id),
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@ -298,40 +300,41 @@ class _ConfigureYubiOtpDialogState
|
||||
autofillHints: isAndroid ? [] : const [AutofillHints.password],
|
||||
maxLength: secretLength,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: l10n.s_generate_secret_key,
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
errorText: _validateSecretFormat && !secretFormatValid
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null,
|
||||
labelText: l10n.s_secret_key),
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
|
@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/core/models.dart';
|
||||
|
||||
import '../../app/models.dart';
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
@ -40,6 +40,7 @@ class AuthenticationDialog extends ConsumerStatefulWidget {
|
||||
class _AuthenticationDialogState extends ConsumerState<AuthenticationDialog> {
|
||||
bool _defaultKeyUsed = false;
|
||||
bool _keyIsWrong = false;
|
||||
bool _keyFormatInvalid = false;
|
||||
final _keyController = TextEditingController();
|
||||
|
||||
@override
|
||||
@ -56,6 +57,7 @@ class _AuthenticationDialogState extends ConsumerState<AuthenticationDialog> {
|
||||
ManagementKeyType.tdes)
|
||||
.keyLength *
|
||||
2;
|
||||
final keyFormatInvalid = !Format.hex.isValid(_keyController.text);
|
||||
return ResponsiveDialog(
|
||||
title: Text(l10n.l_unlock_piv_management),
|
||||
actions: [
|
||||
@ -63,6 +65,12 @@ class _AuthenticationDialogState extends ConsumerState<AuthenticationDialog> {
|
||||
key: keys.unlockButton,
|
||||
onPressed: _keyController.text.length == keyLen
|
||||
? () async {
|
||||
if (keyFormatInvalid) {
|
||||
setState(() {
|
||||
_keyFormatInvalid = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
final navigator = Navigator.of(context);
|
||||
try {
|
||||
final status = await ref
|
||||
@ -99,42 +107,59 @@ class _AuthenticationDialogState extends ConsumerState<AuthenticationDialog> {
|
||||
autofocus: true,
|
||||
autofillHints: const [AutofillHints.password],
|
||||
controller: _keyController,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp('[a-f0-9]', caseSensitive: false))
|
||||
],
|
||||
readOnly: _defaultKeyUsed,
|
||||
maxLength: !_defaultKeyUsed ? keyLen : null,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_management_key,
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
errorText: _keyIsWrong ? l10n.l_wrong_key : null,
|
||||
errorMaxLines: 3,
|
||||
helperText: _defaultKeyUsed ? l10n.l_default_key_used : null,
|
||||
suffixIcon: hasMetadata
|
||||
errorText: _keyIsWrong
|
||||
? l10n.l_wrong_key
|
||||
: _keyFormatInvalid
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
suffixIcon: hasMetadata && (!_keyIsWrong && !_keyFormatInvalid)
|
||||
? null
|
||||
: IconButton(
|
||||
icon: Icon(_defaultKeyUsed
|
||||
? Icons.auto_awesome
|
||||
: Icons.auto_awesome_outlined),
|
||||
tooltip: l10n.s_use_default,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_defaultKeyUsed = !_defaultKeyUsed;
|
||||
if (_defaultKeyUsed) {
|
||||
_keyController.text = defaultManagementKey;
|
||||
} else {
|
||||
_keyController.clear();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
: 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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_keyIsWrong = false;
|
||||
_keyFormatInvalid = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -162,11 +162,15 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
autofocus: true,
|
||||
key: keys.subjectField,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_subject,
|
||||
errorText: _subject.isNotEmpty && _invalidSubject
|
||||
? l10n.l_rfc4514_invalid
|
||||
: null),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_subject,
|
||||
errorText: _subject.isNotEmpty && _invalidSubject
|
||||
? l10n.l_rfc4514_invalid
|
||||
: null,
|
||||
suffixIcon: _subject.isNotEmpty && _invalidSubject
|
||||
? const Icon(Icons.error)
|
||||
: null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
enabled: !_generating,
|
||||
onChanged: (value) {
|
||||
|
@ -129,11 +129,13 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
|
||||
autofillHints: const [AutofillHints.password],
|
||||
key: keys.managementKeyField,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_password,
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
errorText: _passwordIsWrong ? l10n.s_wrong_password : null,
|
||||
errorMaxLines: 3),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_password,
|
||||
errorText: _passwordIsWrong ? l10n.s_wrong_password : null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
suffixIcon: _passwordIsWrong ? const Icon(Icons.error) : null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
|
@ -17,9 +17,9 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/core/models.dart';
|
||||
|
||||
import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
@ -49,6 +49,8 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
late bool _usesStoredKey;
|
||||
late bool _storeKey;
|
||||
bool _currentIsWrong = false;
|
||||
bool _currentInvalidFormat = false;
|
||||
bool _newInvalidFormat = false;
|
||||
int _attemptsRemaining = -1;
|
||||
ManagementKeyType _keyType = ManagementKeyType.tdes;
|
||||
final _currentController = TextEditingController();
|
||||
@ -76,6 +78,16 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
}
|
||||
|
||||
_submit() async {
|
||||
final currentInvalidFormat = Format.hex.isValid(_currentController.text);
|
||||
final newInvalidFormat = Format.hex.isValid(_keyController.text);
|
||||
if (!currentInvalidFormat || !newInvalidFormat) {
|
||||
setState(() {
|
||||
_currentInvalidFormat = !currentInvalidFormat;
|
||||
_newInvalidFormat = !newInvalidFormat;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final notifier = ref.read(pivStateProvider(widget.path).notifier);
|
||||
if (_usesStoredKey) {
|
||||
final status = (await notifier.verifyPin(_currentController.text)).when(
|
||||
@ -161,18 +173,25 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
maxLength: 8,
|
||||
controller: _currentController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_pin,
|
||||
prefixIcon: const Icon(Icons.pin_outlined),
|
||||
errorText: _currentIsWrong
|
||||
? l10n
|
||||
.l_wrong_pin_attempts_remaining(_attemptsRemaining)
|
||||
: null,
|
||||
errorMaxLines: 3),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_pin,
|
||||
errorText: _currentIsWrong
|
||||
? l10n.l_wrong_pin_attempts_remaining(_attemptsRemaining)
|
||||
: _currentInvalidFormat
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.pin_outlined),
|
||||
suffixIcon: _currentIsWrong || _currentInvalidFormat
|
||||
? const Icon(Icons.error)
|
||||
: null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_currentIsWrong = false;
|
||||
_currentInvalidFormat = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
@ -187,33 +206,52 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_current_management_key,
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
errorText: _currentIsWrong ? l10n.l_wrong_key : null,
|
||||
errorMaxLines: 3,
|
||||
helperText: _defaultKeyUsed ? l10n.l_default_key_used : null,
|
||||
suffixIcon: _hasMetadata
|
||||
errorText: _currentIsWrong
|
||||
? l10n.l_wrong_key
|
||||
: _currentInvalidFormat
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null,
|
||||
errorMaxLines: 3,
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
suffixIcon: (_hasMetadata &&
|
||||
!_currentIsWrong &&
|
||||
!_currentInvalidFormat)
|
||||
? null
|
||||
: IconButton(
|
||||
icon: Icon(_defaultKeyUsed
|
||||
? Icons.auto_awesome
|
||||
: Icons.auto_awesome_outlined),
|
||||
tooltip: l10n.s_use_default,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_defaultKeyUsed = !_defaultKeyUsed;
|
||||
if (_defaultKeyUsed) {
|
||||
_currentController.text = defaultManagementKey;
|
||||
} else {
|
||||
_currentController.clear();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
: (_hasMetadata && _currentIsWrong ||
|
||||
_currentInvalidFormat)
|
||||
? 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(() {
|
||||
_defaultKeyUsed = !_defaultKeyUsed;
|
||||
if (_defaultKeyUsed) {
|
||||
_currentController.text =
|
||||
defaultManagementKey;
|
||||
} else {
|
||||
_currentController.clear();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_currentIsWrong ||
|
||||
_currentInvalidFormat) ...[
|
||||
const Icon(Icons.error_outlined),
|
||||
const SizedBox(
|
||||
width: 8.0,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp('[a-f0-9]', caseSensitive: false))
|
||||
],
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@ -227,33 +265,44 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
maxLength: hexLength,
|
||||
controller: _keyController,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp('[a-f0-9]', caseSensitive: false))
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_new_management_key,
|
||||
prefixIcon: const Icon(Icons.key_outlined),
|
||||
errorText: _newInvalidFormat
|
||||
? l10n.l_invalid_format_allowed_chars(
|
||||
Format.hex.allowedCharacters)
|
||||
: null,
|
||||
enabled: currentLenOk,
|
||||
suffixIcon: IconButton(
|
||||
key: keys.managementKeyRefresh,
|
||||
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;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
|
@ -109,19 +109,21 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
|
||||
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,
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
errorText: _currentIsWrong
|
||||
? (widget.target == ManageTarget.pin
|
||||
? l10n.l_wrong_pin_attempts_remaining(
|
||||
_attemptsRemaining)
|
||||
: l10n.l_wrong_puk_attempts_remaining(
|
||||
_attemptsRemaining))
|
||||
: null,
|
||||
errorMaxLines: 3),
|
||||
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: _currentIsWrong ? const Icon(Icons.error) : null,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
|
@ -96,22 +96,34 @@ class _PinDialogState extends ConsumerState<PinDialog> {
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_pin,
|
||||
prefixIcon: const Icon(Icons.pin_outlined),
|
||||
errorText: _pinIsWrong
|
||||
? l10n.l_wrong_pin_attempts_remaining(_attemptsRemaining)
|
||||
: null,
|
||||
errorMaxLines: 3,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_isObscure ? Icons.visibility : Icons.visibility_off,
|
||||
color: IconTheme.of(context).color,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isObscure = !_isObscure;
|
||||
});
|
||||
},
|
||||
tooltip: _isObscure ? l10n.s_show_pin : l10n.s_hide_pin,
|
||||
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,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
|
Loading…
Reference in New Issue
Block a user