diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 077edd402..1bc2b0139 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,6 +49,6 @@ jobs: channel: 'stable' flutter-version: ${{env.FLUTTER_VERSION}} - run: sudo apt update - - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libmpv-dev + - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libmpv-dev libappindicator3-1 libappindicator3-dev - run: flutter pub get - run: flutter build linux -v \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4550d41e..614a2bb68 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,7 +96,7 @@ jobs: # channel: 'stable' # flutter-version: ${{env.FLUTTER_VERSION}} # - run: sudo apt update - # - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libmpv-dev + # - run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libmpv-dev libappindicator3-1 libappindicator3-dev # - run: flutter pub get # - uses: snapcore/action-build@v1 diff --git a/assets/images/tray_icon.ico b/assets/images/tray_icon.ico new file mode 100644 index 000000000..2c7869b94 Binary files /dev/null and b/assets/images/tray_icon.ico differ diff --git a/assets/images/tray_icon.png b/assets/images/tray_icon.png new file mode 100644 index 000000000..27b14684e Binary files /dev/null and b/assets/images/tray_icon.png differ diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index f7d8d7d99..dd47bff73 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:phoenix_theme/phoenix_theme.dart' hide ColorX, isMobile; import 'package:system_theme/system_theme.dart'; import 'package:watch_it/watch_it.dart'; +import 'package:window_manager/window_manager.dart'; import 'package:yaru/yaru.dart'; import '../../common/view/icons.dart'; @@ -14,6 +15,7 @@ import '../../library/library_model.dart'; import '../../settings/settings_model.dart'; import 'scaffold.dart'; import 'splash_screen.dart'; +import 'system_tray.dart'; class YaruMusicPodApp extends StatelessWidget { const YaruMusicPodApp({ @@ -105,7 +107,7 @@ class _MusicPodApp extends StatefulWidget with WatchItStatefulWidgetMixin { State<_MusicPodApp> createState() => _MusicPodAppState(); } -class _MusicPodAppState extends State<_MusicPodApp> { +class _MusicPodAppState extends State<_MusicPodApp> with WindowListener { late Future _initFuture; @override @@ -118,9 +120,25 @@ class _MusicPodAppState extends State<_MusicPodApp> { await di().init(); if (!mounted) return false; di().init(); + di().updateTrayMenuItems(context); + windowManager.addListener(this); return true; } + @override + void dispose() { + windowManager.removeListener(this); + super.dispose(); + } + + @override + void onWindowEvent(String eventName) { + if ('show' == eventName || 'hide' == eventName) { + di().updateTrayMenuItems(context); + } + super.onWindowEvent(eventName); + } + @override Widget build(BuildContext context) { final themeIndex = watchPropertyValue((SettingsModel m) => m.themeIndex); diff --git a/lib/app/view/system_tray.dart b/lib/app/view/system_tray.dart new file mode 100644 index 000000000..bffb54342 --- /dev/null +++ b/lib/app/view/system_tray.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:tray_manager/tray_manager.dart'; +import 'package:window_manager/window_manager.dart'; + +String trayIcon() { + if (Platform.isWindows) { + return 'assets/images/tray_icon.ico'; + } else { + return 'assets/images/tray_icon.png'; + } +} + +class SystemTray with TrayListener { + late List trayMenuItems; + + Future init() async { + trayManager.addListener(this); + await trayManager.setIcon(trayIcon()); + } + + Future dispose() async { + trayManager.removeListener(this); + } + + Future updateTrayMenuItems( + BuildContext context, + ) async { + bool isVisible = await windowManager.isVisible(); + + trayMenuItems = [ + MenuItem( + key: 'show_hide_window', + label: isVisible ? 'Hide Window' : 'Show Window', + ), + MenuItem.separator(), + MenuItem( + key: 'close_application', + label: 'Close Application', + ), + ]; + + await trayManager.setContextMenu(Menu(items: trayMenuItems)); + } + + @override + void onTrayMenuItemClick(MenuItem menuItem) { + switch (menuItem.key) { + case 'show_hide_window': + windowManager.isVisible().then((value) { + if (value) { + windowManager.hide(); + } else { + windowManager.show(); + } + }); + case 'close_application': + windowManager.close(); + } + } +} diff --git a/lib/common/data/close_btn_action.dart b/lib/common/data/close_btn_action.dart new file mode 100644 index 000000000..4f61d82eb --- /dev/null +++ b/lib/common/data/close_btn_action.dart @@ -0,0 +1,16 @@ +import '../../l10n/l10n.dart'; + +enum CloseBtnAction { + alwaysAsk, + hideToTray, + close; + + @override + String toString() => name; + + String localize(AppLocalizations l10n) => switch (this) { + alwaysAsk => l10n.alwaysAsk, + hideToTray => l10n.hideToTray, + close => l10n.closeApp, + }; +} diff --git a/lib/common/view/header_bar.dart b/lib/common/view/header_bar.dart index 1020867d8..3364f96be 100644 --- a/lib/common/view/header_bar.dart +++ b/lib/common/view/header_bar.dart @@ -2,7 +2,10 @@ import 'dart:io'; import '../../app/app_model.dart'; import '../../extensions/build_context_x.dart'; +import '../../l10n/l10n.dart'; import '../../library/library_model.dart'; +import '../../settings/settings_model.dart'; +import '../data/close_btn_action.dart'; import 'global_keys.dart'; import 'icons.dart'; import 'nav_back_button.dart'; @@ -11,6 +14,8 @@ import 'package:phoenix_theme/phoenix_theme.dart' hide isMobile; import 'package:watch_it/watch_it.dart'; import 'package:yaru/yaru.dart'; +import 'theme.dart'; + class HeaderBar extends StatelessWidget with WatchItMixin implements PreferredSizeWidget { @@ -40,6 +45,8 @@ class HeaderBar extends StatelessWidget @override Widget build(BuildContext context) { final canPop = watchPropertyValue((LibraryModel m) => m.canPop); + final closeBtnAction = + watchPropertyValue((SettingsModel m) => m.closeBtnActionIndex); Widget? leading; @@ -89,6 +96,21 @@ class HeaderBar extends StatelessWidget backgroundColor: backgroundColor ?? context.theme.scaffoldBackgroundColor, style: theStyle, foregroundColor: foregroundColor, + onClose: (context) { + switch (closeBtnAction) { + case CloseBtnAction.alwaysAsk: + showDialog( + context: context, + builder: (context) { + return const CloseWindowActionConfirmDialog(); + }, + ); + case CloseBtnAction.hideToTray: + YaruWindow.hide(context); + case CloseBtnAction.close: + YaruWindow.close(context); + } + }, ); } @@ -101,6 +123,77 @@ class HeaderBar extends StatelessWidget ); } +class CloseWindowActionConfirmDialog extends StatefulWidget { + const CloseWindowActionConfirmDialog({super.key}); + + @override + State createState() => + _CloseWindowActionConfirmDialogState(); +} + +class _CloseWindowActionConfirmDialogState + extends State { + bool rememberChoice = false; + @override + Widget build(BuildContext context) { + final model = di(); + return AlertDialog( + title: yaruStyled + ? YaruDialogTitleBar( + backgroundColor: Colors.transparent, + title: Text(context.l10n.closeMusicPod), + ) + : null, + titlePadding: yaruStyled ? EdgeInsets.zero : null, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 12), + Text( + context.l10n.confirmCloseOrHideTip, + ), + CheckboxListTile( + title: Text(context.l10n.doNotAskAgain), + value: rememberChoice, + onChanged: (value) { + setState(() { + rememberChoice = value!; + }); + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + if (rememberChoice) { + model.setCloseBtnActionIndex(CloseBtnAction.hideToTray); + } + Navigator.of(context).pop(); + YaruWindow.hide(context); + }, + child: Text(context.l10n.hideToTray), + ), + TextButton( + onPressed: () { + if (rememberChoice) { + model.setCloseBtnActionIndex(CloseBtnAction.close); + } + Navigator.of(context).pop(); + YaruWindow.close(context); + }, + child: Text(context.l10n.closeApp), + ), + ], + ); + } +} + class SidebarButton extends StatelessWidget { const SidebarButton({super.key}); diff --git a/lib/constants.dart b/lib/constants.dart index bd94299f9..b0bff436e 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -161,6 +161,7 @@ const kFavCountryCodes = 'favCountryCodes'; const kFavLanguageCodes = 'favLanguageCodes'; const kAscendingFeeds = 'ascendingfeed:::'; const kPatchNotesDisposed = 'kPatchNotesDisposed'; +const kCloseBtnAction = 'closeBtnAction'; const shops = { 'https://us.7digital.com/': '7digital', diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0b66ea41f..9e73d6141 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -331,5 +331,13 @@ "replayAllEpisodes": "Replay all episodes", "checkForUpdates": "Check for updates", "playbackWillStopIn": "Playback will stop in: {duration} ({timeOfDay})", - "schedulePlaybackStopTimer": "Schedule a time to stop playback" + "schedulePlaybackStopTimer": "Schedule a time to stop playback", + "alwaysAsk": "Always ask", + "hideToTray": "Hide to tray", + "closeBtnAction": "Close Button Action", + "whenCloseBtnClicked": "When close button is clicked", + "closeApp": "Close Application", + "closeMusicPod": "Close MusicPod?", + "confirmCloseOrHideTip": "Please confirm if you need to close the application or hide it?", + "doNotAskAgain": "Do not ask again" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index e53f7d1b3..5e702dbc2 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -91,7 +91,7 @@ "cryptocurrencyXXXPodcastIndexOnly": "加密货币", "cultureXXXPodcastIndexOnly": "文化", "dailyXXXPodcastIndexOnly": "每日", - "dark": "暗色", + "dark": "浅色", "decreaseSearchLimit": "请减少搜索限制", "deletePlaylist": "删除播放列表", "dependencies": "依赖", @@ -330,5 +330,14 @@ "writeMetadata": "写入元数据", "year": "年份", "years": "年份", - "playbackWillStopIn": "播放将在 {duration} ({timeOfDay})后停止。" + "playbackWillStopIn": "播放将在 {duration} ({timeOfDay})后停止。", + "schedulePlaybackStopTimer": "计划停止播放的时间", + "alwaysAsk": "总是询问", + "hideToTray": "隐藏到托盘", + "closeBtnAction": "关闭按钮的行为", + "whenCloseBtnClicked": "当点击关闭按钮时", + "closeApp": "关闭应用", + "closeMusicPod": "关闭 MusicPod?", + "confirmCloseOrHideTip": "请确定您想要退出应用还是隐藏到托盘?", + "doNotAskAgain": "不再询问" } diff --git a/lib/l10n/app_zh_CN.arb b/lib/l10n/app_zh_CN.arb index 2a9329cf3..9f8505650 100644 --- a/lib/l10n/app_zh_CN.arb +++ b/lib/l10n/app_zh_CN.arb @@ -91,7 +91,7 @@ "cryptocurrencyXXXPodcastIndexOnly": "加密货币", "cultureXXXPodcastIndexOnly": "文化", "dailyXXXPodcastIndexOnly": "每日", - "dark": "暗色", + "dark": "浅色", "decreaseSearchLimit": "请减少搜索限制", "deletePlaylist": "删除播放列表", "dependencies": "依赖", @@ -330,5 +330,14 @@ "writeMetadata": "写入元数据", "year": "年份", "years": "年份", - "playbackWillStopIn": "播放将在 {duration} ({timeOfDay})后停止。" + "playbackWillStopIn": "播放将在 {duration} ({timeOfDay})后停止。", + "schedulePlaybackStopTimer": "计划停止播放的时间", + "alwaysAsk": "总是询问", + "hideToTray": "隐藏到托盘", + "closeBtnAction": "关闭按钮的行为", + "whenCloseBtnClicked": "当点击关闭按钮时", + "closeApp": "关闭应用", + "closeMusicPod": "关闭 MusicPod?", + "confirmCloseOrHideTip": "请确定您想要退出应用还是隐藏到托盘?", + "doNotAskAgain": "不再询问" } diff --git a/lib/main.dart b/lib/main.dart index 42001802c..e70ba2cad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ import '../../library/library_model.dart'; import 'app/app_model.dart'; import 'app/connectivity_model.dart'; import 'app/view/app.dart'; +import 'app/view/system_tray.dart'; import 'library/library_service.dart'; import 'local_audio/local_audio_model.dart'; import 'local_audio/local_audio_service.dart'; @@ -133,6 +134,15 @@ Future main(List args) async { final gitHub = GitHub(); di.registerSingleton(gitHub); + if (!isMobile) { + final systemTray = SystemTray(); + await systemTray.init(); + di.registerSingleton( + systemTray, + dispose: (s) async => s.dispose(), + ); + } + // Register ViewModels di.registerLazySingleton( () => SettingsModel( diff --git a/lib/settings/settings_model.dart b/lib/settings/settings_model.dart index 7e1b60af7..af29808a0 100644 --- a/lib/settings/settings_model.dart +++ b/lib/settings/settings_model.dart @@ -4,6 +4,7 @@ import 'package:github/github.dart'; import 'package:safe_change_notifier/safe_change_notifier.dart'; import '../../external_path/external_path_service.dart'; +import '../common/data/close_btn_action.dart'; import 'settings_service.dart'; class SettingsModel extends SafeChangeNotifier { @@ -25,6 +26,7 @@ class SettingsModel extends SafeChangeNotifier { StreamSubscription? _themeIndexChangedSub; StreamSubscription? _recentPatchNotesDisposedChangedSub; StreamSubscription? _useArtistGridViewChangedSub; + StreamSubscription? _closeBtnActionIndexChangedSub; String? get directory => _service.directory; Future setDirectory(String? value) async { @@ -57,6 +59,10 @@ class SettingsModel extends SafeChangeNotifier { Future getPathOfDirectory() async => _externalPathService.getPathOfDirectory(); + CloseBtnAction get closeBtnActionIndex => _service.closeBtnActionIndex; + void setCloseBtnActionIndex(CloseBtnAction value) => + _service.setCloseBtnActionIndex(value); + void init() { _themeIndexChangedSub ??= _service.themeIndexChanged.listen((_) => notifyListeners()); @@ -73,6 +79,8 @@ class SettingsModel extends SafeChangeNotifier { _recentPatchNotesDisposedChangedSub ??= _service .recentPatchNotesDisposedChanged .listen((_) => notifyListeners()); + _closeBtnActionIndexChangedSub ??= + _service.closeBtnActionChanged.listen((_) => notifyListeners()); } @override @@ -85,6 +93,7 @@ class SettingsModel extends SafeChangeNotifier { await _directoryChangedSub?.cancel(); await _recentPatchNotesDisposedChangedSub?.cancel(); await _useArtistGridViewChangedSub?.cancel(); + await _closeBtnActionIndexChangedSub?.cancel(); super.dispose(); } } diff --git a/lib/settings/settings_service.dart b/lib/settings/settings_service.dart index f085dbe00..a1096095b 100644 --- a/lib/settings/settings_service.dart +++ b/lib/settings/settings_service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; +import '../common/data/close_btn_action.dart'; import '../constants.dart'; class SettingsService { @@ -87,6 +88,25 @@ class SettingsService { }); } + final _closeBtnActionIndexController = StreamController.broadcast(); + Stream get closeBtnActionChanged => + _closeBtnActionIndexController.stream; + CloseBtnAction get closeBtnActionIndex => + _preferences.getString(kCloseBtnAction) == null + ? CloseBtnAction.alwaysAsk + : CloseBtnAction.values.firstWhere( + (element) => + element.toString() == _preferences.getString(kCloseBtnAction), + orElse: () => CloseBtnAction.alwaysAsk, + ); + void setCloseBtnActionIndex(CloseBtnAction value) { + _preferences.setString(kCloseBtnAction, value.toString()).then( + (saved) { + if (saved) _closeBtnActionIndexController.add(true); + }, + ); + } + Future dispose() async { await _themeIndexController.close(); await _recentPatchNotesDisposedController.close(); @@ -95,5 +115,6 @@ class SettingsService { await _podcastIndexApiSecretController.close(); await _usePodcastIndexController.close(); await _podcastIndexApiKeyController.close(); + await _closeBtnActionIndexController.close(); } } diff --git a/lib/settings/view/settings_page.dart b/lib/settings/view/settings_page.dart index 07723e30a..110623326 100644 --- a/lib/settings/view/settings_page.dart +++ b/lib/settings/view/settings_page.dart @@ -6,7 +6,9 @@ import 'package:yaru/yaru.dart'; import '../../app/app_model.dart'; import '../../app/connectivity_model.dart'; +import '../../common/data/close_btn_action.dart'; import '../../common/view/common_widgets.dart'; +import '../../common/view/drop_down_arrow.dart'; import '../../common/view/global_keys.dart'; import '../../common/view/icons.dart'; import '../../common/view/progress.dart'; @@ -38,11 +40,12 @@ class SettingsPage extends StatelessWidget { ), Expanded( child: ListView( - children: const [ - _ThemeSection(), - _PodcastSection(), - _LocalAudioSection(), - _AboutSection(), + children: [ + const _ThemeSection(), + if (!isMobile) const _CloseActionSection(), + const _PodcastSection(), + const _LocalAudioSection(), + const _AboutSection(), ], ), ), @@ -98,6 +101,51 @@ class _ThemeSection extends StatelessWidget with WatchItMixin { } } +class _CloseActionSection extends StatelessWidget with WatchItMixin { + const _CloseActionSection(); + + @override + Widget build(BuildContext context) { + final model = di(); + + final closeBtnAction = + watchPropertyValue((SettingsModel m) => m.closeBtnActionIndex); + return YaruSection( + margin: const EdgeInsets.only( + left: kYaruPagePadding, + top: kYaruPagePadding, + right: kYaruPagePadding, + ), + headline: Text(context.l10n.closeBtnAction), + child: Column( + children: [ + YaruTile( + title: Text(context.l10n.whenCloseBtnClicked), + trailing: YaruPopupMenuButton( + icon: const DropDownArrow(), + initialValue: closeBtnAction, + child: Text(closeBtnAction.localize(context.l10n)), + onSelected: (value) { + model.setCloseBtnActionIndex(value); + }, + itemBuilder: (context) { + return [ + for (var i = 0; i < CloseBtnAction.values.length; ++i) + PopupMenuItem( + value: CloseBtnAction.values[i], + child: + Text(CloseBtnAction.values[i].localize(context.l10n)), + ), + ]; + }, + ), + ), + ], + ), + ); + } +} + class _PodcastSection extends StatefulWidget with WatchItStatefulWidgetMixin { const _PodcastSection(); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 1af123b9b..6bc70a71e 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) system_theme_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin"); system_theme_plugin_register_with_registrar(system_theme_registrar); + g_autoptr(FlPluginRegistrar) tray_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); + tray_manager_plugin_register_with_registrar(tray_manager_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index d52676c1e..a5d30a295 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST screen_retriever super_native_extensions system_theme + tray_manager url_launcher_linux window_manager yaru_window_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9e16b13bf..712af2624 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -21,6 +21,7 @@ import shared_preferences_foundation import sqflite import super_native_extensions import system_theme +import tray_manager import url_launcher_macos import wakelock_plus import window_manager @@ -42,6 +43,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin")) + TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/needs_translation.json b/needs_translation.json index ca20363cf..400f62e14 100644 --- a/needs_translation.json +++ b/needs_translation.json @@ -8,7 +8,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "da": [ @@ -22,13 +30,29 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "de": [ "gitHubClientConnectError", "replayEpisode", - "replayAllEpisodes" + "replayAllEpisodes", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "es": [ @@ -232,7 +256,37 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" + ], + + "fr": [ + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" + ], + + "it": [ + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "nl": [ @@ -464,7 +518,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "pl": [ @@ -516,7 +578,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "pt": [ @@ -719,7 +789,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "pt_BR": [ @@ -922,7 +1000,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "ru": [ @@ -940,7 +1026,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "sk": [ @@ -952,7 +1046,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "sv": [ @@ -1145,7 +1247,15 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "tr": [ @@ -1157,22 +1267,38 @@ "replayAllEpisodes", "checkForUpdates", "playbackWillStopIn", - "schedulePlaybackStopTimer" - ], - - "zh": [ - "schedulePlaybackStopTimer" - ], - - "zh_CN": [ - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "zh_HK": [ - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ], "zh_TW": [ - "schedulePlaybackStopTimer" + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain" ] } diff --git a/pubspec.lock b/pubspec.lock index 54fd059d6..643bdecd8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -901,6 +901,14 @@ packages: url: "https://github.com/media-kit/media-kit" source: git version: "1.2.4" + menu_base: + dependency: transitive + description: + name: menu_base + sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405" + url: "https://pub.dev" + source: hosted + version: "0.1.1" meta: dependency: transitive description: @@ -1342,6 +1350,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + shortid: + dependency: transitive + description: + name: shortid + sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb + url: "https://pub.dev" + source: hosted + version: "0.1.2" sky_engine: dependency: transitive description: flutter @@ -1507,6 +1523,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + tray_manager: + dependency: "direct main" + description: + name: tray_manager + sha256: c9a63fd88bd3546287a7eb8ccc978d707eef82c775397af17dda3a4f4c039e64 + url: "https://pub.dev" + source: hosted + version: "0.2.3" tuple: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c30d6c349..715ef3c7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,6 +71,7 @@ dependencies: smtc_windows: ^0.1.3 super_drag_and_drop: ^0.8.18 system_theme: ^3.0.0 + tray_manager: ^0.2.3 url_launcher: ^6.3.0 watch_it: ^1.4.2 win32: ^5.5.4 @@ -92,6 +93,8 @@ flutter: assets: - snap/gui/musicpod.png - assets/images/media-optical.png + - assets/images/tray_icon.ico + - assets/images/tray_icon.png - CHANGELOG.md dependency_overrides: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index b34aa2b54..820f3252e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); SystemThemePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SystemThemePlugin")); + TrayManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("TrayManagerPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 8b941b22e..f2b18ea3c 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST screen_retriever super_native_extensions system_theme + tray_manager url_launcher_windows window_manager )