mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
Fix state of details column.
This commit is contained in:
parent
b50ada490a
commit
0e709f0085
@ -39,102 +39,110 @@ import 'fingerprint_dialog.dart';
|
|||||||
import 'key_actions.dart';
|
import 'key_actions.dart';
|
||||||
import 'rename_fingerprint_dialog.dart';
|
import 'rename_fingerprint_dialog.dart';
|
||||||
|
|
||||||
final _selectedItem = StateProvider<Object?>(
|
class FidoUnlockedPage extends ConsumerStatefulWidget {
|
||||||
(ref) => null,
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _registerFingerprintActions(
|
|
||||||
DevicePath devicePath,
|
|
||||||
Fingerprint fingerprint, {
|
|
||||||
required WidgetRef ref,
|
|
||||||
required Widget Function(BuildContext context) builder,
|
|
||||||
Map<Type, Action<Intent>> actions = const {},
|
|
||||||
}) {
|
|
||||||
final hasFeature = ref.watch(featureProvider);
|
|
||||||
return Actions(
|
|
||||||
actions: {
|
|
||||||
if (hasFeature(features.fingerprintsEdit))
|
|
||||||
EditIntent: CallbackAction<EditIntent>(onInvoke: (_) async {
|
|
||||||
final renamed = await ref.read(withContextProvider)(
|
|
||||||
(context) => showBlurDialog<Fingerprint>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => RenameFingerprintDialog(
|
|
||||||
devicePath,
|
|
||||||
fingerprint,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
if (renamed != null && ref.read(_selectedItem) == fingerprint) {
|
|
||||||
ref.read(_selectedItem.notifier).state = renamed;
|
|
||||||
}
|
|
||||||
return renamed;
|
|
||||||
}),
|
|
||||||
if (hasFeature(features.fingerprintsDelete))
|
|
||||||
DeleteIntent: CallbackAction<DeleteIntent>(onInvoke: (_) async {
|
|
||||||
final deleted = await ref.read(withContextProvider)(
|
|
||||||
(context) => showBlurDialog<bool?>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => DeleteFingerprintDialog(
|
|
||||||
devicePath,
|
|
||||||
fingerprint,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
if (deleted == true && ref.read(_selectedItem) == fingerprint) {
|
|
||||||
ref.read(_selectedItem.notifier).state = null;
|
|
||||||
}
|
|
||||||
return deleted;
|
|
||||||
}),
|
|
||||||
...actions,
|
|
||||||
},
|
|
||||||
child: Builder(builder: builder),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _registerCredentialActions(
|
|
||||||
DevicePath devicePath,
|
|
||||||
FidoCredential credential, {
|
|
||||||
required WidgetRef ref,
|
|
||||||
required Widget Function(BuildContext context) builder,
|
|
||||||
Map<Type, Action<Intent>> actions = const {},
|
|
||||||
}) {
|
|
||||||
final hasFeature = ref.watch(featureProvider);
|
|
||||||
return Actions(
|
|
||||||
actions: {
|
|
||||||
if (hasFeature(features.credentialsDelete))
|
|
||||||
DeleteIntent: CallbackAction<DeleteIntent>(onInvoke: (_) async {
|
|
||||||
final deleted = await ref.read(withContextProvider)(
|
|
||||||
(context) => showBlurDialog<bool?>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => DeleteCredentialDialog(
|
|
||||||
devicePath,
|
|
||||||
credential,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (deleted == true && ref.read(_selectedItem) == credential) {
|
|
||||||
ref.read(_selectedItem.notifier).state = null;
|
|
||||||
}
|
|
||||||
return deleted;
|
|
||||||
}),
|
|
||||||
...actions,
|
|
||||||
},
|
|
||||||
child: Builder(builder: builder),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class FidoUnlockedPage extends ConsumerWidget {
|
|
||||||
final DeviceNode node;
|
final DeviceNode node;
|
||||||
final FidoState state;
|
final FidoState state;
|
||||||
|
|
||||||
const FidoUnlockedPage(this.node, this.state, {super.key});
|
FidoUnlockedPage(this.node, this.state) : super(key: ObjectKey(node.path));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<ConsumerStatefulWidget> createState() =>
|
||||||
|
_FidoUnlockedPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FidoUnlockedPageState extends ConsumerState<FidoUnlockedPage> {
|
||||||
|
Object? _selected;
|
||||||
|
|
||||||
|
Widget _registerFingerprintActions(
|
||||||
|
Fingerprint fingerprint, {
|
||||||
|
required WidgetRef ref,
|
||||||
|
required Widget Function(BuildContext context) builder,
|
||||||
|
Map<Type, Action<Intent>> actions = const {},
|
||||||
|
}) {
|
||||||
|
final hasFeature = ref.watch(featureProvider);
|
||||||
|
return Actions(
|
||||||
|
actions: {
|
||||||
|
if (hasFeature(features.fingerprintsEdit))
|
||||||
|
EditIntent: CallbackAction<EditIntent>(onInvoke: (_) async {
|
||||||
|
final renamed = await ref.read(withContextProvider)(
|
||||||
|
(context) => showBlurDialog<Fingerprint>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => RenameFingerprintDialog(
|
||||||
|
widget.node.path,
|
||||||
|
fingerprint,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if (_selected == fingerprint && renamed != null) {
|
||||||
|
setState(() {
|
||||||
|
_selected = renamed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return renamed;
|
||||||
|
}),
|
||||||
|
if (hasFeature(features.fingerprintsDelete))
|
||||||
|
DeleteIntent: CallbackAction<DeleteIntent>(onInvoke: (_) async {
|
||||||
|
final deleted = await ref.read(withContextProvider)(
|
||||||
|
(context) => showBlurDialog<bool?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DeleteFingerprintDialog(
|
||||||
|
widget.node.path,
|
||||||
|
fingerprint,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if (_selected == fingerprint && deleted == true) {
|
||||||
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}),
|
||||||
|
...actions,
|
||||||
|
},
|
||||||
|
child: Builder(builder: builder),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _registerCredentialActions(
|
||||||
|
FidoCredential credential, {
|
||||||
|
required WidgetRef ref,
|
||||||
|
required Widget Function(BuildContext context) builder,
|
||||||
|
Map<Type, Action<Intent>> actions = const {},
|
||||||
|
}) {
|
||||||
|
final hasFeature = ref.watch(featureProvider);
|
||||||
|
return Actions(
|
||||||
|
actions: {
|
||||||
|
if (hasFeature(features.credentialsDelete))
|
||||||
|
DeleteIntent: CallbackAction<DeleteIntent>(onInvoke: (_) async {
|
||||||
|
final deleted = await ref.read(withContextProvider)(
|
||||||
|
(context) => showBlurDialog<bool?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DeleteCredentialDialog(
|
||||||
|
widget.node.path,
|
||||||
|
credential,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (_selected == credential && deleted == true) {
|
||||||
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}),
|
||||||
|
...actions,
|
||||||
|
},
|
||||||
|
child: Builder(builder: builder),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final selected = ref.watch(_selectedItem);
|
final selected = _selected;
|
||||||
List<Widget Function(bool expanded)> children = [];
|
List<Widget Function(bool expanded)> children = [];
|
||||||
|
|
||||||
if (state.credMgmt) {
|
if (widget.state.credMgmt) {
|
||||||
final data = ref.watch(credentialProvider(node.path)).asData;
|
final data = ref.watch(credentialProvider(widget.node.path)).asData;
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return _buildLoadingPage(context);
|
return _buildLoadingPage(context);
|
||||||
}
|
}
|
||||||
@ -143,13 +151,14 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
children.add((_) => ListTitle(l10n.s_passkeys));
|
children.add((_) => ListTitle(l10n.s_passkeys));
|
||||||
children.addAll(
|
children.addAll(
|
||||||
creds.map((cred) => (expanded) => _registerCredentialActions(
|
creds.map((cred) => (expanded) => _registerCredentialActions(
|
||||||
node.path,
|
|
||||||
cred,
|
cred,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
actions: {
|
actions: {
|
||||||
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) {
|
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
ref.read(_selectedItem.notifier).state = cred;
|
setState(() {
|
||||||
|
_selected = cred;
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return showBlurDialog(
|
return showBlurDialog(
|
||||||
@ -170,8 +179,8 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int nFingerprints = 0;
|
int nFingerprints = 0;
|
||||||
if (state.bioEnroll != null) {
|
if (widget.state.bioEnroll != null) {
|
||||||
final data = ref.watch(fingerprintProvider(node.path)).asData;
|
final data = ref.watch(fingerprintProvider(widget.node.path)).asData;
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return _buildLoadingPage(context);
|
return _buildLoadingPage(context);
|
||||||
}
|
}
|
||||||
@ -181,13 +190,14 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
children.add((_) => ListTitle(l10n.s_fingerprints));
|
children.add((_) => ListTitle(l10n.s_fingerprints));
|
||||||
children.addAll(
|
children.addAll(
|
||||||
fingerprints.map((fp) => (expanded) => _registerFingerprintActions(
|
fingerprints.map((fp) => (expanded) => _registerFingerprintActions(
|
||||||
node.path,
|
|
||||||
fp,
|
fp,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
actions: {
|
actions: {
|
||||||
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) {
|
OpenIntent: CallbackAction<OpenIntent>(onInvoke: (_) {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
ref.read(_selectedItem.notifier).state = fp;
|
setState(() {
|
||||||
|
_selected = fp;
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return showBlurDialog(
|
return showBlurDialog(
|
||||||
@ -214,7 +224,9 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
actions: {
|
actions: {
|
||||||
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
|
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
ref.read(_selectedItem.notifier).state = null;
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
Actions.invoke(context, intent);
|
Actions.invoke(context, intent);
|
||||||
}
|
}
|
||||||
@ -225,7 +237,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
title: Text(l10n.s_webauthn),
|
title: Text(l10n.s_webauthn),
|
||||||
keyActionsBuilder: switch (selected) {
|
keyActionsBuilder: switch (selected) {
|
||||||
FidoCredential credential => (context) =>
|
FidoCredential credential => (context) =>
|
||||||
_registerCredentialActions(node.path, credential,
|
_registerCredentialActions(credential,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
builder: (context) => Column(
|
builder: (context) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@ -274,7 +286,6 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
Fingerprint fingerprint => (context) => _registerFingerprintActions(
|
Fingerprint fingerprint => (context) => _registerFingerprintActions(
|
||||||
node.path,
|
|
||||||
fingerprint,
|
fingerprint,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
builder: (context) => Column(
|
builder: (context) => Column(
|
||||||
@ -309,11 +320,11 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
_ => hasActions
|
_ => hasActions
|
||||||
? (context) =>
|
? (context) => fidoBuildActions(
|
||||||
fidoBuildActions(context, node, state, nFingerprints)
|
context, widget.node, widget.state, nFingerprints)
|
||||||
: null
|
: null
|
||||||
},
|
},
|
||||||
keyActionsBadge: fidoShowActionsNotifier(state),
|
keyActionsBadge: fidoShowActionsNotifier(widget.state),
|
||||||
builder: (context, expanded) => Column(
|
builder: (context, expanded) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: children.map((f) => f(expanded)).toList()),
|
children: children.map((f) => f(expanded)).toList()),
|
||||||
@ -321,7 +332,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.bioEnroll != null) {
|
if (widget.state.bioEnroll != null) {
|
||||||
return MessagePage(
|
return MessagePage(
|
||||||
title: Text(l10n.s_webauthn),
|
title: Text(l10n.s_webauthn),
|
||||||
graphic: Icon(Icons.fingerprint,
|
graphic: Icon(Icons.fingerprint,
|
||||||
@ -329,9 +340,10 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
header: l10n.s_no_fingerprints,
|
header: l10n.s_no_fingerprints,
|
||||||
message: l10n.l_add_one_or_more_fps,
|
message: l10n.l_add_one_or_more_fps,
|
||||||
keyActionsBuilder: hasActions
|
keyActionsBuilder: hasActions
|
||||||
? (context) => fidoBuildActions(context, node, state, 0)
|
? (context) =>
|
||||||
|
fidoBuildActions(context, widget.node, widget.state, 0)
|
||||||
: null,
|
: null,
|
||||||
keyActionsBadge: fidoShowActionsNotifier(state),
|
keyActionsBadge: fidoShowActionsNotifier(widget.state),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,9 +354,9 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
header: l10n.l_no_discoverable_accounts,
|
header: l10n.l_no_discoverable_accounts,
|
||||||
message: l10n.l_register_sk_on_websites,
|
message: l10n.l_register_sk_on_websites,
|
||||||
keyActionsBuilder: hasActions
|
keyActionsBuilder: hasActions
|
||||||
? (context) => fidoBuildActions(context, node, state, 0)
|
? (context) => fidoBuildActions(context, widget.node, widget.state, 0)
|
||||||
: null,
|
: null,
|
||||||
keyActionsBadge: fidoShowActionsNotifier(state),
|
keyActionsBadge: fidoShowActionsNotifier(widget.state),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,20 +37,23 @@ import 'actions.dart';
|
|||||||
import 'key_actions.dart';
|
import 'key_actions.dart';
|
||||||
import 'slot_dialog.dart';
|
import 'slot_dialog.dart';
|
||||||
|
|
||||||
final _selectedSlot = StateProvider<OtpSlot?>(
|
class OtpScreen extends ConsumerStatefulWidget {
|
||||||
(ref) => null,
|
|
||||||
);
|
|
||||||
|
|
||||||
class OtpScreen extends ConsumerWidget {
|
|
||||||
final DevicePath devicePath;
|
final DevicePath devicePath;
|
||||||
|
|
||||||
const OtpScreen(this.devicePath, {super.key});
|
OtpScreen(this.devicePath) : super(key: ObjectKey(devicePath));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<ConsumerStatefulWidget> createState() => _OtpScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OtpScreenState extends ConsumerState<OtpScreen> {
|
||||||
|
SlotId? _selected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final hasFeature = ref.watch(featureProvider);
|
final hasFeature = ref.watch(featureProvider);
|
||||||
return ref.watch(otpStateProvider(devicePath)).when(
|
return ref.watch(otpStateProvider(widget.devicePath)).when(
|
||||||
loading: () => MessagePage(
|
loading: () => MessagePage(
|
||||||
title: Text(l10n.s_slots),
|
title: Text(l10n.s_slots),
|
||||||
graphic: const CircularProgressIndicator(),
|
graphic: const CircularProgressIndicator(),
|
||||||
@ -59,12 +62,16 @@ class OtpScreen extends ConsumerWidget {
|
|||||||
error: (error, _) =>
|
error: (error, _) =>
|
||||||
AppFailurePage(title: Text(l10n.s_slots), cause: error),
|
AppFailurePage(title: Text(l10n.s_slots), cause: error),
|
||||||
data: (otpState) {
|
data: (otpState) {
|
||||||
final selected = ref.watch(_selectedSlot);
|
final selected = _selected != null
|
||||||
|
? otpState.slots.firstWhere((e) => e.slot == _selected)
|
||||||
|
: null;
|
||||||
return Actions(
|
return Actions(
|
||||||
actions: {
|
actions: {
|
||||||
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
|
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
ref.read(_selectedSlot.notifier).state = null;
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
Actions.invoke(context, intent);
|
Actions.invoke(context, intent);
|
||||||
}
|
}
|
||||||
@ -75,7 +82,7 @@ class OtpScreen extends ConsumerWidget {
|
|||||||
title: Text(l10n.s_slots),
|
title: Text(l10n.s_slots),
|
||||||
keyActionsBuilder: selected != null
|
keyActionsBuilder: selected != null
|
||||||
? (context) => registerOtpActions(
|
? (context) => registerOtpActions(
|
||||||
devicePath,
|
widget.devicePath,
|
||||||
selected,
|
selected,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
builder: (context) => Column(
|
builder: (context) => Column(
|
||||||
@ -119,36 +126,45 @@ class OtpScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: (hasFeature(features.actions)
|
: (hasFeature(features.actions)
|
||||||
? (context) =>
|
? (context) => otpBuildActions(
|
||||||
otpBuildActions(context, devicePath, otpState, ref)
|
context, widget.devicePath, otpState, ref)
|
||||||
: null),
|
: null),
|
||||||
builder: (context, expanded) {
|
builder: (context, expanded) {
|
||||||
// De-select if window is resized to be non-expanded.
|
// De-select if window is resized to be non-expanded.
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
Timer.run(() {
|
Timer.run(() {
|
||||||
ref.read(_selectedSlot.notifier).state = null;
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
ListTitle(l10n.s_slots),
|
ListTitle(l10n.s_slots),
|
||||||
...otpState.slots.map((e) => registerOtpActions(devicePath, e,
|
...otpState.slots
|
||||||
ref: ref,
|
.map((e) => registerOtpActions(widget.devicePath, e,
|
||||||
actions: {
|
ref: ref,
|
||||||
OpenIntent:
|
actions: {
|
||||||
CallbackAction<OpenIntent>(onInvoke: (_) async {
|
OpenIntent:
|
||||||
if (expanded) {
|
CallbackAction<OpenIntent>(onInvoke: (_) async {
|
||||||
ref.read(_selectedSlot.notifier).state = e;
|
if (expanded) {
|
||||||
} else {
|
setState(() {
|
||||||
await showBlurDialog(
|
_selected = e.slot;
|
||||||
context: context,
|
});
|
||||||
barrierColor: Colors.transparent,
|
} else {
|
||||||
builder: (context) => SlotDialog(e.slot),
|
await showBlurDialog(
|
||||||
);
|
context: context,
|
||||||
}
|
barrierColor: Colors.transparent,
|
||||||
return null;
|
builder: (context) => SlotDialog(e.slot),
|
||||||
}),
|
);
|
||||||
},
|
}
|
||||||
builder: (context) => _SlotListItem(e, expanded)))
|
return null;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
builder: (context) => _SlotListItem(
|
||||||
|
e,
|
||||||
|
expanded: expanded,
|
||||||
|
selected: e == selected,
|
||||||
|
)))
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -160,8 +176,10 @@ class OtpScreen extends ConsumerWidget {
|
|||||||
class _SlotListItem extends ConsumerWidget {
|
class _SlotListItem extends ConsumerWidget {
|
||||||
final OtpSlot otpSlot;
|
final OtpSlot otpSlot;
|
||||||
final bool expanded;
|
final bool expanded;
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
const _SlotListItem(this.otpSlot, this.expanded);
|
const _SlotListItem(this.otpSlot,
|
||||||
|
{required this.expanded, required this.selected});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -170,7 +188,6 @@ class _SlotListItem extends ConsumerWidget {
|
|||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final isConfigured = otpSlot.isConfigured;
|
final isConfigured = otpSlot.isConfigured;
|
||||||
final hasFeature = ref.watch(featureProvider);
|
final hasFeature = ref.watch(featureProvider);
|
||||||
final selected = ref.watch(_selectedSlot) == otpSlot;
|
|
||||||
|
|
||||||
return AppListItem(
|
return AppListItem(
|
||||||
selected: selected,
|
selected: selected,
|
||||||
|
@ -39,20 +39,23 @@ import 'cert_info_view.dart';
|
|||||||
import 'key_actions.dart';
|
import 'key_actions.dart';
|
||||||
import 'slot_dialog.dart';
|
import 'slot_dialog.dart';
|
||||||
|
|
||||||
final _selectedSlot = StateProvider<PivSlot?>(
|
class PivScreen extends ConsumerStatefulWidget {
|
||||||
(ref) => null,
|
|
||||||
);
|
|
||||||
|
|
||||||
class PivScreen extends ConsumerWidget {
|
|
||||||
final DevicePath devicePath;
|
final DevicePath devicePath;
|
||||||
|
|
||||||
const PivScreen(this.devicePath, {super.key});
|
PivScreen(this.devicePath) : super(key: ObjectKey(devicePath));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<ConsumerStatefulWidget> createState() => _PivScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PivScreenState extends ConsumerState<PivScreen> {
|
||||||
|
SlotId? _selected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final hasFeature = ref.watch(featureProvider);
|
final hasFeature = ref.watch(featureProvider);
|
||||||
return ref.watch(pivStateProvider(devicePath)).when(
|
return ref.watch(pivStateProvider(widget.devicePath)).when(
|
||||||
loading: () => MessagePage(
|
loading: () => MessagePage(
|
||||||
title: Text(l10n.s_certificates),
|
title: Text(l10n.s_certificates),
|
||||||
graphic: const CircularProgressIndicator(),
|
graphic: const CircularProgressIndicator(),
|
||||||
@ -63,8 +66,11 @@ class PivScreen extends ConsumerWidget {
|
|||||||
cause: error,
|
cause: error,
|
||||||
),
|
),
|
||||||
data: (pivState) {
|
data: (pivState) {
|
||||||
final pivSlots = ref.watch(pivSlotsProvider(devicePath)).asData;
|
final pivSlots =
|
||||||
final selected = ref.watch(_selectedSlot);
|
ref.watch(pivSlotsProvider(widget.devicePath)).asData;
|
||||||
|
final selected = _selected != null
|
||||||
|
? pivSlots?.value.firstWhere((e) => e.slot == _selected)
|
||||||
|
: null;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
// This is what ListTile uses for subtitle
|
// This is what ListTile uses for subtitle
|
||||||
@ -75,7 +81,9 @@ class PivScreen extends ConsumerWidget {
|
|||||||
actions: {
|
actions: {
|
||||||
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
|
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
ref.read(_selectedSlot.notifier).state = null;
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
Actions.invoke(context, intent);
|
Actions.invoke(context, intent);
|
||||||
}
|
}
|
||||||
@ -87,7 +95,7 @@ class PivScreen extends ConsumerWidget {
|
|||||||
keyActionsBuilder: selected != null
|
keyActionsBuilder: selected != null
|
||||||
// TODO: Reuse slot dialog
|
// TODO: Reuse slot dialog
|
||||||
? (context) => registerPivActions(
|
? (context) => registerPivActions(
|
||||||
devicePath,
|
widget.devicePath,
|
||||||
pivState,
|
pivState,
|
||||||
selected,
|
selected,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
@ -127,20 +135,22 @@ class PivScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (hasFeature(features.actions)) ...[
|
if (hasFeature(features.actions)) ...[
|
||||||
pivBuildActions(
|
pivBuildActions(
|
||||||
context, devicePath, pivState, ref),
|
context, widget.devicePath, pivState, ref),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: (hasFeature(features.actions)
|
: (hasFeature(features.actions)
|
||||||
? (context) =>
|
? (context) => pivBuildActions(
|
||||||
pivBuildActions(context, devicePath, pivState, ref)
|
context, widget.devicePath, pivState, ref)
|
||||||
: null),
|
: null),
|
||||||
builder: (context, expanded) {
|
builder: (context, expanded) {
|
||||||
// De-select if window is resized to be non-expanded.
|
// De-select if window is resized to be non-expanded.
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
Timer.run(() {
|
Timer.run(() {
|
||||||
ref.read(_selectedSlot.notifier).state = null;
|
setState(() {
|
||||||
|
_selected = null;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
@ -148,7 +158,7 @@ class PivScreen extends ConsumerWidget {
|
|||||||
ListTitle(l10n.s_certificates),
|
ListTitle(l10n.s_certificates),
|
||||||
if (pivSlots?.hasValue == true)
|
if (pivSlots?.hasValue == true)
|
||||||
...pivSlots!.value.map((e) => registerPivActions(
|
...pivSlots!.value.map((e) => registerPivActions(
|
||||||
devicePath,
|
widget.devicePath,
|
||||||
pivState,
|
pivState,
|
||||||
e,
|
e,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
@ -156,7 +166,9 @@ class PivScreen extends ConsumerWidget {
|
|||||||
OpenIntent: CallbackAction<OpenIntent>(
|
OpenIntent: CallbackAction<OpenIntent>(
|
||||||
onInvoke: (_) async {
|
onInvoke: (_) async {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
ref.read(_selectedSlot.notifier).state = e;
|
setState(() {
|
||||||
|
_selected = e.slot;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await showBlurDialog(
|
await showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -167,8 +179,11 @@ class PivScreen extends ConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
builder: (context) =>
|
builder: (context) => _CertificateListItem(
|
||||||
_CertificateListItem(e, expanded),
|
e,
|
||||||
|
expanded: expanded,
|
||||||
|
selected: e == selected,
|
||||||
|
),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -183,8 +198,10 @@ class PivScreen extends ConsumerWidget {
|
|||||||
class _CertificateListItem extends ConsumerWidget {
|
class _CertificateListItem extends ConsumerWidget {
|
||||||
final PivSlot pivSlot;
|
final PivSlot pivSlot;
|
||||||
final bool expanded;
|
final bool expanded;
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
const _CertificateListItem(this.pivSlot, this.expanded);
|
const _CertificateListItem(this.pivSlot,
|
||||||
|
{required this.expanded, required this.selected});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -193,7 +210,6 @@ class _CertificateListItem extends ConsumerWidget {
|
|||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final hasFeature = ref.watch(featureProvider);
|
final hasFeature = ref.watch(featureProvider);
|
||||||
final selected = ref.watch(_selectedSlot) == pivSlot;
|
|
||||||
|
|
||||||
return AppListItem(
|
return AppListItem(
|
||||||
selected: selected,
|
selected: selected,
|
||||||
|
Loading…
Reference in New Issue
Block a user