Use Shortcuts/Intent/Action.

This commit is contained in:
Dain Nilsson 2022-06-10 13:49:02 +02:00
parent d5c2bbebfa
commit 6e3c3d2e4c
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
5 changed files with 159 additions and 151 deletions

View File

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:yubico_authenticator/app/logging.dart';
import '../theme.dart';
import 'logging.dart';
import 'shortcuts.dart';
import 'state.dart';
class YubicoAuthenticatorApp extends ConsumerWidget {
@ -12,13 +13,16 @@ class YubicoAuthenticatorApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return LogWarningOverlay(
child: MaterialApp(
title: 'Yubico Authenticator',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ref.watch(themeModeProvider),
home: page,
debugShowCheckedModeBanner: false,
child: Shortcuts(
shortcuts: globalShortcuts,
child: MaterialApp(
title: 'Yubico Authenticator',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ref.watch(themeModeProvider),
home: page,
debugShowCheckedModeBanner: false,
),
),
);
}

17
lib/app/shortcuts.dart Executable file
View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CopyIntent extends Intent {
const CopyIntent();
}
class SearchIntent extends Intent {
const SearchIntent();
}
final globalShortcuts = {
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC):
const CopyIntent(),
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF):
const SearchIntent(),
};

View File

@ -1,10 +1,9 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/shortcuts.dart';
import '../../app/state.dart';
import '../../core/models.dart';
import '../../core/state.dart';
@ -100,88 +99,84 @@ class AccountDialog extends ConsumerWidget with AccountMixin {
Timer(Duration.zero, () => calculateCode(context, ref));
}
}
//TODO: Use Shortcuts, Intents, Actions
return Focus(
autofocus: true,
onKey: (node, event) {
if (event is RawKeyDownEvent &&
(Platform.isMacOS ? event.isMetaPressed : event.isControlPressed) &&
event.logicalKey == LogicalKeyboardKey.keyC) {
() async {
if (isExpired(code, ref)) {
await calculateCode(context, ref);
}
await ref.read(withContextProvider)(
(context) async {
copyToClipboard(context, ref);
},
);
}();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
return Actions(
actions: {
CopyIntent: CallbackAction(onInvoke: (_) async {
if (isExpired(code, ref)) {
await calculateCode(context, ref);
}
await ref.read(withContextProvider)(
(context) async {
copyToClipboard(context, ref);
},
);
return null;
}),
},
child: DialogFrame(
child: AlertDialog(
title: Center(
child: Text(
title,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.headlineSmall,
maxLines: 1,
softWrap: false,
child: Focus(
autofocus: true,
child: DialogFrame(
child: AlertDialog(
title: Center(
child: Text(
title,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.headlineSmall,
maxLines: 1,
softWrap: false,
),
),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (subtitle != null)
Text(
subtitle!,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
// This is what ListTile uses for subtitle
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).textTheme.caption!.color,
),
),
const SizedBox(height: 12.0),
DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: CardTheme.of(context).color,
borderRadius: const BorderRadius.all(Radius.circular(30.0)),
),
child: Center(
child: FittedBox(
child: DefaultTextStyle.merge(
style: const TextStyle(fontSize: 28),
child: IconTheme(
data: IconTheme.of(context).copyWith(size: 24),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: buildCodeView(ref),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (subtitle != null)
Text(
subtitle!,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
// This is what ListTile uses for subtitle
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).textTheme.caption!.color,
),
),
const SizedBox(height: 12.0),
DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: CardTheme.of(context).color,
borderRadius: const BorderRadius.all(Radius.circular(30.0)),
),
child: Center(
child: FittedBox(
child: DefaultTextStyle.merge(
style: const TextStyle(fontSize: 28),
child: IconTheme(
data: IconTheme.of(context).copyWith(size: 24),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: buildCodeView(ref),
),
),
),
),
),
),
),
],
),
actionsPadding: const EdgeInsets.only(top: 10.0, right: -16.0),
actions: [
Center(
child: FittedBox(
alignment: Alignment.center,
child: Row(children: _buildActions(context, ref)),
),
)
],
),
actionsPadding: const EdgeInsets.only(top: 10.0, right: -16.0),
actions: [
Center(
child: FittedBox(
alignment: Alignment.center,
child: Row(children: _buildActions(context, ref)),
),
)
],
),
),
);

