yubioath-flutter/lib/otp/views/access_code_dialog.dart
2024-03-27 16:46:40 +01:00

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(),
)),
);
}
}