diff --git a/lib/features/app/widget/app.dart b/lib/features/app/widget/app.dart index 81ac692f..3841232b 100644 --- a/lib/features/app/widget/app.dart +++ b/lib/features/app/widget/app.dart @@ -1,7 +1,6 @@ import 'package:accessibility_tools/accessibility_tools.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:hiddify/core/localization/locale_extensions.dart'; import 'package:hiddify/core/localization/locale_preferences.dart'; @@ -12,6 +11,7 @@ import 'package:hiddify/core/theme/app_theme.dart'; import 'package:hiddify/core/theme/theme_preferences.dart'; import 'package:hiddify/features/app_update/notifier/app_update_notifier.dart'; import 'package:hiddify/features/common/common_controllers.dart'; +import 'package:hiddify/features/wrapper/shortcut/shortcut_wrapper.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:upgrader/upgrader.dart'; @@ -32,11 +32,8 @@ class App extends HookConsumerWidget with PresLogger { final upgrader = ref.watch(upgraderProvider); - return Shortcuts( - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), - }, - child: MaterialApp.router( + return ShortcutWrapper( + MaterialApp.router( routerConfig: router, locale: locale.flutterLocale, supportedLocales: AppLocaleUtils.supportedLocales, diff --git a/lib/features/wrapper/shortcut/shortcut_wrapper.dart b/lib/features/wrapper/shortcut/shortcut_wrapper.dart new file mode 100644 index 00000000..826d7fe6 --- /dev/null +++ b/lib/features/wrapper/shortcut/shortcut_wrapper.dart @@ -0,0 +1,90 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hiddify/core/router/router.dart'; +import 'package:hiddify/features/common/window/window_controller.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ShortcutWrapper extends HookConsumerWidget { + const ShortcutWrapper(this.child, {super.key}); + + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Shortcuts( + shortcuts: { + // Android TV D-pad select support + LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), + + if (Platform.isLinux) ...{ + // quit app using Control+Q on Linux + const SingleActivator(LogicalKeyboardKey.keyQ, control: true): + QuitAppIntent(), + }, + + if (Platform.isMacOS) ...{ + // close window using Command+W on macOS + const SingleActivator(LogicalKeyboardKey.keyW, meta: true): + CloseWindowIntent(), + + // open settings using Command+, on macOS + const SingleActivator(LogicalKeyboardKey.comma, meta: true): + OpenSettingsIntent(), + }, + + // try adding profile using Command+V and Control+V + const SingleActivator(LogicalKeyboardKey.keyV, meta: true): + PasteIntent(), + const SingleActivator(LogicalKeyboardKey.keyV, control: true): + PasteIntent(), + }, + child: Actions( + actions: { + CloseWindowIntent: CallbackAction( + onInvoke: (_) async { + await ref.read(windowControllerProvider.notifier).hide(); + return null; + }, + ), + QuitAppIntent: CallbackAction( + onInvoke: (_) async { + await ref.read(windowControllerProvider.notifier).quit(); + return null; + }, + ), + OpenSettingsIntent: CallbackAction( + onInvoke: (_) { + if (rootNavigatorKey.currentContext != null) { + const SettingsRoute().go(rootNavigatorKey.currentContext!); + } + return null; + }, + ), + PasteIntent: CallbackAction( + onInvoke: (_) async { + if (rootNavigatorKey.currentContext != null) { + final captureResult = + await Clipboard.getData(Clipboard.kTextPlain) + .then((value) => value?.text ?? ''); + AddProfileRoute(url: captureResult) + .push(rootNavigatorKey.currentContext!); + } + return null; + }, + ), + }, + child: child, + ), + ); + } +} + +class CloseWindowIntent extends Intent {} + +class QuitAppIntent extends Intent {} + +class OpenSettingsIntent extends Intent {} + +class PasteIntent extends Intent {}