mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
Count UTF8 bytes for byte-limited text fields.
This commit is contained in:
parent
7ee5b82906
commit
662536140a
@ -10,6 +10,7 @@ import 'package:yubico_authenticator/app/logging.dart';
|
||||
import '../../app/message.dart';
|
||||
import '../../desktop/models.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
import '../../widgets/utf8_text_fields.dart';
|
||||
import '../state.dart';
|
||||
import '../../fido/models.dart';
|
||||
import '../../app/models.dart';
|
||||
@ -179,6 +180,8 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
|
||||
TextFormField(
|
||||
focusNode: _nameFocus,
|
||||
maxLength: 15,
|
||||
inputFormatters: [limitBytesLength(15)],
|
||||
buildCounter: buildCountersFor(_label),
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
enabled: _fingerprint != null,
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../app/message.dart';
|
||||
import '../../desktop/models.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
import '../../widgets/utf8_text_fields.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import '../../app/models.dart';
|
||||
@ -69,8 +70,9 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
|
||||
const Text('This will change the label of the fingerprint.'),
|
||||
TextFormField(
|
||||
initialValue: _label,
|
||||
// TODO: Make this field count UTF-8 bytes instead of characters.
|
||||
maxLength: 15,
|
||||
inputFormatters: [limitBytesLength(15)],
|
||||
buildCounter: buildCountersFor(_label),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Label',
|
||||
|
@ -5,14 +5,15 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:yubico_authenticator/app/logging.dart';
|
||||
|
||||
import '../../app/logging.dart';
|
||||
import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/state.dart';
|
||||
import '../../desktop/models.dart';
|
||||
import '../../widgets/file_drop_target.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
import '../../widgets/utf8_text_fields.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'utils.dart';
|
||||
@ -210,6 +211,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
autofocus: true,
|
||||
enabled: issuerRemaining > 0,
|
||||
maxLength: max(issuerRemaining, 1),
|
||||
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||
buildCounter: buildCountersFor(_issuerController.text),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Issuer (optional)',
|
||||
@ -229,6 +232,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
key: const Key('name'),
|
||||
controller: _accountController,
|
||||
maxLength: max(nameRemaining, 1),
|
||||
buildCounter: buildCountersFor(_accountController.text),
|
||||
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Account name',
|
||||
|
@ -7,6 +7,7 @@ import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../desktop/models.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
import '../../widgets/utf8_text_fields.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'utils.dart';
|
||||
@ -97,6 +98,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
initialValue: _issuer,
|
||||
enabled: issuerRemaining > 0,
|
||||
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
||||
buildCounter: buildCountersFor(_issuer),
|
||||
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Issuer (optional)',
|
||||
@ -112,6 +115,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
||||
TextFormField(
|
||||
initialValue: _account,
|
||||
maxLength: nameRemaining,
|
||||
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||
buildCounter: buildCountersFor(_account),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'Account name',
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import '../models.dart';
|
||||
@ -18,7 +19,7 @@ Pair<int, int> getRemainingKeySpace(
|
||||
// Non-standard TOTP periods are stored as part of this data, as a "D/"- prefix.
|
||||
remaining -= '$period/'.length;
|
||||
}
|
||||
int issuerSpace = issuer.length;
|
||||
int issuerSpace = utf8.encode(issuer).length;
|
||||
if (issuer.isNotEmpty) {
|
||||
// Issuer is separated from name with a ":", if present.
|
||||
issuerSpace += 1;
|
||||
@ -26,7 +27,7 @@ Pair<int, int> getRemainingKeySpace(
|
||||
|
||||
return Pair(
|
||||
// Always reserve at least one character for name
|
||||
remaining - 1 - max(name.length, 1),
|
||||
remaining - 1 - max(utf8.encode(name).length, 1),
|
||||
remaining - issuerSpace,
|
||||
);
|
||||
}
|
||||
|
21
lib/widgets/utf8_text_fields.dart
Executable file
21
lib/widgets/utf8_text_fields.dart
Executable file
@ -0,0 +1,21 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
int _byteLength(String value) => utf8.encode(value).length;
|
||||
|
||||
InputCounterWidgetBuilder buildCountersFor(String currentValue) =>
|
||||
(context, {required currentLength, required isFocused, maxLength}) => Text(
|
||||
maxLength != null ? '${_byteLength(currentValue)}/$maxLength' : '',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
);
|
||||
|
||||
TextInputFormatter limitBytesLength(int maxByteLength) =>
|
||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||
final newLength = _byteLength(newValue.text);
|
||||
if (newLength <= maxByteLength) {
|
||||
return newValue;
|
||||
}
|
||||
return oldValue;
|
||||
});
|
Loading…
Reference in New Issue
Block a user