This commit is contained in:
Dain Nilsson 2023-02-13 17:02:58 +01:00
commit ee6dbdf4db
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
4 changed files with 127 additions and 30 deletions

View File

@ -30,9 +30,9 @@ class YubicoAuthenticatorApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return LogWarningOverlay(
child: Shortcuts(
shortcuts: globalShortcuts,
return registerGlobalShortcuts(
ref: ref,
child: LogWarningOverlay(
child: MaterialApp(
title: 'Yubico Authenticator',
theme: AppTheme.lightTheme,

View File

@ -18,19 +18,127 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:window_manager/window_manager.dart';
import '../about_page.dart';
import '../android/views/android_settings_page.dart';
import '../core/state.dart';
import '../oath/keys.dart';
import '../settings_page.dart';
import 'message.dart';
import 'models.dart';
import 'state.dart';
class CopyIntent extends Intent {
const CopyIntent();
}
class CloseIntent extends Intent {
const CloseIntent();
}
class SearchIntent extends Intent {
const SearchIntent();
}
class NextDeviceIntent extends Intent {
const NextDeviceIntent();
}
class SettingsIntent extends Intent {
const SettingsIntent();
}
class AboutIntent extends Intent {
const AboutIntent();
}
final ctrlOrCmd =
Platform.isMacOS ? LogicalKeyboardKey.meta : LogicalKeyboardKey.control;
final globalShortcuts = {
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyC): const CopyIntent(),
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyF): const SearchIntent(),
};
Widget registerGlobalShortcuts(
{required WidgetRef ref, required Widget child}) =>
Actions(
actions: {
CloseIntent: CallbackAction<CloseIntent>(onInvoke: (_) {
windowManager.close();
return null;
}),
SearchIntent: CallbackAction<SearchIntent>(onInvoke: (intent) {
// If the OATH view doesn't have focus, but is shown, find and select the search bar.
final searchContext = searchAccountsField.currentContext;
if (searchContext != null) {
if (!Navigator.of(searchContext).canPop()) {
return Actions.maybeInvoke(searchContext, intent);
}
}
return null;
}),
NextDeviceIntent: CallbackAction<NextDeviceIntent>(onInvoke: (_) {
ref.read(withContextProvider)((context) async {
if (!Navigator.of(context).canPop()) {
final attached = ref
.read(attachedDevicesProvider)
.whereType<UsbYubiKeyNode>()
.toList();
if (attached.length > 1) {
final current = ref.read(currentDeviceProvider);
if (current != null && current is UsbYubiKeyNode) {
final index = attached.indexOf(current);
ref.read(currentDeviceProvider.notifier).setCurrentDevice(
attached[(index + 1) % attached.length]);
}
}
}
});
return null;
}),
SettingsIntent: CallbackAction<SettingsIntent>(onInvoke: (_) {
ref.read(withContextProvider)((context) async {
if (!Navigator.of(context).canPop()) {
await showBlurDialog(
context: context,
builder: (context) => Platform.isAndroid
? const AndroidSettingsPage()
: const SettingsPage(),
routeSettings: const RouteSettings(name: 'settings'),
);
}
});
return null;
}),
AboutIntent: CallbackAction<AboutIntent>(onInvoke: (_) {
ref.read(withContextProvider)((context) async {
if (!Navigator.of(context).canPop()) {
await showBlurDialog(
context: context,
builder: (context) => const AboutPage(),
routeSettings: const RouteSettings(name: 'about'),
);
}
});
return null;
}),
},
child: Shortcuts(
shortcuts: {
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyC): const CopyIntent(),
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyW):
const CloseIntent(),
LogicalKeySet(ctrlOrCmd, LogicalKeyboardKey.keyF):
const SearchIntent(),
if (isDesktop) ...{
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.tab):
const NextDeviceIntent(),
},
if (Platform.isMacOS) ...{
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyQ):
const CloseIntent(),
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.comma):
const SettingsIntent(),
},
},
child: child,
),
);

View File

@ -14,18 +14,14 @@
* limitations under the License.
*/
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../about_page.dart';
import '../../android/views/android_settings_page.dart';
import '../../management/views/management_screen.dart';
import '../../settings_page.dart';
import '../message.dart';
import '../models.dart';
import '../shortcuts.dart';
import '../state.dart';
import 'keys.dart';
@ -76,7 +72,7 @@ class MainPageDrawer extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final supportedApps = ref.watch(supportedAppsProvider);
final data = ref.watch(currentDeviceDataProvider).value;
final data = ref.watch(currentDeviceDataProvider).valueOrNull;
final color =
Theme.of(context).brightness == Brightness.dark ? 'white' : 'green';
@ -105,30 +101,21 @@ class MainPageDrawer extends ConsumerWidget {
index++;
}
Widget Function(BuildContext) dialogBuilder;
RouteSettings? routeSettings;
switch (index) {
case 0:
dialogBuilder = (context) => ManagementScreen(data!);
showBlurDialog(
context: context,
// data must be non-null when index == 0
builder: (context) => ManagementScreen(data!),
);
break;
case 1:
dialogBuilder = (context) => Platform.isAndroid
? const AndroidSettingsPage()
: const SettingsPage();
routeSettings = const RouteSettings(name: 'settings');
Actions.maybeInvoke(context, const SettingsIntent());
break;
case 2:
dialogBuilder = (context) => const AboutPage();
routeSettings = const RouteSettings(name: 'about');
Actions.maybeInvoke(context, const AboutIntent());
break;
default:
return;
}
showBlurDialog(
context: context,
builder: dialogBuilder,
routeSettings: routeSettings,
);
}
},
children: [

View File

@ -24,7 +24,9 @@ const resetAction = Key('$_prefix.reset');
const noAccountsView = Key('$_prefix.no_accounts');
const searchAccountsField = Key('$_prefix.search_accounts');
// This is global so we can access it from the global Ctrl+F shortcut.
final searchAccountsField = GlobalKey();
const passwordField = Key('$_prefix.password');
const currentPasswordField = Key('$_prefix.current_password');
const newPasswordField = Key('$_prefix.new_password');