diff --git a/lib/main.dart b/lib/main.dart index 11eac7c..8de4e11 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,6 +44,7 @@ import 'package:surface/providers/widget.dart'; import 'package:surface/router.dart'; import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy; import 'package:surface/widgets/dialog.dart'; +import 'package:surface/widgets/menu_bar.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:version/version.dart'; import 'package:workmanager/workmanager.dart'; @@ -331,18 +332,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { Future _hotkeyInitialization() async { if (kIsWeb) return; - - if (Platform.isMacOS) { - HotKey quitHotKey = HotKey( - key: PhysicalKeyboardKey.keyQ, - modifiers: [HotKeyModifier.meta], - scope: HotKeyScope.inapp, - ); - await hotKeyManager.register(quitHotKey, keyUpHandler: (_) { - _appLifecycleListener?.dispose(); - SystemChannels.platform.invokeMethod('SystemNavigator.pop'); - }); - } + // The quit key has been removed, and the logic of the quit key is moved to system menu bar activator. } final Menu _appTrayMenu = Menu( @@ -426,6 +416,15 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { return AppExitResponse.cancel; } + void _quitApp() { + _appLifecycleListener?.dispose(); + if (Platform.isWindows) { + appWindow.close(); + } else { + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } + } + @override void onTrayIconMouseDown() { if (Platform.isWindows) { @@ -460,12 +459,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { Timer(const Duration(milliseconds: 100), () => appWindow.show()); break; case 'exit': - _appLifecycleListener?.dispose(); - if (Platform.isWindows) { - appWindow.close(); - } else { - SystemChannels.platform.invokeMethod('SystemNavigator.pop'); - } + _quitApp(); break; } } @@ -482,28 +476,31 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { @override Widget build(BuildContext context) { final cfg = context.read(); - return NotificationListener( - onNotification: (notification) { - WidgetsBinding.instance.addPostFrameCallback((_) { - cfg.calcDrawerSize(context); - }); - return false; - }, - child: OrientationBuilder( - builder: (context, orientation) { - final cfg = context.read(); + return AppSystemMenuBar( + onQuit: _quitApp, + child: NotificationListener( + onNotification: (notification) { WidgetsBinding.instance.addPostFrameCallback((_) { cfg.calcDrawerSize(context); }); - Future.delayed(const Duration(milliseconds: 300), () { - if (context.mounted) { - cfg.calcDrawerSize(context); - } - }); - return SizeChangedLayoutNotifier( - child: widget.child, - ); + return false; }, + child: OrientationBuilder( + builder: (context, orientation) { + final cfg = context.read(); + WidgetsBinding.instance.addPostFrameCallback((_) { + cfg.calcDrawerSize(context); + }); + Future.delayed(const Duration(milliseconds: 300), () { + if (context.mounted) { + cfg.calcDrawerSize(context); + } + }); + return SizeChangedLayoutNotifier( + child: widget.child, + ); + }, + ), ), ); } diff --git a/lib/widgets/menu_bar.dart b/lib/widgets/menu_bar.dart new file mode 100644 index 0000000..566282d --- /dev/null +++ b/lib/widgets/menu_bar.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:surface/providers/navigation.dart'; +import 'package:surface/router.dart'; + +// https://api.flutter.dev/flutter/widgets/PlatformMenuBar-class.html +// All the code following is only works on macOS +class AppSystemMenuBar extends StatelessWidget { + final Function? onQuit; + final Widget child; + const AppSystemMenuBar({super.key, this.onQuit, required this.child}); + + @override + Widget build(BuildContext context) { + if (kIsWeb || !Platform.isMacOS) return child; + + final nav = context.watch(); + + return PlatformMenuBar( + menus: [ + PlatformMenu( + label: 'Solian', + menus: [ + PlatformMenuItemGroup( + members: [ + PlatformMenuItem( + label: 'screenAbout'.tr(), + onSelected: () { + appRouter.goNamed('about'); + nav.autoDetectIndex(appRouter); + }, + ), + ], + ), + PlatformMenuItemGroup( + members: [ + PlatformMenuItem( + label: 'screenHome'.tr(), + shortcut: const SingleActivator( + LogicalKeyboardKey.digit1, + meta: true, + ), + onSelected: () { + appRouter.goNamed('home'); + nav.autoDetectIndex(appRouter); + }, + ), + PlatformMenuItem( + label: 'screenExplore'.tr(), + shortcut: const SingleActivator( + LogicalKeyboardKey.digit2, + meta: true, + ), + onSelected: () { + appRouter.goNamed('explore'); + nav.autoDetectIndex(appRouter); + }, + ), + PlatformMenuItem( + label: 'screenChat'.tr(), + shortcut: const SingleActivator( + LogicalKeyboardKey.digit3, + meta: true, + ), + onSelected: () { + appRouter.goNamed('chat'); + }, + ), + PlatformMenuItem( + label: 'screenAccount'.tr(), + shortcut: const SingleActivator( + LogicalKeyboardKey.digit4, + meta: true, + ), + onSelected: () { + appRouter.goNamed('account'); + }, + ), + ], + ), + if (onQuit != null) + PlatformMenuItem( + shortcut: const SingleActivator( + LogicalKeyboardKey.keyQ, + meta: true, + ), + label: 'trayMenuExit'.tr(), + onSelected: () { + onQuit?.call(); + }, + ), + ], + ), + ], + child: child, + ); + } +}