Update FIDO passkeys views.

This commit is contained in:
Dain Nilsson 2023-05-04 16:20:50 +02:00
parent 68b482c23d
commit e54b0602a8
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
4 changed files with 172 additions and 48 deletions

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

View File

@ -38,14 +38,14 @@ class DeleteCredentialDialog extends ConsumerWidget {
final label = credential.userName;
return ResponsiveDialog(
title: Text(l10n.s_delete_credential),
title: Text(l10n.s_delete_passkey),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.p_warning_delete_credential),
Text(l10n.l_credential(label)),
Text(l10n.p_warning_delete_passkey),
Text(l10n.l_passkey(label)),
]
.map((e) => Padding(
child: e,
@ -63,7 +63,7 @@ class DeleteCredentialDialog extends ConsumerWidget {
await ref.read(withContextProvider)(
(context) async {
Navigator.of(context).pop(true);
showMessage(context, l10n.s_credential_deleted);
showMessage(context, l10n.s_passkey_deleted);
},
);
},

View File

@ -27,7 +27,7 @@ import '../../app/views/message_page.dart';
import '../../widgets/list_title.dart';
import '../models.dart';
import '../state.dart';
import 'delete_credential_dialog.dart';
import 'credential_dialog.dart';
import 'fingerprint_dialog.dart';
import 'key_actions.dart';
@ -48,42 +48,19 @@ class FidoUnlockedPage extends ConsumerWidget {
}
final creds = data.value;
if (creds.isNotEmpty) {
children.add(ListTitle(l10n.s_credentials));
children.addAll(
creds.map(
(cred) => ListTile(
leading: CircleAvatar(
foregroundColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
child: const Icon(Icons.person),
),
title: Text(
cred.userName,
softWrap: false,
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)),
],
),
),
),
);
children.add(ListTitle(l10n.s_passkeys));
children.addAll(creds.map((cred) => Actions(
actions: {
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) async {
await showBlurDialog(
context: context,
builder: (context) => CredentialDialog(cred),
);
return null;
}),
},
child: _CredentialListItem(cred),
)));
}
}
@ -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 {
final Fingerprint fingerprint;
const _FingerprintListItem(this.fingerprint);

View File

@ -291,19 +291,20 @@
"l_calculate_code_desc": "Get a new code from your YubiKey",
"@_fido_credentials": {},
"l_credential": "Credential: {label}",
"@l_credential" : {
"l_passkey": "Passkey: {label}",
"@l_passkey" : {
"placeholders": {
"label": {}
}
},
"s_credentials": "Credentials",
"s_passkeys": "Passkeys",
"l_ready_to_use": "Ready to use",
"l_register_sk_on_websites": "Register as a Security Key on websites",
"l_no_discoverable_accounts": "No discoverable accounts",
"s_delete_credential": "Delete credential",
"s_credential_deleted": "Credential deleted",
"p_warning_delete_credential": "This will delete the credential from your YubiKey.",
"l_no_discoverable_accounts": "No Passkeys stored",
"s_delete_passkey": "Delete Passkey",
"l_delete_passkey_desc": "Remove the Passkey from the YubiKey",
"s_passkey_deleted": "Passkey deleted",
"p_warning_delete_passkey": "This will delete the Passkey from your YubiKey.",
"@_fingerprints": {},
"l_fingerprint": "Fingerprint: {label}",