mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-24 02:33:44 +03:00
Add gradient behind device image in picker.
This commit is contained in:
parent
9a753ffcc9
commit
f4838850e7
@ -5,46 +5,34 @@ import '../models.dart';
|
||||
import 'device_images.dart';
|
||||
|
||||
class DeviceAvatar extends StatelessWidget {
|
||||
final bool selected;
|
||||
final Widget child;
|
||||
final Widget? badge;
|
||||
final double? radius;
|
||||
const DeviceAvatar(
|
||||
{super.key,
|
||||
this.selected = false,
|
||||
required this.child,
|
||||
this.badge,
|
||||
this.radius});
|
||||
const DeviceAvatar({super.key, required this.child, this.badge, this.radius});
|
||||
|
||||
factory DeviceAvatar.yubiKeyData(YubiKeyData data,
|
||||
{bool selected = false, double? radius}) =>
|
||||
factory DeviceAvatar.yubiKeyData(YubiKeyData data, {double? radius}) =>
|
||||
DeviceAvatar(
|
||||
badge: data.node is NfcReaderNode ? nfcIcon : null,
|
||||
selected: selected,
|
||||
radius: radius,
|
||||
child: getProductImage(data.info, data.name),
|
||||
);
|
||||
|
||||
factory DeviceAvatar.deviceNode(DeviceNode node,
|
||||
{bool selected = false, double? radius}) =>
|
||||
factory DeviceAvatar.deviceNode(DeviceNode node, {double? radius}) =>
|
||||
node.map(
|
||||
usbYubiKey: (node) {
|
||||
final info = node.info;
|
||||
if (info != null) {
|
||||
return DeviceAvatar.yubiKeyData(
|
||||
YubiKeyData(node, node.name, info),
|
||||
selected: selected,
|
||||
radius: radius,
|
||||
);
|
||||
}
|
||||
return DeviceAvatar(
|
||||
selected: selected,
|
||||
radius: radius,
|
||||
child: const Icon(Icons.device_unknown),
|
||||
);
|
||||
},
|
||||
nfcReader: (_) => DeviceAvatar(
|
||||
selected: selected,
|
||||
radius: radius,
|
||||
child: nfcIcon,
|
||||
),
|
||||
@ -52,17 +40,12 @@ class DeviceAvatar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final radius = this.radius ?? 24;
|
||||
final radius = this.radius ?? 20;
|
||||
return Stack(
|
||||
alignment: AlignmentDirectional.bottomEnd,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: selected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.transparent,
|
||||
child: CircleAvatar(
|
||||
radius: radius - 1,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
child: IconTheme(
|
||||
data: IconTheme.of(context).copyWith(
|
||||
@ -71,7 +54,6 @@ class DeviceAvatar extends StatelessWidget {
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (badge != null)
|
||||
CircleAvatar(
|
||||
radius: radius / 3,
|
||||
|
@ -18,19 +18,16 @@ class DeviceButton extends ConsumerWidget {
|
||||
deviceWidget = ref.watch(currentDeviceDataProvider).maybeWhen(
|
||||
data: (data) => DeviceAvatar.yubiKeyData(
|
||||
data,
|
||||
selected: true,
|
||||
radius: radius,
|
||||
radius: radius - 1,
|
||||
),
|
||||
orElse: () => DeviceAvatar.deviceNode(
|
||||
deviceNode,
|
||||
selected: true,
|
||||
radius: radius,
|
||||
radius: radius - 1,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
deviceWidget = DeviceAvatar(
|
||||
radius: radius,
|
||||
selected: true,
|
||||
radius: radius - 1,
|
||||
child: const Icon(Icons.usb),
|
||||
);
|
||||
}
|
||||
@ -41,8 +38,16 @@ class DeviceButton extends ConsumerWidget {
|
||||
icon: OverflowBox(
|
||||
maxHeight: 44,
|
||||
maxWidth: 44,
|
||||
child: CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
child: IconTheme(
|
||||
// Force the standard icon theme
|
||||
data: IconTheme.of(context),
|
||||
child: deviceWidget,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showBlurDialog(
|
||||
context: context,
|
||||
|
@ -10,7 +10,7 @@ import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'device_avatar.dart';
|
||||
|
||||
String _getSubtitle(DeviceInfo info) {
|
||||
String _getInfoString(DeviceInfo info) {
|
||||
final serial = info.serial;
|
||||
var subtitle = '';
|
||||
if (serial != null) {
|
||||
@ -64,11 +64,12 @@ class DevicePickerDialog extends ConsumerWidget {
|
||||
} else {
|
||||
hero = Column(
|
||||
children: [
|
||||
DeviceAvatar(
|
||||
selected: true,
|
||||
_HeroAvatar(
|
||||
child: DeviceAvatar(
|
||||
radius: 64,
|
||||
child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title:
|
||||
Center(child: Text(Platform.isAndroid ? 'No YubiKey' : 'USB')),
|
||||
@ -88,15 +89,11 @@ class DevicePickerDialog extends ConsumerWidget {
|
||||
ListTile(
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: DeviceAvatar(
|
||||
radius: 20,
|
||||
child: Icon(Icons.usb),
|
||||
),
|
||||
child: DeviceAvatar(child: Icon(Icons.usb)),
|
||||
),
|
||||
title: const Text('USB'),
|
||||
subtitle: const Text('No YubiKey present'),
|
||||
onTap: () {
|
||||
//Navigator.of(context).pop();
|
||||
ref.read(currentDeviceProvider.notifier).setCurrentDevice(null);
|
||||
},
|
||||
),
|
||||
@ -149,6 +146,37 @@ class DevicePickerDialog extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _HeroAvatar extends StatelessWidget {
|
||||
final Widget child;
|
||||
const _HeroAvatar({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
theme.colorScheme.background,
|
||||
theme.colorScheme.background.withOpacity(0.4),
|
||||
(theme.dialogTheme.backgroundColor ?? theme.dialogBackgroundColor)
|
||||
.withOpacity(0),
|
||||
],
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Theme(
|
||||
// Give the avatar a transparent background
|
||||
data: theme.copyWith(
|
||||
colorScheme:
|
||||
theme.colorScheme.copyWith(background: Colors.transparent)),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CurrentDeviceRow extends StatelessWidget {
|
||||
final DeviceNode node;
|
||||
final AsyncValue<YubiKeyData> data;
|
||||
@ -156,98 +184,68 @@ class _CurrentDeviceRow extends StatelessWidget {
|
||||
const _CurrentDeviceRow(this.node, this.data);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => data.when(
|
||||
data: (data) {
|
||||
final isNfc = data.node is NfcReaderNode;
|
||||
return Column(
|
||||
children: [
|
||||
DeviceAvatar.yubiKeyData(
|
||||
data,
|
||||
selected: true,
|
||||
radius: 64,
|
||||
),
|
||||
ListTile(
|
||||
isThreeLine: isNfc,
|
||||
title: Center(child: Text(data.name)),
|
||||
subtitle: Column(
|
||||
children: [
|
||||
Text(_getSubtitle(data.info)),
|
||||
if (isNfc) Text(node.name),
|
||||
],
|
||||
),
|
||||
//onTap: onTap,
|
||||
),
|
||||
],
|
||||
Widget build(BuildContext context) {
|
||||
final isNfc = node is NfcReaderNode;
|
||||
final hero = data.maybeWhen(
|
||||
data: (data) => DeviceAvatar.yubiKeyData(data, radius: 64),
|
||||
orElse: () => DeviceAvatar.deviceNode(node, radius: 64),
|
||||
);
|
||||
},
|
||||
|
||||
final messages = data.whenOrNull(
|
||||
data: (data) => [_getInfoString(data.info)],
|
||||
error: (error, _) {
|
||||
final String message;
|
||||
switch (error) {
|
||||
case 'unknown-device':
|
||||
message = 'Unrecognized device';
|
||||
break;
|
||||
default:
|
||||
message = 'No YubiKey present';
|
||||
return ['Unrecognized device'];
|
||||
case 'device-inaccessible':
|
||||
return ['Device inacessible'];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
) ??
|
||||
['No YubiKey present'];
|
||||
|
||||
String name =
|
||||
data.asData?.value.name ?? (isNfc ? messages.removeAt(0) : node.name);
|
||||
if (isNfc) {
|
||||
messages.add(node.name);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
DeviceAvatar.deviceNode(
|
||||
node,
|
||||
selected: true,
|
||||
radius: 64,
|
||||
),
|
||||
_HeroAvatar(child: hero),
|
||||
ListTile(
|
||||
title: Center(child: Text(message)),
|
||||
subtitle: Center(child: Text(node.name)),
|
||||
),
|
||||
title: Text(name, textAlign: TextAlign.center),
|
||||
isThreeLine: messages.length > 1,
|
||||
subtitle: Text(messages.join('\n'), textAlign: TextAlign.center),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => Column(
|
||||
children: [
|
||||
DeviceAvatar.deviceNode(
|
||||
node,
|
||||
selected: true,
|
||||
radius: 64,
|
||||
),
|
||||
ListTile(
|
||||
title: Center(child: Text(node.name)),
|
||||
subtitle: const Center(child: Text('Device inaccessible')),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeviceRow extends ConsumerWidget {
|
||||
final DeviceNode node;
|
||||
final DeviceInfo? info;
|
||||
|
||||
const _DeviceRow(
|
||||
this.node, {
|
||||
this.info,
|
||||
});
|
||||
const _DeviceRow(this.node, {this.info});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: DeviceAvatar.deviceNode(
|
||||
node,
|
||||
radius: 20,
|
||||
),
|
||||
child: DeviceAvatar.deviceNode(node),
|
||||
),
|
||||
title: Text(node.name),
|
||||
subtitle: Text(
|
||||
node.when(
|
||||
usbYubiKey: (_, __, ___, info) =>
|
||||
info == null ? 'Device inaccessible' : _getSubtitle(info),
|
||||
info == null ? 'Device inaccessible' : _getInfoString(info),
|
||||
nfcReader: (_, __) => 'Select to scan',
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
//Navigator.of(context).pop();
|
||||
ref.read(currentDeviceProvider.notifier).setCurrentDevice(node);
|
||||
},
|
||||
);
|
||||
|
@ -226,6 +226,8 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
|
||||
final info = dev.info;
|
||||
if (info != null) {
|
||||
state = AsyncValue.data(YubiKeyData(dev, dev.name, info));
|
||||
} else {
|
||||
state = const AsyncValue.error('device-inaccessible');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user