View File

@ -1,11 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:yubico_authenticator/app/state.dart';
import '../../app/shortcuts.dart';
import '../../app/state.dart';
import '../models.dart';
import '../state.dart';
import 'account_dialog.dart';
@ -104,22 +103,16 @@ class AccountView extends ConsumerWidget with AccountMixin {
items: _buildPopupMenu(context, ref),
);
},
child: LayoutBuilder(builder: (context, constraints) {
final showAvatar = constraints.maxWidth >= 315;
//TODO: Use Shortcuts, Intents, Actions
return Focus(
onKey: (node, event) {
if (event is RawKeyDownEvent &&
(Platform.isMacOS
? event.isMetaPressed
: event.isControlPressed) &&
event.logicalKey == LogicalKeyboardKey.keyC) {
triggerCopy();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: ListTile(
child: Actions(
actions: {
CopyIntent: CallbackAction(onInvoke: (_) {
triggerCopy();
return null;
}),
},
child: LayoutBuilder(builder: (context, constraints) {
final showAvatar = constraints.maxWidth >= 315;
return ListTile(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
focusNode: focusNode,
@ -175,9 +168,9 @@ class AccountView extends ConsumerWidget with AccountMixin {
),
),
),
),
);
}),
);
}),
),
);
}
}

View File

@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/shortcuts.dart';
import '../../app/views/app_failure_page.dart';
import '../../app/views/app_loading_screen.dart';
import '../../app/views/app_page.dart';
@ -133,58 +134,56 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
actions: _buildActions(context, true),
);
}
return Focus(
autofocus: true,
onKey: (node, event) {
//TODO: Use Shortcuts, Intents, Actions
if (event is RawKeyDownEvent &&
(Platform.isMacOS ? event.isMetaPressed : event.isControlPressed) &&
event.logicalKey == LogicalKeyboardKey.keyF) {
return Actions(
actions: {
SearchIntent: CallbackAction(onInvoke: (_) {
searchController.selection = TextSelection(
baseOffset: 0, extentOffset: searchController.text.length);
searchFocus.requestFocus();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
return null;
}),
},
child: AppPage(
title: Focus(
canRequestFocus: false,
onKeyEvent: (node, event) {
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
node.focusInDirection(TraversalDirection.down);
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: Builder(builder: (context) {
return TextFormField(
key: const Key('search_accounts'),
controller: searchController,
focusNode: searchFocus,
style: Theme.of(context).textTheme.titleSmall,
decoration: const InputDecoration(
hintText: 'Search accounts',
isDense: true,
prefixIcon: Icon(Icons.search_outlined),
prefixIconConstraints: BoxConstraints(
minHeight: 30,
minWidth: 30,
child: Focus(
autofocus: true,
child: AppPage(
title: Focus(
canRequestFocus: false,
onKeyEvent: (node, event) {
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
node.focusInDirection(TraversalDirection.down);
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: Builder(builder: (context) {
return TextFormField(
key: const Key('search_accounts'),
controller: searchController,
focusNode: searchFocus,
style: Theme.of(context).textTheme.titleSmall,
decoration: const InputDecoration(
hintText: 'Search accounts',
isDense: true,
prefixIcon: Icon(Icons.search_outlined),
prefixIconConstraints: BoxConstraints(
minHeight: 30,
minWidth: 30,
),
border: InputBorder.none,
),
border: InputBorder.none,
),
onChanged: (value) {
ref.read(searchProvider.notifier).setFilter(value);
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
Focus.of(context).focusInDirection(TraversalDirection.down);
},
);
}),
onChanged: (value) {
ref.read(searchProvider.notifier).setFilter(value);
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
Focus.of(context).focusInDirection(TraversalDirection.down);
},
);
}),
),
actions: _buildActions(context, false),
child: AccountList(widget.devicePath, widget.oathState),
),
actions: _buildActions(context, false),
child: AccountList(widget.devicePath, widget.oathState),
),
);
}