mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-24 02:33:44 +03:00
136 lines
4.6 KiB
Dart
136 lines
4.6 KiB
Dart
|
import 'package:flutter/material.dart';
|
||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
import 'package:material_symbols_icons/symbols.dart';
|
||
|
|
||
|
import '../../app/models.dart';
|
||
|
import '../../core/models.dart';
|
||
|
import '../../widgets/app_input_decoration.dart';
|
||
|
import '../../widgets/app_text_field.dart';
|
||
|
import '../../widgets/responsive_dialog.dart';
|
||
|
import '../models.dart';
|
||
|
|
||
|
class AccessCodeDialog extends ConsumerStatefulWidget {
|
||
|
final DevicePath devicePath;
|
||
|
final OtpSlot otpSlot;
|
||
|
final Future<void> Function(String accessCode) action;
|
||
|
const AccessCodeDialog(
|
||
|
{super.key,
|
||
|
required this.devicePath,
|
||
|
required this.otpSlot,
|
||
|
required this.action});
|
||
|
|
||
|
@override
|
||
|
ConsumerState<AccessCodeDialog> createState() => _AccessCodeDialogState();
|
||
|
}
|
||
|
|
||
|
class _AccessCodeDialogState extends ConsumerState<AccessCodeDialog> {
|
||
|
final _accessCodeController = TextEditingController();
|
||
|
final _accessCodeFocus = FocusNode();
|
||
|
bool _accessCodeIsWrong = false;
|
||
|
String _accessCodeError = '';
|
||
|
bool _isObscure = true;
|
||
|
final accessCodeLength = 12;
|
||
|
|
||
|
@override
|
||
|
void dispose() {
|
||
|
_accessCodeController.dispose();
|
||
|
_accessCodeFocus.dispose();
|
||
|
super.dispose();
|
||
|
}
|
||
|
|
||
|
void _submit() async {
|
||
|
final l10n = AppLocalizations.of(context)!;
|
||
|
if (!Format.hex.isValid(_accessCodeController.text)) {
|
||
|
_accessCodeController.selection = TextSelection(
|
||
|
baseOffset: 0, extentOffset: _accessCodeController.text.length);
|
||
|
_accessCodeFocus.requestFocus();
|
||
|
setState(() {
|
||
|
_accessCodeError =
|
||
|
l10n.l_invalid_format_allowed_chars(Format.hex.allowedCharacters);
|
||
|
_accessCodeIsWrong = true;
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
final navigator = Navigator.of(context);
|
||
|
await widget.action(_accessCodeController.text);
|
||
|
navigator.pop(true);
|
||
|
} catch (e) {
|
||
|
_accessCodeController.selection = TextSelection(
|
||
|
baseOffset: 0, extentOffset: _accessCodeController.text.length);
|
||
|
_accessCodeFocus.requestFocus();
|
||
|
setState(() {
|
||
|
_accessCodeIsWrong = true;
|
||
|
_accessCodeError = l10n.l_wrong_access_code;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
final l10n = AppLocalizations.of(context)!;
|
||
|
final accessCode = _accessCodeController.text.replaceAll(' ', '');
|
||
|
final accessCodeLengthValid =
|
||
|
accessCode.isNotEmpty && accessCode.length == accessCodeLength;
|
||
|
return ResponsiveDialog(
|
||
|
title: Text(l10n.s_access_code),
|
||
|
actions: [
|
||
|
TextButton(
|
||
|
onPressed: accessCodeLengthValid ? _submit : null,
|
||
|
child: Text(l10n.s_unlock),
|
||
|
)
|
||
|
],
|
||
|
child: Padding(
|
||
|
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||
|
child: Column(
|
||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
children: [
|
||
|
Text(l10n.p_enter_access_code(
|
||
|
widget.otpSlot.slot.numberId.toString())),
|
||
|
AppTextField(
|
||
|
autofocus: true,
|
||
|
obscureText: _isObscure,
|
||
|
maxLength: accessCodeLength,
|
||
|
autofillHints: const [AutofillHints.password],
|
||
|
controller: _accessCodeController,
|
||
|
focusNode: _accessCodeFocus,
|
||
|
decoration: AppInputDecoration(
|
||
|
border: const OutlineInputBorder(),
|
||
|
labelText: l10n.s_access_code,
|
||
|
errorText: _accessCodeIsWrong ? _accessCodeError : null,
|
||
|
errorMaxLines: 3,
|
||
|
prefixIcon: const Icon(Symbols.pin),
|
||
|
suffixIcon: IconButton(
|
||
|
icon: Icon(_isObscure
|
||
|
? Symbols.visibility
|
||
|
: Symbols.visibility_off),
|
||
|
onPressed: () {
|
||
|
setState(() {
|
||
|
_isObscure = !_isObscure;
|
||
|
});
|
||
|
},
|
||
|
tooltip: _isObscure
|
||
|
? l10n.s_show_access_code
|
||
|
: l10n.s_hide_access_code,
|
||
|
),
|
||
|
),
|
||
|
textInputAction: TextInputAction.next,
|
||
|
onChanged: (value) {
|
||
|
setState(() {
|
||
|
_accessCodeIsWrong = false;
|
||
|
});
|
||
|
},
|
||
|
onSubmitted: (_) => _submit(),
|
||
|
).init(),
|
||
|
]
|
||
|
.map((e) => Padding(
|
||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||
|
child: e,
|
||
|
))
|
||
|
.toList(),
|
||
|
)),
|
||
|
);
|
||
|
}
|
||
|
}
|