mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
Add access code dialog for OTP
This commit is contained in:
parent
bb973bb508
commit
e5435e78a6
@ -65,7 +65,10 @@ class YubiOtpNode(RpcNode):
|
||||
|
||||
@action
|
||||
def swap(self, params, event, signal):
|
||||
self.session.swap_slots()
|
||||
try:
|
||||
self.session.swap_slots()
|
||||
except CommandError:
|
||||
raise ValueError(_FAIL_MSG)
|
||||
return dict()
|
||||
|
||||
@child
|
||||
@ -148,7 +151,9 @@ class SlotNode(RpcNode):
|
||||
@action(condition=lambda self: self._maybe_configured(self.slot))
|
||||
def delete(self, params, event, signal):
|
||||
try:
|
||||
self.session.delete_slot(self.slot, params.pop("cur_acc_code", None))
|
||||
access_code = params.pop("curr_acc_code", None)
|
||||
access_code = bytes.fromhex(access_code) if access_code else None
|
||||
self.session.delete_slot(self.slot, access_code)
|
||||
except CommandError:
|
||||
raise ValueError(_FAIL_MSG)
|
||||
|
||||
@ -218,6 +223,8 @@ class SlotNode(RpcNode):
|
||||
def put(self, params, event, signal):
|
||||
type = params.pop("type")
|
||||
options = params.pop("options", {})
|
||||
access_code = params.pop("curr_acc_code", None)
|
||||
access_code = bytes.fromhex(access_code) if access_code else None
|
||||
args = params
|
||||
|
||||
config = self._get_config(type, **args)
|
||||
@ -226,8 +233,8 @@ class SlotNode(RpcNode):
|
||||
self.session.put_configuration(
|
||||
self.slot,
|
||||
config,
|
||||
params.pop("acc_code", None),
|
||||
params.pop("cur_acc_code", None),
|
||||
access_code,
|
||||
access_code,
|
||||
)
|
||||
return dict()
|
||||
except CommandError:
|
||||
|
@ -117,16 +117,21 @@ class _DesktopOtpStateNotifier extends OtpStateNotifier {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteSlot(SlotId slot) async {
|
||||
await _session.command('delete', target: [..._subpath, slot.id]);
|
||||
Future<void> deleteSlot(SlotId slot, {String? accessCode}) async {
|
||||
await _session.command('delete',
|
||||
target: [..._subpath, slot.id],
|
||||
params: accessCode != null ? {'curr_acc_code': accessCode} : null);
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> configureSlot(SlotId slot,
|
||||
{required SlotConfiguration configuration}) async {
|
||||
{required SlotConfiguration configuration, String? accessCode}) async {
|
||||
await _session.command('put',
|
||||
target: [..._subpath, slot.id], params: configuration.toJson());
|
||||
target: [..._subpath, slot.id],
|
||||
params: accessCode != null
|
||||
? {...configuration.toJson(), 'curr_acc_code': accessCode}
|
||||
: configuration.toJson());
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
@ -701,6 +701,19 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"p_otp_swap_error": null,
|
||||
"l_wrong_access_code": null,
|
||||
|
||||
"@_otp_access_code": {},
|
||||
"s_access_code": null,
|
||||
"s_show_access_code": null,
|
||||
"s_hide_access_code": null,
|
||||
"p_enter_access_code": null,
|
||||
"@p_enter_access_code": {
|
||||
"placeholders": {
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"@_permissions": {},
|
||||
|
@ -701,6 +701,19 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"p_otp_swap_error": "Failed to swap slots! Make sure the YubiKey does not have restrictive access.",
|
||||
"l_wrong_access_code": "Wrong access code",
|
||||
|
||||
"@_otp_access_code": {},
|
||||
"s_access_code": "Access code",
|
||||
"s_show_access_code": "Show access code",
|
||||
"s_hide_access_code": "Hide access code",
|
||||
"p_enter_access_code": "Enter access code for slot {slot}.",
|
||||
"@p_enter_access_code": {
|
||||
"placeholders": {
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"@_permissions": {},
|
||||
|
@ -701,6 +701,19 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"p_otp_swap_error": null,
|
||||
"l_wrong_access_code": null,
|
||||
|
||||
"@_otp_access_code": {},
|
||||
"s_access_code": null,
|
||||
"s_show_access_code": null,
|
||||
"s_hide_access_code": null,
|
||||
"p_enter_access_code": null,
|
||||
"@p_enter_access_code": {
|
||||
"placeholders": {
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"@_permissions": {},
|
||||
|
@ -701,6 +701,19 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"p_otp_swap_error": null,
|
||||
"l_wrong_access_code": null,
|
||||
|
||||
"@_otp_access_code": {},
|
||||
"s_access_code": null,
|
||||
"s_show_access_code": null,
|
||||
"s_hide_access_code": null,
|
||||
"p_enter_access_code": null,
|
||||
"@p_enter_access_code": {
|
||||
"placeholders": {
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"@_permissions": {},
|
||||
|
@ -701,6 +701,19 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"p_otp_swap_error": null,
|
||||
"l_wrong_access_code": null,
|
||||
|
||||
"@_otp_access_code": {},
|
||||
"s_access_code": null,
|
||||
"s_show_access_code": null,
|
||||
"s_hide_access_code": null,
|
||||
"p_enter_access_code": null,
|
||||
"@p_enter_access_code": {
|
||||
"placeholders": {
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"@_permissions": {},
|
||||
|
@ -47,6 +47,6 @@ abstract class OtpStateNotifier extends ApplicationStateNotifier<OtpState> {
|
||||
int serial, String publicId, String privateId, String key);
|
||||
Future<void> swapSlots();
|
||||
Future<void> configureSlot(SlotId slot,
|
||||
{required SlotConfiguration configuration});
|
||||
Future<void> deleteSlot(SlotId slot);
|
||||
{required SlotConfiguration configuration, String? accessCode});
|
||||
Future<void> deleteSlot(SlotId slot, {String? accessCode});
|
||||
}
|
||||
|
135
lib/otp/views/access_code_dialog.dart
Normal file
135
lib/otp/views/access_code_dialog.dart
Normal file
@ -0,0 +1,135 @@
|
||||
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(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import '../../widgets/responsive_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'access_code_dialog.dart';
|
||||
import 'overwrite_confirm_dialog.dart';
|
||||
|
||||
final _log = Logger('otp.view.configure_chalresp_dialog');
|
||||
@ -91,30 +92,45 @@ class _ConfigureChalrespDialogState
|
||||
|
||||
final otpNotifier =
|
||||
ref.read(otpStateProvider(widget.devicePath).notifier);
|
||||
final configuration = SlotConfiguration.chalresp(
|
||||
key: secret,
|
||||
options: SlotConfigurationOptions(
|
||||
requireTouch: _requireTouch));
|
||||
|
||||
bool configurationSucceded = false;
|
||||
try {
|
||||
await otpNotifier.configureSlot(widget.otpSlot.slot,
|
||||
configuration: SlotConfiguration.chalresp(
|
||||
key: secret,
|
||||
options: SlotConfigurationOptions(
|
||||
requireTouch: _requireTouch)));
|
||||
configuration: configuration);
|
||||
configurationSucceded = true;
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
// Access code required
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
final result = await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => AccessCodeDialog(
|
||||
devicePath: widget.devicePath,
|
||||
otpSlot: widget.otpSlot,
|
||||
action: (accessCode) async {
|
||||
await otpNotifier.configureSlot(
|
||||
widget.otpSlot.slot,
|
||||
configuration: configuration,
|
||||
accessCode: accessCode);
|
||||
},
|
||||
));
|
||||
configurationSucceded = result ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
if (configurationSucceded) {
|
||||
showMessage(
|
||||
context,
|
||||
l10n.l_slot_credential_configured(
|
||||
l10n.s_challenge_response));
|
||||
});
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
showMessage(
|
||||
context,
|
||||
l10n.p_otp_slot_configuration_error(
|
||||
widget.otpSlot.slot.getDisplayName(l10n)),
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.s_save),
|
||||
|
@ -34,6 +34,7 @@ import '../../widgets/responsive_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'access_code_dialog.dart';
|
||||
import 'overwrite_confirm_dialog.dart';
|
||||
|
||||
final _log = Logger('otp.view.configure_hotp_dialog');
|
||||
@ -90,29 +91,43 @@ class _ConfigureHotpDialogState extends ConsumerState<ConfigureHotpDialog> {
|
||||
|
||||
final otpNotifier =
|
||||
ref.read(otpStateProvider(widget.devicePath).notifier);
|
||||
final configuration = SlotConfiguration.hotp(
|
||||
key: secret,
|
||||
options: SlotConfigurationOptions(
|
||||
digits8: _digits == 8, appendCr: _appendEnter));
|
||||
|
||||
bool configurationSucceded = false;
|
||||
try {
|
||||
await otpNotifier.configureSlot(widget.otpSlot.slot,
|
||||
configuration: SlotConfiguration.hotp(
|
||||
key: secret,
|
||||
options: SlotConfigurationOptions(
|
||||
digits8: _digits == 8,
|
||||
appendCr: _appendEnter)));
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context,
|
||||
l10n.l_slot_credential_configured(l10n.s_hotp));
|
||||
});
|
||||
configuration: configuration);
|
||||
configurationSucceded = true;
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
// Access code required
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
showMessage(
|
||||
context,
|
||||
l10n.p_otp_slot_configuration_error(
|
||||
widget.otpSlot.slot.getDisplayName(l10n)),
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
final result = await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => AccessCodeDialog(
|
||||
devicePath: widget.devicePath,
|
||||
otpSlot: widget.otpSlot,
|
||||
action: (accessCode) async {
|
||||
await otpNotifier.configureSlot(
|
||||
widget.otpSlot.slot,
|
||||
configuration: configuration,
|
||||
accessCode: accessCode);
|
||||
},
|
||||
));
|
||||
configurationSucceded = result ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
if (configurationSucceded) {
|
||||
showMessage(context,
|
||||
l10n.l_slot_credential_configured(l10n.s_hotp));
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.s_save),
|
||||
|
@ -32,6 +32,7 @@ import '../../widgets/responsive_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'access_code_dialog.dart';
|
||||
import 'overwrite_confirm_dialog.dart';
|
||||
|
||||
final _log = Logger('otp.view.configure_static_dialog');
|
||||
@ -110,31 +111,46 @@ class _ConfigureStaticDialogState extends ConsumerState<ConfigureStaticDialog> {
|
||||
|
||||
final otpNotifier =
|
||||
ref.read(otpStateProvider(widget.devicePath).notifier);
|
||||
final configuration = SlotConfiguration.static(
|
||||
password: password,
|
||||
keyboardLayout: _keyboardLayout,
|
||||
options:
|
||||
SlotConfigurationOptions(appendCr: _appendEnter));
|
||||
|
||||
bool configurationSucceded = false;
|
||||
try {
|
||||
await otpNotifier.configureSlot(widget.otpSlot.slot,
|
||||
configuration: SlotConfiguration.static(
|
||||
password: password,
|
||||
keyboardLayout: _keyboardLayout,
|
||||
options: SlotConfigurationOptions(
|
||||
appendCr: _appendEnter)));
|
||||
configuration: configuration);
|
||||
configurationSucceded = true;
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
// Access code required
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
final result = await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => AccessCodeDialog(
|
||||
devicePath: widget.devicePath,
|
||||
otpSlot: widget.otpSlot,
|
||||
action: (accessCode) async {
|
||||
await otpNotifier.configureSlot(
|
||||
widget.otpSlot.slot,
|
||||
configuration: configuration,
|
||||
accessCode: accessCode);
|
||||
},
|
||||
));
|
||||
configurationSucceded = result ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
if (configurationSucceded) {
|
||||
showMessage(
|
||||
context,
|
||||
l10n.l_slot_credential_configured(
|
||||
l10n.s_static_password));
|
||||
});
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
showMessage(
|
||||
context,
|
||||
l10n.p_otp_slot_configuration_error(
|
||||
widget.otpSlot.slot.getDisplayName(l10n)),
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.s_save),
|
||||
|
@ -39,6 +39,7 @@ import '../../widgets/responsive_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'access_code_dialog.dart';
|
||||
import 'overwrite_confirm_dialog.dart';
|
||||
|
||||
final _log = Logger('otp.view.configure_yubiotp_dialog');
|
||||
@ -153,14 +154,39 @@ class _ConfigureYubiOtpDialogState
|
||||
|
||||
final otpNotifier =
|
||||
ref.read(otpStateProvider(widget.devicePath).notifier);
|
||||
final configuration = SlotConfiguration.yubiotp(
|
||||
publicId: publicId,
|
||||
privateId: privateId,
|
||||
key: secret,
|
||||
options:
|
||||
SlotConfigurationOptions(appendCr: _appendEnter));
|
||||
|
||||
bool configurationSucceded = false;
|
||||
try {
|
||||
await otpNotifier.configureSlot(widget.otpSlot.slot,
|
||||
configuration: SlotConfiguration.yubiotp(
|
||||
publicId: publicId,
|
||||
privateId: privateId,
|
||||
key: secret,
|
||||
options: SlotConfigurationOptions(
|
||||
appendCr: _appendEnter)));
|
||||
configuration: configuration);
|
||||
configurationSucceded = true;
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
// Access code required
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
final result = await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => AccessCodeDialog(
|
||||
devicePath: widget.devicePath,
|
||||
otpSlot: widget.otpSlot,
|
||||
action: (accessCode) async {
|
||||
await otpNotifier.configureSlot(
|
||||
widget.otpSlot.slot,
|
||||
configuration: configuration,
|
||||
accessCode: accessCode);
|
||||
},
|
||||
));
|
||||
configurationSucceded = result ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
if (configurationSucceded) {
|
||||
if (outputFile != null) {
|
||||
final csv = await otpNotifier.formatYubiOtpCsv(
|
||||
info!.serial!, publicId, privateId, secret);
|
||||
@ -169,8 +195,10 @@ class _ConfigureYubiOtpDialogState
|
||||
'$csv${Platform.lineTerminator}',
|
||||
mode: FileMode.append);
|
||||
}
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
if (configurationSucceded) {
|
||||
showMessage(
|
||||
context,
|
||||
outputFile != null
|
||||
@ -179,24 +207,8 @@ class _ConfigureYubiOtpDialogState
|
||||
outputFile.uri.pathSegments.last)
|
||||
: l10n.l_slot_credential_configured(
|
||||
l10n.s_capability_otp));
|
||||
});
|
||||
} catch (e) {
|
||||
_log.error('Failed to program credential', e);
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
final String errorMessage;
|
||||
if (e is PathNotFoundException) {
|
||||
errorMessage = '${e.message} ${e.path.toString()}';
|
||||
} else {
|
||||
errorMessage = l10n.p_otp_slot_configuration_error(
|
||||
widget.otpSlot.slot.getDisplayName(l10n));
|
||||
}
|
||||
showMessage(
|
||||
context,
|
||||
errorMessage,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.s_save),
|
||||
|
@ -25,6 +25,7 @@ import '../../widgets/responsive_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'access_code_dialog.dart';
|
||||
|
||||
class DeleteSlotDialog extends ConsumerWidget {
|
||||
final DevicePath devicePath;
|
||||
@ -40,25 +41,34 @@ class DeleteSlotDialog extends ConsumerWidget {
|
||||
TextButton(
|
||||
key: keys.deleteButton,
|
||||
onPressed: () async {
|
||||
final otpStateNotifier =
|
||||
ref.read(otpStateProvider(devicePath).notifier);
|
||||
bool deleteSucceeded = false;
|
||||
try {
|
||||
await ref
|
||||
.read(otpStateProvider(devicePath).notifier)
|
||||
.deleteSlot(otpSlot.slot);
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop(true);
|
||||
showMessage(context, l10n.l_slot_deleted);
|
||||
});
|
||||
await otpStateNotifier.deleteSlot(otpSlot.slot);
|
||||
deleteSucceeded = true;
|
||||
} catch (e) {
|
||||
// Access code required
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop(true);
|
||||
showMessage(
|
||||
context,
|
||||
l10n.p_otp_slot_configuration_error(
|
||||
otpSlot.slot.getDisplayName(l10n)),
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
final result = await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => AccessCodeDialog(
|
||||
devicePath: devicePath,
|
||||
otpSlot: otpSlot,
|
||||
action: (accessCode) async {
|
||||
await otpStateNotifier.deleteSlot(otpSlot.slot,
|
||||
accessCode: accessCode);
|
||||
},
|
||||
));
|
||||
deleteSucceeded = result ?? false;
|
||||
});
|
||||
}
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop(true);
|
||||
if (deleteSucceeded) {
|
||||
showMessage(context, l10n.l_slot_deleted);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Text(l10n.s_delete),
|
||||
)
|
||||
|
@ -38,11 +38,20 @@ class SwapSlotsDialog extends ConsumerWidget {
|
||||
TextButton(
|
||||
key: swapButton,
|
||||
onPressed: () async {
|
||||
await ref.read(otpStateProvider(devicePath).notifier).swapSlots();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.l_slots_swapped);
|
||||
});
|
||||
try {
|
||||
await ref
|
||||
.read(otpStateProvider(devicePath).notifier)
|
||||
.swapSlots();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.l_slots_swapped);
|
||||
});
|
||||
} catch (e) {
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.p_otp_swap_error);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text(l10n.s_swap))
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user