From bb8f250f5f351c1a353791b77b25b9de7586191f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 2 Dec 2023 23:18:07 +0600 Subject: [PATCH] fix: doesn't minimize to tray when system title bar close button is used #866 --- .../shared/page_window_title_bar.dart | 17 +- .../configurators/use_close_behavior.dart | 32 +++ .../configurators/use_window_listener.dart | 197 ++++++++++++++++++ lib/main.dart | 6 + 4 files changed, 236 insertions(+), 16 deletions(-) create mode 100644 lib/hooks/configurators/use_close_behavior.dart create mode 100644 lib/hooks/configurators/use_window_listener.dart diff --git a/lib/components/shared/page_window_title_bar.dart b/lib/components/shared/page_window_title_bar.dart index 43435f7d4..d8e20184c 100644 --- a/lib/components/shared/page_window_title_bar.dart +++ b/lib/components/shared/page_window_title_bar.dart @@ -11,16 +11,6 @@ import 'dart:io' show Platform, exit; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:local_notifier/local_notifier.dart'; -final closeNotification = DesktopTools.createNotification( - title: 'Spotube', - message: 'Running in background. Minimized to System Tray', - actions: [ - LocalNotificationAction(text: 'Close The App'), - ], -)?..onClickAction = (value) { - exit(0); - }; - class PageWindowTitleBar extends StatefulHookConsumerWidget implements PreferredSizeWidget { final Widget? leading; @@ -113,12 +103,7 @@ class WindowTitleBarButtons extends HookConsumerWidget { const type = ThemeType.auto; Future onClose() async { - if (preferences.closeBehavior == CloseBehavior.close) { - exit(0); - } else { - await DesktopTools.window.hide(); - await closeNotification?.show(); - } + await DesktopTools.window.close(); } useEffect(() { diff --git a/lib/hooks/configurators/use_close_behavior.dart b/lib/hooks/configurators/use_close_behavior.dart new file mode 100644 index 000000000..05c03fff6 --- /dev/null +++ b/lib/hooks/configurators/use_close_behavior.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/hooks/configurators/use_window_listener.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; +import 'package:local_notifier/local_notifier.dart'; + +final closeNotification = DesktopTools.createNotification( + title: 'Spotube', + message: 'Running in background. Minimized to System Tray', + actions: [ + LocalNotificationAction(text: 'Close The App'), + ], +)?..onClickAction = (value) { + exit(0); + }; + +void useCloseBehavior(WidgetRef ref) { + useWindowListener( + onWindowClose: () async { + final preferences = ref.read(userPreferencesProvider); + if (preferences.closeBehavior == CloseBehavior.minimizeToTray) { + await DesktopTools.window.hide(); + closeNotification?.show(); + } else { + exit(0); + } + }, + ); +} diff --git a/lib/hooks/configurators/use_window_listener.dart b/lib/hooks/configurators/use_window_listener.dart new file mode 100644 index 000000000..b91ad413f --- /dev/null +++ b/lib/hooks/configurators/use_window_listener.dart @@ -0,0 +1,197 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class CallbackWindowListener implements WindowListener { + final VoidCallback? _onWindowClose; + final VoidCallback? _onWindowFocus; + final VoidCallback? _onWindowBlur; + final VoidCallback? _onWindowMaximize; + final VoidCallback? _onWindowUnmaximize; + final VoidCallback? _onWindowMinimize; + final VoidCallback? _onWindowRestore; + final VoidCallback? _onWindowResize; + final VoidCallback? _onWindowResized; + final VoidCallback? _onWindowMove; + final VoidCallback? _onWindowMoved; + final VoidCallback? _onWindowEnterFullScreen; + final VoidCallback? _onWindowLeaveFullScreen; + final VoidCallback? _onWindowDocked; + final VoidCallback? _onWindowUndocked; + final VoidCallback? _onWindowEvent; + + const CallbackWindowListener({ + VoidCallback? onWindowClose, + VoidCallback? onWindowFocus, + VoidCallback? onWindowBlur, + VoidCallback? onWindowMaximize, + VoidCallback? onWindowUnmaximize, + VoidCallback? onWindowMinimize, + VoidCallback? onWindowRestore, + VoidCallback? onWindowResize, + VoidCallback? onWindowResized, + VoidCallback? onWindowMove, + VoidCallback? onWindowMoved, + VoidCallback? onWindowEnterFullScreen, + VoidCallback? onWindowLeaveFullScreen, + VoidCallback? onWindowDocked, + VoidCallback? onWindowUndocked, + VoidCallback? onWindowEvent, + }) : _onWindowClose = onWindowClose, + _onWindowFocus = onWindowFocus, + _onWindowBlur = onWindowBlur, + _onWindowMaximize = onWindowMaximize, + _onWindowUnmaximize = onWindowUnmaximize, + _onWindowMinimize = onWindowMinimize, + _onWindowRestore = onWindowRestore, + _onWindowResize = onWindowResize, + _onWindowResized = onWindowResized, + _onWindowMove = onWindowMove, + _onWindowMoved = onWindowMoved, + _onWindowEnterFullScreen = onWindowEnterFullScreen, + _onWindowLeaveFullScreen = onWindowLeaveFullScreen, + _onWindowDocked = onWindowDocked, + _onWindowUndocked = onWindowUndocked, + _onWindowEvent = onWindowEvent; + + @override + void onWindowBlur() { + return _onWindowBlur?.call(); + } + + @override + void onWindowClose() { + return _onWindowClose?.call(); + } + + @override + void onWindowDocked() { + return _onWindowDocked?.call(); + } + + @override + void onWindowEnterFullScreen() { + return _onWindowEnterFullScreen?.call(); + } + + @override + void onWindowEvent(String eventName) { + return _onWindowEvent?.call(); + } + + @override + void onWindowFocus() { + return _onWindowFocus?.call(); + } + + @override + void onWindowLeaveFullScreen() { + return _onWindowLeaveFullScreen?.call(); + } + + @override + void onWindowMaximize() { + return _onWindowMaximize?.call(); + } + + @override + void onWindowMinimize() { + return _onWindowMinimize?.call(); + } + + @override + void onWindowMove() { + return _onWindowMove?.call(); + } + + @override + void onWindowMoved() { + return _onWindowMoved?.call(); + } + + @override + void onWindowResize() { + return _onWindowResize?.call(); + } + + @override + void onWindowResized() { + return _onWindowResized?.call(); + } + + @override + void onWindowRestore() { + return _onWindowRestore?.call(); + } + + @override + void onWindowUndocked() { + return _onWindowUndocked?.call(); + } + + @override + void onWindowUnmaximize() { + return _onWindowUnmaximize?.call(); + } +} + +void useWindowListener({ + VoidCallback? onWindowClose, + VoidCallback? onWindowFocus, + VoidCallback? onWindowBlur, + VoidCallback? onWindowMaximize, + VoidCallback? onWindowUnmaximize, + VoidCallback? onWindowMinimize, + VoidCallback? onWindowRestore, + VoidCallback? onWindowResize, + VoidCallback? onWindowResized, + VoidCallback? onWindowMove, + VoidCallback? onWindowMoved, + VoidCallback? onWindowEnterFullScreen, + VoidCallback? onWindowLeaveFullScreen, + VoidCallback? onWindowDocked, + VoidCallback? onWindowUndocked, + VoidCallback? onWindowEvent, +}) { + useEffect(() { + final listener = CallbackWindowListener( + onWindowClose: onWindowClose, + onWindowFocus: onWindowFocus, + onWindowBlur: onWindowBlur, + onWindowMaximize: onWindowMaximize, + onWindowUnmaximize: onWindowUnmaximize, + onWindowMinimize: onWindowMinimize, + onWindowRestore: onWindowRestore, + onWindowResize: onWindowResize, + onWindowResized: onWindowResized, + onWindowMove: onWindowMove, + onWindowMoved: onWindowMoved, + onWindowEnterFullScreen: onWindowEnterFullScreen, + onWindowLeaveFullScreen: onWindowLeaveFullScreen, + onWindowDocked: onWindowDocked, + onWindowUndocked: onWindowUndocked, + onWindowEvent: onWindowEvent, + ); + DesktopTools.window.addListener(listener); + return () { + DesktopTools.window.removeListener(listener); + }; + }, [ + onWindowClose, + onWindowFocus, + onWindowBlur, + onWindowMaximize, + onWindowUnmaximize, + onWindowMinimize, + onWindowRestore, + onWindowResize, + onWindowResized, + onWindowMove, + onWindowMoved, + onWindowEnterFullScreen, + onWindowLeaveFullScreen, + onWindowDocked, + onWindowUndocked, + onWindowEvent, + ]); +} diff --git a/lib/main.dart b/lib/main.dart index 24ba81b42..91ec789d9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'package:metadata_god/metadata_god.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/intents.dart'; +import 'package:spotube/hooks/configurators/use_close_behavior.dart'; import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart'; import 'package:spotube/hooks/configurators/use_get_storage_perms.dart'; import 'package:spotube/l10n/l10n.dart'; @@ -49,6 +50,10 @@ Future main(List rawArgs) async { await FlutterDisplayMode.setHighRefreshRate(); } + if (DesktopTools.platform.isDesktop) { + await DesktopTools.window.setPreventClose(true); + } + await DesktopTools.ensureInitialized( DesktopWindowOptions( hideTitleBar: true, @@ -177,6 +182,7 @@ class SpotubeState extends ConsumerState { ref.watch(paletteProvider.select((s) => s?.dominantColor?.color)); useInitSysTray(ref); + useCloseBehavior(ref); useEffect(() { FlutterNativeSplash.remove();