This commit is contained in:
Adam Velebil 2022-11-22 13:21:05 +01:00
commit c290ac52fb
No known key found for this signature in database
GPG Key ID: AC6D6B9D715FC084
2 changed files with 40 additions and 26 deletions

View File

@ -261,23 +261,28 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
} }
final period = int.tryParse(_periodController.text) ?? -1; final period = int.tryParse(_periodController.text) ?? -1;
final issuerText = _issuerController.text.trim();
final nameText = _accountController.text.trim();
final remaining = getRemainingKeySpace( final remaining = getRemainingKeySpace(
oathType: _oathType, oathType: _oathType,
period: period, period: period,
issuer: _issuerController.text.trim(), issuer: issuerText,
name: _accountController.text.trim(), name: nameText,
); );
final issuerRemaining = remaining.first; final issuerRemaining = remaining.first;
final nameRemaining = remaining.second; final nameRemaining = remaining.second;
final issuerMaxLength = max(issuerRemaining, 1);
final nameMaxLength = max(nameRemaining, 1);
final secret = _secretController.text.replaceAll(' ', ''); final secret = _secretController.text.replaceAll(' ', '');
final secretLengthValid = secret.length * 5 % 8 < 5; final secretLengthValid = secret.length * 5 % 8 < 5;
// is this credentials name/issuer pair different from all other? // is this credentials name/issuer pair different from all other?
final isUnique = _credentials final isUnique = _credentials
?.where((element) => ?.where((element) =>
element.name == _accountController.text.trim() && element.name == nameText &&
(element.issuer ?? '') == _issuerController.text.trim()) (element.issuer ?? '') == issuerText)
.isEmpty ?? .isEmpty ??
true; true;
final issuerNoColon = !_issuerController.text.contains(':'); final issuerNoColon = !_issuerController.text.contains(':');
@ -285,7 +290,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
final isLocked = oathState?.locked ?? false; final isLocked = oathState?.locked ?? false;
final isValid = !isLocked && final isValid = !isLocked &&
_accountController.text.trim().isNotEmpty && nameText.isNotEmpty &&
secret.isNotEmpty && secret.isNotEmpty &&
isUnique && isUnique &&
issuerNoColon && issuerNoColon &&
@ -311,11 +316,9 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
void submit() async { void submit() async {
if (secretLengthValid) { if (secretLengthValid) {
final issuer = _issuerController.text.trim();
final cred = CredentialData( final cred = CredentialData(
issuer: issuer.isEmpty ? null : issuer, issuer: issuerText.isEmpty ? null : issuerText,
name: _accountController.text.trim(), name: nameText,
secret: secret, secret: secret,
oathType: _oathType, oathType: _oathType,
hashAlgorithm: _hashAlgorithm, hashAlgorithm: _hashAlgorithm,
@ -389,12 +392,11 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
controller: _issuerController, controller: _issuerController,
autofocus: widget.credentialData == null, autofocus: widget.credentialData == null,
enabled: issuerRemaining > 0, enabled: issuerRemaining > 0,
maxLength: max(issuerRemaining, 1), maxLength: issuerMaxLength,
inputFormatters: [ inputFormatters: [
limitBytesLength(issuerRemaining), limitBytesLength(issuerRemaining),
], ],
buildCounter: buildCounter: buildByteCounterFor(issuerText),
buildByteCounterFor(_issuerController.text.trim()),
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: labelText:
@ -402,10 +404,12 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
helperText: helperText:
'', // Prevents dialog resizing when disabled '', // Prevents dialog resizing when disabled
prefixIcon: const Icon(Icons.business_outlined), prefixIcon: const Icon(Icons.business_outlined),
errorText: issuerNoColon errorText: (byteLength(issuerText) > issuerMaxLength)
? null ? '' // needs empty string to render as error
: AppLocalizations.of(context)! : issuerNoColon
.oath_invalid_character_issuer, ? null
: AppLocalizations.of(context)!
.oath_invalid_character_issuer,
), ),
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
onChanged: (value) { onChanged: (value) {
@ -420,9 +424,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
TextField( TextField(
key: keys.nameField, key: keys.nameField,
controller: _accountController, controller: _accountController,
maxLength: max(nameRemaining, 1), maxLength: nameMaxLength,
buildCounter: buildCounter: buildByteCounterFor(nameText),
buildByteCounterFor(_accountController.text.trim()),
inputFormatters: [limitBytesLength(nameRemaining)], inputFormatters: [limitBytesLength(nameRemaining)],
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
@ -431,9 +434,12 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
AppLocalizations.of(context)!.oath_account_name, AppLocalizations.of(context)!.oath_account_name,
helperText: helperText:
'', // Prevents dialog resizing when disabled '', // Prevents dialog resizing when disabled
errorText: isUnique errorText: (byteLength(nameText) > nameMaxLength)
? null ? '' // needs empty string to render as error
: AppLocalizations.of(context)!.oath_duplicate_name, : isUnique
? null
: AppLocalizations.of(context)!
.oath_duplicate_name,
), ),
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
onChanged: (value) { onChanged: (value) {

View File

@ -28,10 +28,18 @@ int byteLength(String value) => utf8.encode(value).length;
/// used rather than number of characters. [currentValue] should always match /// used rather than number of characters. [currentValue] should always match
/// the input text value to measure. /// the input text value to measure.
InputCounterWidgetBuilder buildByteCounterFor(String currentValue) => InputCounterWidgetBuilder buildByteCounterFor(String currentValue) =>
(context, {required currentLength, required isFocused, maxLength}) => Text( (context, {required currentLength, required isFocused, maxLength}) {
maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '', final theme = Theme.of(context);
style: Theme.of(context).textTheme.caption, final caption = theme.textTheme.caption;
); final style = (byteLength(currentValue) <= (maxLength ?? 0))
? caption
: caption?.copyWith(color: theme.errorColor);
return Text(
maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '',
style: style,
semanticsLabel: 'Character count',
);
};
/// Limits the input in length based on the byte length when encoded. /// Limits the input in length based on the byte length when encoded.
/// This is generally used together with [buildByteCounterFor]. /// This is generally used together with [buildByteCounterFor].