Add gradient behind device image in picker.

This commit is contained in:
Dain Nilsson 2022-07-06 13:24:01 +02:00
parent 9a753ffcc9
commit f4838850e7
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
4 changed files with 102 additions and 115 deletions

View File

@ -5,46 +5,34 @@ import '../models.dart';
import 'device_images.dart'; import 'device_images.dart';
class DeviceAvatar extends StatelessWidget { class DeviceAvatar extends StatelessWidget {
final bool selected;
final Widget child; final Widget child;
final Widget? badge; final Widget? badge;
final double? radius; final double? radius;
const DeviceAvatar( const DeviceAvatar({super.key, required this.child, this.badge, this.radius});
{super.key,
this.selected = false,
required this.child,
this.badge,
this.radius});
factory DeviceAvatar.yubiKeyData(YubiKeyData data, factory DeviceAvatar.yubiKeyData(YubiKeyData data, {double? radius}) =>
{bool selected = false, double? radius}) =>
DeviceAvatar( DeviceAvatar(
badge: data.node is NfcReaderNode ? nfcIcon : null, badge: data.node is NfcReaderNode ? nfcIcon : null,
selected: selected,
radius: radius, radius: radius,
child: getProductImage(data.info, data.name), child: getProductImage(data.info, data.name),
); );
factory DeviceAvatar.deviceNode(DeviceNode node, factory DeviceAvatar.deviceNode(DeviceNode node, {double? radius}) =>
{bool selected = false, double? radius}) =>
node.map( node.map(
usbYubiKey: (node) { usbYubiKey: (node) {
final info = node.info; final info = node.info;
if (info != null) { if (info != null) {
return DeviceAvatar.yubiKeyData( return DeviceAvatar.yubiKeyData(
YubiKeyData(node, node.name, info), YubiKeyData(node, node.name, info),
selected: selected,
radius: radius, radius: radius,
); );
} }
return DeviceAvatar( return DeviceAvatar(
selected: selected,
radius: radius, radius: radius,
child: const Icon(Icons.device_unknown), child: const Icon(Icons.device_unknown),
); );
}, },
nfcReader: (_) => DeviceAvatar( nfcReader: (_) => DeviceAvatar(
selected: selected,
radius: radius, radius: radius,
child: nfcIcon, child: nfcIcon,
), ),
@ -52,17 +40,12 @@ class DeviceAvatar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final radius = this.radius ?? 24; final radius = this.radius ?? 20;
return Stack( return Stack(
alignment: AlignmentDirectional.bottomEnd, alignment: AlignmentDirectional.bottomEnd,
children: [ children: [
CircleAvatar( CircleAvatar(
radius: radius, radius: radius,
backgroundColor: selected
? Theme.of(context).colorScheme.primary
: Colors.transparent,
child: CircleAvatar(
radius: radius - 1,
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
child: IconTheme( child: IconTheme(
data: IconTheme.of(context).copyWith( data: IconTheme.of(context).copyWith(
@ -71,7 +54,6 @@ class DeviceAvatar extends StatelessWidget {
child: child, child: child,
), ),
), ),
),
if (badge != null) if (badge != null)
CircleAvatar( CircleAvatar(
radius: radius / 3, radius: radius / 3,

View File

@ -18,19 +18,16 @@ class DeviceButton extends ConsumerWidget {
deviceWidget = ref.watch(currentDeviceDataProvider).maybeWhen( deviceWidget = ref.watch(currentDeviceDataProvider).maybeWhen(
data: (data) => DeviceAvatar.yubiKeyData( data: (data) => DeviceAvatar.yubiKeyData(
data, data,
selected: true, radius: radius - 1,
radius: radius,
), ),
orElse: () => DeviceAvatar.deviceNode( orElse: () => DeviceAvatar.deviceNode(
deviceNode, deviceNode,
selected: true, radius: radius - 1,
radius: radius,
), ),
); );
} else { } else {
deviceWidget = DeviceAvatar( deviceWidget = DeviceAvatar(
radius: radius, radius: radius - 1,
selected: true,
child: const Icon(Icons.usb), child: const Icon(Icons.usb),
); );
} }
@ -41,8 +38,16 @@ class DeviceButton extends ConsumerWidget {
icon: OverflowBox( icon: OverflowBox(
maxHeight: 44, maxHeight: 44,
maxWidth: 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, child: deviceWidget,
), ),
),
),
onPressed: () { onPressed: () {
showBlurDialog( showBlurDialog(
context: context, context: context,

View File

@ -10,7 +10,7 @@ import '../models.dart';
import '../state.dart'; import '../state.dart';
import 'device_avatar.dart'; import 'device_avatar.dart';
String _getSubtitle(DeviceInfo info) { String _getInfoString(DeviceInfo info) {
final serial = info.serial; final serial = info.serial;
var subtitle = ''; var subtitle = '';
if (serial != null) { if (serial != null) {
@ -64,11 +64,12 @@ class DevicePickerDialog extends ConsumerWidget {
} else { } else {
hero = Column( hero = Column(
children: [ children: [
DeviceAvatar( _HeroAvatar(
selected: true, child: DeviceAvatar(
radius: 64, radius: 64,
child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb), child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb),
), ),
),
ListTile( ListTile(
title: title:
Center(child: Text(Platform.isAndroid ? 'No YubiKey' : 'USB')), Center(child: Text(Platform.isAndroid ? 'No YubiKey' : 'USB')),
@ -88,15 +89,11 @@ class DevicePickerDialog extends ConsumerWidget {
ListTile( ListTile(
leading: const Padding( leading: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4), padding: EdgeInsets.symmetric(horizontal: 4),
child: DeviceAvatar( child: DeviceAvatar(child: Icon(Icons.usb)),
radius: 20,
child: Icon(Icons.usb),
),
), ),
title: const Text('USB'), title: const Text('USB'),
subtitle: const Text('No YubiKey present'), subtitle: const Text('No YubiKey present'),
onTap: () { onTap: () {
//Navigator.of(context).pop();
ref.read(currentDeviceProvider.notifier).setCurrentDevice(null); 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 { class _CurrentDeviceRow extends StatelessWidget {
final DeviceNode node; final DeviceNode node;
final AsyncValue<YubiKeyData> data; final AsyncValue<YubiKeyData> data;
@ -156,98 +184,68 @@ class _CurrentDeviceRow extends StatelessWidget {
const _CurrentDeviceRow(this.node, this.data); const _CurrentDeviceRow(this.node, this.data);
@override @override
Widget build(BuildContext context) => data.when( Widget build(BuildContext context) {
data: (data) { final isNfc = node is NfcReaderNode;
final isNfc = data.node is NfcReaderNode; final hero = data.maybeWhen(
return Column( data: (data) => DeviceAvatar.yubiKeyData(data, radius: 64),
children: [ orElse: () => DeviceAvatar.deviceNode(node, radius: 64),
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,
),
],
); );
},
final messages = data.whenOrNull(
data: (data) => [_getInfoString(data.info)],
error: (error, _) { error: (error, _) {
final String message;
switch (error) { switch (error) {
case 'unknown-device': case 'unknown-device':
message = 'Unrecognized device'; return ['Unrecognized device'];
break; case 'device-inaccessible':
default: return ['Device inacessible'];
message = 'No YubiKey present';
} }
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( return Column(
children: [ children: [
DeviceAvatar.deviceNode( _HeroAvatar(child: hero),
node,
selected: true,
radius: 64,
),
ListTile( ListTile(
title: Center(child: Text(message)), title: Text(name, textAlign: TextAlign.center),
subtitle: Center(child: Text(node.name)), 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 { class _DeviceRow extends ConsumerWidget {
final DeviceNode node; final DeviceNode node;
final DeviceInfo? info; final DeviceInfo? info;
const _DeviceRow( const _DeviceRow(this.node, {this.info});
this.node, {
this.info,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return ListTile( return ListTile(
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 4),
child: DeviceAvatar.deviceNode( child: DeviceAvatar.deviceNode(node),
node,
radius: 20,
),
), ),
title: Text(node.name), title: Text(node.name),
subtitle: Text( subtitle: Text(
node.when( node.when(
usbYubiKey: (_, __, ___, info) => usbYubiKey: (_, __, ___, info) =>
info == null ? 'Device inaccessible' : _getSubtitle(info), info == null ? 'Device inaccessible' : _getInfoString(info),
nfcReader: (_, __) => 'Select to scan', nfcReader: (_, __) => 'Select to scan',
), ),
), ),
onTap: () { onTap: () {
//Navigator.of(context).pop();
ref.read(currentDeviceProvider.notifier).setCurrentDevice(node); ref.read(currentDeviceProvider.notifier).setCurrentDevice(node);
}, },
); );

View File

@ -226,6 +226,8 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
final info = dev.info; final info = dev.info;
if (info != null) { if (info != null) {
state = AsyncValue.data(YubiKeyData(dev, dev.name, info)); state = AsyncValue.data(YubiKeyData(dev, dev.name, info));
} else {
state = const AsyncValue.error('device-inaccessible');
} }
} }
} }