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 00000000..87757168 Binary files /dev/null and b/ui/flutter/assets/tray_icon/icon.ico differ diff --git a/ui/flutter/assets/tray_icon/icon.png b/ui/flutter/assets/tray_icon/icon.png new file mode 100644 index 00000000..10974b1f Binary files /dev/null and b/ui/flutter/assets/tray_icon/icon.png differ 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()) {