From b3e372326f1838abd329d052166da001885ebcc9 Mon Sep 17 00:00:00 2001 From: Li Wei Date: Wed, 7 Jun 2023 21:50:40 +0800 Subject: [PATCH] feat: add desktop system tray support (#120) --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 2 +- ui/flutter/assets/locales/en_US.json | 2 + ui/flutter/assets/locales/fa_IR.json | 2 + ui/flutter/assets/locales/ru_RU.json | 2 + ui/flutter/assets/locales/zh_CN.json | 2 + ui/flutter/assets/tray_icon/icon.ico | Bin 0 -> 4286 bytes ui/flutter/assets/tray_icon/icon.png | Bin 0 -> 10142 bytes .../app/controllers/app_controller.dart | 90 +++++++++++++++++- .../lib/app/modules/home/views/home_view.dart | 9 ++ ui/flutter/lib/generated/locales.g.dart | 10 ++ ui/flutter/lib/main.dart | 19 ++++ ui/flutter/lib/util/util.dart | 4 + ui/flutter/macos/Runner/AppDelegate.swift | 13 +++ ui/flutter/pubspec.lock | 40 ++++++++ ui/flutter/pubspec.yaml | 7 +- ui/flutter/windows/runner/main.cpp | 6 ++ 17 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 ui/flutter/assets/tray_icon/icon.ico create mode 100644 ui/flutter/assets/tray_icon/icon.png diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46169553..249ac3c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -210,7 +210,7 @@ jobs: flutter-version: ${{ env.FLUTTER_VERSION }} - run: | sudo apt-get update -y - sudo apt-get install -y ninja-build libgtk-3-dev + sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev - name: Build env: VERSION: ${{ needs.get-release.outputs.tag_name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29378915..67ae2a57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,7 +72,7 @@ jobs: flutter-version: ${{ env.FLUTTER_VERSION }} - run: | sudo apt-get update -y - sudo apt-get install -y ninja-build libgtk-3-dev + sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev - name: Build run: | go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop diff --git a/ui/flutter/assets/locales/en_US.json b/ui/flutter/assets/locales/en_US.json index 71c415d9..c08c18e2 100644 --- a/ui/flutter/assets/locales/en_US.json +++ b/ui/flutter/assets/locales/en_US.json @@ -9,6 +9,8 @@ "downloading": "downloading", "downloaded": "downloaded", "setting": "Setting", + "donate": "Donate", + "exit": "Exit", "create": "Create Task", "downloadLink": "Download Link", "downloadLinkValid": "Please enter the download link", diff --git a/ui/flutter/assets/locales/fa_IR.json b/ui/flutter/assets/locales/fa_IR.json index fec3764e..a5038654 100644 --- a/ui/flutter/assets/locales/fa_IR.json +++ b/ui/flutter/assets/locales/fa_IR.json @@ -7,6 +7,8 @@ "selectAll": "انتخاب همه", "task": "کار", "setting": "تنظیمات", + "donate": "کمک مالی", + "exit": "خروج", "create": "ایجاد کار", "downloading": "دانلود کردن", "downloaded": "دانلود شدن", diff --git a/ui/flutter/assets/locales/ru_RU.json b/ui/flutter/assets/locales/ru_RU.json index e14a6a93..34087cc9 100644 --- a/ui/flutter/assets/locales/ru_RU.json +++ b/ui/flutter/assets/locales/ru_RU.json @@ -9,6 +9,8 @@ "downloading": "Загрузка", "downloaded": "Загружено", "setting": "Настройки", + "donate": "Пожертвовать", + "exit": "Выход", "create": "Создать задачу", "downloadLink": "Ссылка для скачивание", "downloadLinkValid": "Пожалуйста, введите ссылку для скачивания", diff --git a/ui/flutter/assets/locales/zh_CN.json b/ui/flutter/assets/locales/zh_CN.json index 9dc2d2a3..218dd168 100644 --- a/ui/flutter/assets/locales/zh_CN.json +++ b/ui/flutter/assets/locales/zh_CN.json @@ -9,6 +9,8 @@ "downloading": "下载中", "downloaded": "已完成", "setting": "设置", + "donate": "打赏", + "exit": "退出", "create": "创建任务", "downloadLink": "下载链接", "downloadLinkValid": "请输入下载链接", diff --git a/ui/flutter/assets/tray_icon/icon.ico b/ui/flutter/assets/tray_icon/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..877571685bc95c667d6183ae4ebd6a0a7f7ea6ef GIT binary patch literal 4286 zcmc&&>r<3f7(b04nh9sRS>F=AbonppTbDneY1FI_1fvKRf|r3IF_20#Y^EC1G0`S$ zEXNd#%#4}=R&cp2i$J2lf*AJ&7FbwzpMJl?dzhEqciAw?b$*=lp6Bv=&U2pg?7J%U z5dNj4DE?JvA5-dSrPK=mmeflC*YyXoCz2MixRChoj=DFMg(oq-ysdUqQDxux%7{cf zBGN;IBVo^l-m?{yQ3s%|woA<5tYPdiZUlA#wcBeqPabVM7#R%?gLe66X!b8T;yn0eH4d+M9+mEz`oLS5K|0WUxw^p!9Uql57o zpbQ8Cl2Nxs4m9OT_f(gS&3W22*&**YW@{VTT0Tx;q|dl97#YUrfL{UHE}^uoSSox! zNLBxj;-4FjA?F%p6~6Cz4t9Ry<0||;3&g~LeCajFcwRBwSkwMcU$U$I9q|RaWB9p7 z8FiR1+Bo1UWI7Lzm#tqayl=XWs|&s5NjqI{m#tu{{pL2L$K|Iio;}xD7kTc?Q@1AC zA8`%+q4_WRzEjwYnq8PlyEafY2f0(+dW}Brho5#u&3oi>-+Ac`+zHNy=Tga8e&?jZ zo>h9y8_CFlcO*C@te-r0mg7F|m!JE-QMLm*(RG*QdP|x{>!oGPEt#&ZMqUFw_Q_X)N$$U8>;}(A_T9+P zb!_z!`J(-hoa;U#)XA*dDtjU48QH-#%A&b5ko!XPjLEpCTZ34RN)dy)qu45ga z%1mfl8b_|n;nw}AnZ3GZrb7V(_sejhh4fEGXFNCtib)`G{$$2ItN{7r&&lZWU3qZr6d-U<&Ko^9_88f#*sDu`DuDfh?+yg#{2rr>?~|slMZ1f6mg;r*C7@(M rsTYGvCHs|nv`49jtCd48uBCI`95N$$S^r+1LN literal 0 HcmV?d00001 diff --git a/ui/flutter/assets/tray_icon/icon.png b/ui/flutter/assets/tray_icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..10974b1f3fca4bb54ef4d7e23a41d186245ddd76 GIT binary patch literal 10142 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 M07*qoM6N<$f~I6$T>t<8 literal 0 HcmV?d00001 diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart index 4e40bc4a..aef9cdfe 100644 --- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart +++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart @@ -7,7 +7,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tray_manager/tray_manager.dart'; import 'package:uri_to_file/uri_to_file.dart'; +import 'package:window_manager/window_manager.dart'; import '../../../../api/api.dart'; import '../../../../api/model/downloader_config.dart'; @@ -15,6 +17,7 @@ import '../../../../core/common/start_config.dart'; import '../../../../generated/locales.g.dart'; import '../../../../util/localeManager.dart'; import '../../../../util/log_util.dart'; +import '../../../../util/package_info.dart'; import '../../../../util/util.dart'; import '../../../routes/app_pages.dart'; @@ -59,7 +62,7 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls, return ret; }); -class AppController extends GetxController { +class AppController extends GetxController with WindowListener, TrayListener { static StartConfig? _defaultStartConfig; final startConfig = StartConfig().obs; @@ -72,12 +75,45 @@ class AppController extends GetxController { @override void onReady() { super.onReady(); - _initDeepLinks(); + try { + _initDeepLinks(); + } catch (e) { + logger.w("initDeepLinks error", e); + } + try { + _initWinodws(); + } catch (e) { + logger.w("_initWinodws error", e); + } + try { + _initTray(); + } catch (e) { + logger.w("initTray error", e); + } } @override void onClose() { _linkSubscription?.cancel(); + trayManager.removeListener(this); + } + + @override + void onWindowClose() async { + final isPreventClose = await windowManager.isPreventClose(); + if (isPreventClose) { + windowManager.hide(); + } + } + + @override + void onTrayIconMouseDown() { + windowManager.show(); + } + + @override + void onTrayIconRightMouseDown() { + trayManager.popUpContextMenu(); } Future _initDeepLinks() async { @@ -95,6 +131,56 @@ class AppController extends GetxController { } } + Future _initWinodws() async { + if (!Util.isDesktop()) { + return; + } + windowManager.addListener(this); + } + + Future _initTray() async { + if (!Util.isDesktop()) { + return; + } + await trayManager.setIcon( + Util.isWindows() + ? 'assets/tray_icon/icon.ico' + : 'assets/tray_icon/icon.png', + ); + final menu = Menu(items: [ + MenuItem( + label: "create".tr, + onClick: (menuItem) async => { + await windowManager.show(), + await Get.rootDelegate.offAndToNamed(Routes.CREATE), + }, + ), + MenuItem( + label: 'setting'.tr, + onClick: (menuItem) async => { + await windowManager.show(), + await Get.rootDelegate.offAndToNamed(Routes.SETTING), + }, + ), + MenuItem.separator(), + MenuItem( + label: 'donate'.tr, + onClick: (menuItem) => {windowManager.destroy()}, + ), + MenuItem( + label: '${"version".tr}(${packageInfo.version})', + onClick: (menuItem) => {windowManager.destroy()}, + ), + MenuItem.separator(), + MenuItem( + label: 'exit'.tr, + onClick: (menuItem) => {windowManager.destroy()}, + ), + ]); + await trayManager.setContextMenu(menu); + trayManager.addListener(this); + } + Future _toCreate(Uri uri) async { final path = uri.scheme == "magnet" ? uri.toString() diff --git a/ui/flutter/lib/app/modules/home/views/home_view.dart b/ui/flutter/lib/app/modules/home/views/home_view.dart index f8c808c1..1dc5c615 100644 --- a/ui/flutter/lib/app/modules/home/views/home_view.dart +++ b/ui/flutter/lib/app/modules/home/views/home_view.dart @@ -11,6 +11,15 @@ class HomeView extends GetView { @override Widget build(BuildContext context) { return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) { + switch (currentRoute?.location) { + case Routes.SETTING: + controller.currentIndex.value = 1; + break; + default: + controller.currentIndex.value = 0; + break; + } + return Scaffold( // extendBody: true, body: Row( diff --git a/ui/flutter/lib/generated/locales.g.dart b/ui/flutter/lib/generated/locales.g.dart index a2793ddd..1fc112b8 100644 --- a/ui/flutter/lib/generated/locales.g.dart +++ b/ui/flutter/lib/generated/locales.g.dart @@ -23,6 +23,8 @@ class LocaleKeys { static const downloading = 'downloading'; static const downloaded = 'downloaded'; static const setting = 'setting'; + static const donate = 'donate'; + static const exit = 'exit'; static const create = 'create'; static const downloadLink = 'downloadLink'; static const downloadLinkValid = 'downloadLinkValid'; @@ -82,6 +84,8 @@ class Locales { 'downloading': 'downloading', 'downloaded': 'downloaded', 'setting': 'Setting', + 'donate': 'Donate', + 'exit': 'Exit', 'create': 'Create Task', 'downloadLink': 'Download Link', 'downloadLinkValid': 'Please enter the download link', @@ -139,6 +143,8 @@ class Locales { 'selectAll': 'انتخاب همه', 'task': 'کار', 'setting': 'تنظیمات', + 'donate': 'کمک مالی', + 'exit': 'خروج', 'create': 'ایجاد کار', 'downloading': 'دانلود کردن', 'downloaded': 'دانلود شدن', @@ -196,6 +202,8 @@ class Locales { 'downloading': 'Загрузка', 'downloaded': 'Загружено', 'setting': 'Настройки', + 'donate': 'Пожертвовать', + 'exit': 'Выход', 'create': 'Создать задачу', 'downloadLink': 'Ссылка для скачивание', 'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания', @@ -255,6 +263,8 @@ class Locales { 'downloading': '下载中', 'downloaded': '已完成', 'setting': '设置', + 'donate': '打赏', + 'exit': '退出', 'create': '创建任务', 'downloadLink': '下载链接', 'downloadLinkValid': '请输入下载链接', diff --git a/ui/flutter/lib/main.dart b/ui/flutter/lib/main.dart index 5d4902fd..0499b9c4 100644 --- a/ui/flutter/lib/main.dart +++ b/ui/flutter/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:window_manager/window_manager.dart'; import 'api/api.dart' as api; import 'app/modules/app/controllers/app_controller.dart'; @@ -11,6 +12,7 @@ import 'util/localeManager.dart'; import 'util/log_util.dart'; import 'util/mac_secure_util.dart'; import 'util/package_info.dart'; +import 'util/util.dart'; void main() async { await init(); @@ -21,6 +23,23 @@ void main() async { Future init() async { WidgetsFlutterBinding.ensureInitialized(); + if (Util.isDesktop()) { + await windowManager.ensureInitialized(); + const windowOptions = WindowOptions( + size: Size(1024, 768), + center: true, + backgroundColor: Colors.transparent, + skipTaskbar: false, + ); + windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.setBrightness( + Brightness.light, + ); + await windowManager.show(); + await windowManager.focus(); + await windowManager.setPreventClose(true); + }); + } final controller = Get.put(AppController()); try { diff --git a/ui/flutter/lib/util/util.dart b/ui/flutter/lib/util/util.dart index 44caf3c8..c5e8e5f2 100644 --- a/ui/flutter/lib/util/util.dart +++ b/ui/flutter/lib/util/util.dart @@ -41,6 +41,10 @@ class Util { return Platform.isWindows || Platform.isLinux || Platform.isMacOS; } + static isWindows() { + return !kIsWeb && Platform.isWindows; + } + static isMacos() { return !kIsWeb && Platform.isMacOS; } diff --git a/ui/flutter/macos/Runner/AppDelegate.swift b/ui/flutter/macos/Runner/AppDelegate.swift index d53ef643..a62b065f 100644 --- a/ui/flutter/macos/Runner/AppDelegate.swift +++ b/ui/flutter/macos/Runner/AppDelegate.swift @@ -4,6 +4,19 @@ import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return false + } + + override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + if !flag { + for window in NSApp.windows { + if !window.isVisible { + window.setIsVisible(true) + } + window.makeKeyAndOrderFront(self) + NSApp.activate(ignoringOtherApps: true) + } + } return true } } diff --git a/ui/flutter/pubspec.lock b/ui/flutter/pubspec.lock index 8be2ef7c..ed31406f 100644 --- a/ui/flutter/pubspec.lock +++ b/ui/flutter/pubspec.lock @@ -526,6 +526,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.4.0" + menu_base: + dependency: transitive + description: + name: menu_base + sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.1" meta: dependency: transitive description: @@ -718,6 +726,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.27.5" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "4931f226ca158123ccd765325e9fbf360bfed0af9b460a10f960f9bb13d58323" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.6" scroll_pos: dependency: transitive description: @@ -806,6 +822,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" + shortid: + dependency: transitive + description: + name: shortid + sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2" sky_engine: dependency: transitive description: flutter @@ -899,6 +923,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" + tray_manager: + dependency: "direct main" + description: + name: tray_manager + sha256: b1975a05e0c6999e983cf9a58a6a098318c896040ccebac5398a3cc9e43b9c69 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.0" typed_data: dependency: transitive description: @@ -1011,6 +1043,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.1.4" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "95096fede562cbb65f30d38b62d819a458f59ba9fe4a317f6cee669710f6676b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.4" xdg_directories: dependency: transitive description: diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml index d39efe20..94b76c62 100644 --- a/ui/flutter/pubspec.yaml +++ b/ui/flutter/pubspec.yaml @@ -55,6 +55,8 @@ dependencies: badges: ^3.0.3 app_links: ^3.4.2 uri_to_file: ^0.2.0 + window_manager: ^0.3.4 + tray_manager: ^0.2.0 dev_dependencies: flutter_test: sdk: flutter @@ -85,9 +87,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - macos/Frameworks/libgopeed.dylib - # - macos/lib/libgopeed.dylib + assets: + - assets/tray_icon/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/ui/flutter/windows/runner/main.cpp b/ui/flutter/windows/runner/main.cpp index 4bdd0d72..61de3867 100644 --- a/ui/flutter/windows/runner/main.cpp +++ b/ui/flutter/windows/runner/main.cpp @@ -7,6 +7,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { + HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", L"gopeed"); + if (hwnd != NULL) { + ::ShowWindow(hwnd, SW_NORMAL); + ::SetForegroundWindow(hwnd); + return EXIT_FAILURE; + } // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {