Clean up device picker and avatar.

This commit is contained in:
Dain Nilsson 2022-02-01 13:30:03 +01:00
parent a8c14637f7
commit 16b492638f
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
3 changed files with 115 additions and 80 deletions

View File

@ -1,35 +1,60 @@
import 'package:flutter/material.dart';
import 'package:yubico_authenticator/management/models.dart';
import '../models.dart';
import 'device_images.dart';
/*
TODO: This class should be refactored once we settle more on the final design.
We may want to have two separate implementations depending on if it's an NFC reader or a USB YubiKey.
*/
class DeviceAvatar extends StatelessWidget {
final DeviceNode node;
final String name;
final DeviceInfo? info;
final bool selected;
const DeviceAvatar(this.node, this.name, this.info,
{this.selected = false, Key? key})
final Widget child;
final IconData? badge;
const DeviceAvatar._(
{Key? key, this.selected = false, required this.child, this.badge})
: super(key: key);
factory DeviceAvatar.yubiKeyData(YubiKeyData data, {bool selected = false}) =>
DeviceAvatar._(
child: getProductImage(data.info, data.name),
badge: data.node is NfcReaderNode ? Icons.nfc : null,
selected: selected,
);
factory DeviceAvatar.deviceNode(DeviceNode node, {bool selected = false}) =>
node.map(
usbYubiKey: (node) => DeviceAvatar.yubiKeyData(
YubiKeyData(node, node.name, node.info),
selected: selected,
),
nfcReader: (_) => DeviceAvatar._(
child: const Icon(Icons.nfc),
selected: selected,
),
);
@override
Widget build(BuildContext context) {
return CircleAvatar(
child: CircleAvatar(
child:
info != null ? getProductImage(info!, name) : const Icon(Icons.nfc),
backgroundColor: Theme.of(context).colorScheme.background,
),
radius: 22,
backgroundColor: selected
? Theme.of(context).colorScheme.secondary
: Colors.transparent,
return Stack(
alignment: AlignmentDirectional.bottomEnd,
children: [
CircleAvatar(
radius: 22,
backgroundColor: selected
? Theme.of(context).colorScheme.secondary
: Colors.transparent,
child: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.background,
child: child,
),
),
if (badge != null)
CircleAvatar(
radius: 8,
backgroundColor: Theme.of(context).colorScheme.secondary,
child: Icon(
badge!,
size: 12,
),
),
],
);
}
}

View File

@ -26,29 +26,32 @@ class MainActionsDialog extends ConsumerWidget {
return SimpleDialog(
children: [
if (currentNode != null)
CurrentDeviceRow(
_CurrentDeviceRow(
currentNode,
data?.name,
info: data?.info,
data: data,
onTap: () {
Navigator.of(context).pop();
},
),
...devices.map(
(e) => DeviceRow(
(e) => _DeviceRow(
e,
e.name,
info: e.when(
usbYubiKey: (path, name, pid, info) => info,
nfcReader: (path, name) => null,
info: e.map(
usbYubiKey: (node) => node.info,
nfcReader: (_) => null,
),
selected: false,
onTap: () {
Navigator.of(context).pop();
ref.read(currentDeviceProvider.notifier).setCurrentDevice(e);
},
),
),
if (currentNode == null && devices.isEmpty)
Center(
child: Text(
'No YubiKey found',
style: Theme.of(context).textTheme.titleMedium,
)),
if (actions.isNotEmpty) const Divider(),
...actions.map((a) => ListTile(
dense: true,
@ -64,35 +67,41 @@ class MainActionsDialog extends ConsumerWidget {
}
}
class CurrentDeviceRow extends StatelessWidget {
class _CurrentDeviceRow extends StatelessWidget {
final DeviceNode node;
final String? name;
final DeviceInfo? info;
final YubiKeyData? data;
final Function() onTap;
const CurrentDeviceRow(
this.node,
this.name, {
required this.info,
const _CurrentDeviceRow(
this.node, {
this.data,
required this.onTap,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final subtitle = node is NfcReaderNode
? info != null
? '${node.name}\nS/N: ${info!.serial} F/W: ${info!.version}'
: node.name
: 'S/N: ${info!.serial} F/W: ${info!.version}';
final subtitle = node.when(
usbYubiKey: (_, __, ___, info) =>
'S/N: ${info.serial} F/W: ${info.version}',
nfcReader: (_, name) {
final info = data?.info;
return info == null
? name
: '$name\nS/N: ${info.serial} F/W: ${info.version}';
});
return ListTile(
leading: DeviceAvatar(
node,
name ?? '',
info,
selected: true,
),
title: Text(name ?? 'No YubiKey present'),
leading: data != null
? DeviceAvatar.yubiKeyData(
data!,
selected: true,
)
: DeviceAvatar.deviceNode(
node,
selected: true,
),
title: Text(data?.name ?? 'No YubiKey present'),
isThreeLine: subtitle.contains('\n'),
subtitle: Text(subtitle),
onTap: onTap,
@ -100,36 +109,29 @@ class CurrentDeviceRow extends StatelessWidget {
}
}
class DeviceRow extends StatelessWidget {
class _DeviceRow extends StatelessWidget {
final DeviceNode node;
final String name;
final DeviceInfo? info;
final bool selected;
final Function() onTap;
const DeviceRow(
this.node,
this.name, {
const _DeviceRow(
this.node, {
required this.info,
required this.onTap,
this.selected = false,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
leading: DeviceAvatar(
node,
name,
info,
selected: selected,
),
title: Text(name),
leading: DeviceAvatar.deviceNode(node),
title: Text(node.name),
subtitle: Text(
info == null
? (selected ? 'No YubiKey present' : 'Select to scan')
: 'S/N: ${info!.serial} F/W: ${info!.version}',
node.when(
usbYubiKey: (_, __, ___, info) =>
'S/N: ${info.serial} F/W: ${info.version}',
nfcReader: (_, __) => 'Select to scan',
),
),
onTap: onTap,
);

View File

@ -28,9 +28,30 @@ class MainPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final deviceNode = ref.watch(currentDeviceProvider);
final deviceData = ref.watch(currentDeviceDataProvider);
final subPage = ref.watch(subPageProvider);
Widget deviceWidget;
if (deviceNode != null) {
if (deviceData != null) {
deviceWidget = DeviceAvatar.yubiKeyData(
deviceData,
selected: true,
);
} else {
deviceWidget = DeviceAvatar.deviceNode(
deviceNode,
selected: true,
);
}
} else {
deviceWidget = const CircleAvatar(
backgroundColor: Colors.transparent,
child: Icon(Icons.usb_off),
);
}
return Scaffold(
appBar: AppBar(
/*
@ -59,20 +80,7 @@ class MainPage extends ConsumerWidget {
InkWell(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: deviceData == null
? SizedBox.square(
dimension: 44,
child: Icon(
Icons.usb_off,
color: Theme.of(context).colorScheme.background,
),
)
: DeviceAvatar(
deviceData.node,
deviceData.name,
deviceData.info,
selected: true,
),
child: deviceWidget,
),
onTap: () {
showDialog(