mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-24 18:52:55 +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';
|
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,
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user