mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 18:22:39 +03:00
Use Shortcuts/Intent/Action.
This commit is contained in:
parent
d5c2bbebfa
commit
6e3c3d2e4c
@ -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
17
lib/app/shortcuts.dart
Executable 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(),
|
||||
};
|
@ -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)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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 {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user