mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-26 10:33:15 +03:00
Update FIDO passkeys views.
This commit is contained in:
parent
7011f816d4
commit
9eeb44f3ac
114
lib/fido/views/credential_dialog.dart
Normal file
114
lib/fido/views/credential_dialog.dart
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../app/message.dart';
|
||||||
|
import '../../app/shortcuts.dart';
|
||||||
|
import '../../app/state.dart';
|
||||||
|
import '../../app/views/fs_dialog.dart';
|
||||||
|
import '../../widgets/list_title.dart';
|
||||||
|
import '../models.dart';
|
||||||
|
import 'delete_credential_dialog.dart';
|
||||||
|
|
||||||
|
class CredentialDialog extends ConsumerWidget {
|
||||||
|
final FidoCredential credential;
|
||||||
|
const CredentialDialog(this.credential, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
// TODO: Solve this in a cleaner way
|
||||||
|
final node = ref.watch(currentDeviceDataProvider).valueOrNull?.node;
|
||||||
|
if (node == null) {
|
||||||
|
// The rest of this method assumes there is a device, and will throw an exception if not.
|
||||||
|
// This will never be shown, as the dialog will be immediately closed
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Actions(
|
||||||
|
actions: {
|
||||||
|
DeleteIntent: CallbackAction<DeleteIntent>(onInvoke: (_) async {
|
||||||
|
final withContext = ref.read(withContextProvider);
|
||||||
|
final bool? deleted =
|
||||||
|
await ref.read(withContextProvider)((context) async =>
|
||||||
|
await showBlurDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DeleteCredentialDialog(
|
||||||
|
node.path,
|
||||||
|
credential,
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
false);
|
||||||
|
|
||||||
|
// Pop the account dialog if deleted
|
||||||
|
if (deleted == true) {
|
||||||
|
await withContext((context) async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
child: FocusScope(
|
||||||
|
autofocus: true,
|
||||||
|
child: FsDialog(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 48, bottom: 32),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
credential.userName,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
softWrap: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
credential.rpId,
|
||||||
|
softWrap: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
// This is what ListTile uses for subtitle
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Icon(Icons.person, size: 72),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTitle(AppLocalizations.of(context)!.s_actions,
|
||||||
|
textStyle: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
_CredentialDialogActions(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CredentialDialogActions extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
final theme =
|
||||||
|
ButtonTheme.of(context).colorScheme ?? Theme.of(context).colorScheme;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: theme.error,
|
||||||
|
foregroundColor: theme.onError,
|
||||||
|
child: const Icon(Icons.delete),
|
||||||
|
),
|
||||||
|
title: Text(l10n.s_delete_passkey),
|
||||||
|
subtitle: Text(l10n.l_delete_account_desc),
|
||||||
|
onTap: () {
|
||||||
|
Actions.invoke(context, const DeleteIntent());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -38,14 +38,14 @@ class DeleteCredentialDialog extends ConsumerWidget {
|
|||||||
final label = credential.userName;
|
final label = credential.userName;
|
||||||
|
|
||||||
return ResponsiveDialog(
|
return ResponsiveDialog(
|
||||||
title: Text(l10n.s_delete_credential),
|
title: Text(l10n.s_delete_passkey),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.p_warning_delete_credential),
|
Text(l10n.p_warning_delete_passkey),
|
||||||
Text(l10n.l_credential(label)),
|
Text(l10n.l_passkey(label)),
|
||||||
]
|
]
|
||||||
.map((e) => Padding(
|
.map((e) => Padding(
|
||||||
child: e,
|
child: e,
|
||||||
@ -63,7 +63,7 @@ class DeleteCredentialDialog extends ConsumerWidget {
|
|||||||
await ref.read(withContextProvider)(
|
await ref.read(withContextProvider)(
|
||||||
(context) async {
|
(context) async {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
showMessage(context, l10n.s_credential_deleted);
|
showMessage(context, l10n.s_passkey_deleted);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -27,7 +27,7 @@ import '../../app/views/message_page.dart';
|
|||||||
import '../../widgets/list_title.dart';
|
import '../../widgets/list_title.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'delete_credential_dialog.dart';
|
import 'credential_dialog.dart';
|
||||||
import 'fingerprint_dialog.dart';
|
import 'fingerprint_dialog.dart';
|
||||||
import 'key_actions.dart';
|
import 'key_actions.dart';
|
||||||
|
|
||||||
@ -48,42 +48,19 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
final creds = data.value;
|
final creds = data.value;
|
||||||
if (creds.isNotEmpty) {
|
if (creds.isNotEmpty) {
|
||||||
children.add(ListTitle(l10n.s_credentials));
|
children.add(ListTitle(l10n.s_passkeys));
|
||||||
children.addAll(
|
children.addAll(creds.map((cred) => Actions(
|
||||||
creds.map(
|
actions: {
|
||||||
(cred) => ListTile(
|
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) async {
|
||||||
leading: CircleAvatar(
|
await showBlurDialog(
|
||||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
context: context,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
builder: (context) => CredentialDialog(cred),
|
||||||
child: const Icon(Icons.person),
|
);
|
||||||
),
|
return null;
|
||||||
title: Text(
|
}),
|
||||||
cred.userName,
|
},
|
||||||
softWrap: false,
|
child: _CredentialListItem(cred),
|
||||||
overflow: TextOverflow.fade,
|
)));
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
cred.rpId,
|
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showBlurDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) =>
|
|
||||||
DeleteCredentialDialog(node.path, cred),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete_outline)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +130,38 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CredentialListItem extends StatelessWidget {
|
||||||
|
final FidoCredential credential;
|
||||||
|
const _CredentialListItem(this.credential);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
child: const Icon(Icons.person),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
credential.userName,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
credential.rpId,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
trailing: OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Actions.maybeInvoke<OpenIntent>(context, const OpenIntent());
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.more_horiz),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _FingerprintListItem extends StatelessWidget {
|
class _FingerprintListItem extends StatelessWidget {
|
||||||
final Fingerprint fingerprint;
|
final Fingerprint fingerprint;
|
||||||
const _FingerprintListItem(this.fingerprint);
|
const _FingerprintListItem(this.fingerprint);
|
||||||
|
@ -291,19 +291,20 @@
|
|||||||
"l_calculate_code_desc": "Get a new code from your YubiKey",
|
"l_calculate_code_desc": "Get a new code from your YubiKey",
|
||||||
|
|
||||||
"@_fido_credentials": {},
|
"@_fido_credentials": {},
|
||||||
"l_credential": "Credential: {label}",
|
"l_passkey": "Passkey: {label}",
|
||||||
"@l_credential" : {
|
"@l_passkey" : {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"label": {}
|
"label": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"s_credentials": "Credentials",
|
"s_passkeys": "Passkeys",
|
||||||
"l_ready_to_use": "Ready to use",
|
"l_ready_to_use": "Ready to use",
|
||||||
"l_register_sk_on_websites": "Register as a Security Key on websites",
|
"l_register_sk_on_websites": "Register as a Security Key on websites",
|
||||||
"l_no_discoverable_accounts": "No discoverable accounts",
|
"l_no_discoverable_accounts": "No Passkeys stored",
|
||||||
"s_delete_credential": "Delete credential",
|
"s_delete_passkey": "Delete Passkey",
|
||||||
"s_credential_deleted": "Credential deleted",
|
"l_delete_passkey_desc": "Remove the Passkey from the YubiKey",
|
||||||
"p_warning_delete_credential": "This will delete the credential from your YubiKey.",
|
"s_passkey_deleted": "Passkey deleted",
|
||||||
|
"p_warning_delete_passkey": "This will delete the Passkey from your YubiKey.",
|
||||||
|
|
||||||
"@_fingerprints": {},
|
"@_fingerprints": {},
|
||||||
"l_fingerprint": "Fingerprint: {label}",
|
"l_fingerprint": "Fingerprint: {label}",
|
||||||
|
Loading…
Reference in New Issue
Block a user