Compare commits

..
6 Commits
Author SHA1 Message Date
liwei 70c16195ef release: v1.3.2 2023-06-09 22:17:27 +08:00
Li Wei eb9d966855 feat: support start and pause all tasks (#121) 2023-06-09 22:15:59 +08:00
liwei 0e3ca912a7 fix: add mac tray template icon 2023-06-08 14:10:55 +08:00
Li Wei b3e372326f feat: add desktop system tray support (#120) 2023-06-07 21:50:40 +08:00
Li Wei 0100f0131d feat: android deeplink and file selector (#118) 2023-06-06 20:39:07 +08:00
Levi e4c3b449a4 chore: update readme 2023-05-23 18:23:51 +08:00
38 changed files with 574 additions and 73 deletions

No files matched your search

+1 -1
View File
@@ -210,7 +210,7 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- run: | - run: |
sudo apt-get update -y 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 - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.tag_name }}
+1 -1
View File
@@ -72,7 +72,7 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- run: | - run: |
sudo apt-get update -y 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 - name: Build
run: | 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 go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
+12
View File
@@ -153,6 +153,18 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
go run cmd/web/main.go go run cmd/web/main.go
``` ```
## Thanks
### Contributors
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>
### JetBrains
[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)
## License ## License
[GPLv3](LICENSE) [GPLv3](LICENSE)
+12
View File
@@ -152,6 +152,18 @@ go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed
go run cmd/web/main.go go run cmd/web/main.go
``` ```
## 感谢
### 贡献者
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>
### JetBrains
[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)
## 开源许可 ## 开源许可
基于 [GPLv3](LICENSE) 协议开源。 基于 [GPLv3](LICENSE) 协议开源。
+1
View File
@@ -0,0 +1 @@
<svg height="105" viewBox="0 0 105 105" width="105" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="99.03" x2="61.54" y1="59.18" y2="90.91"><stop offset="0" stop-color="#087cfa"/><stop offset=".02" stop-color="#0d7bfa"/><stop offset=".37" stop-color="#5566f9"/><stop offset=".66" stop-color="#8a57f8"/><stop offset=".88" stop-color="#ab4ef7"/><stop offset="1" stop-color="#b74af7"/></linearGradient><linearGradient id="b" x1="37.54" x2="60.33" xlink:href="#a" y1="34.26" y2="8.33"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="17.84" x2="87.09" y1="88.36" y2="43.86"><stop offset="0" stop-color="#087cfa"/><stop offset=".1" stop-color="#1598d3"/><stop offset=".22" stop-color="#23b6aa"/><stop offset=".35" stop-color="#2dcc8b"/><stop offset=".46" stop-color="#35dd74"/><stop offset=".57" stop-color="#39e767"/><stop offset=".67" stop-color="#3bea62"/></linearGradient><path d="m89.06 41.63 11.44 25.37-19.58 33.5-12.06-29.28z" fill="url(#a)"/><path d="m68.86 71.22 12.06 29.28-30.34-10.43z" fill="#b74af7"/><path d="m72.13 31.61-6.22-27.11h-34.49l-26.92 41.18 7.79 18.92-7.79 17.27 55.75-47.43z" fill="url(#b)"/><path d="m100.5 24.88-40.25 9.56-55.75 47.43 35.96 18.63 28.4-29.28z" fill="url(#c)"/><path d="m22.5 22.5h60v60h-60z"/><g fill="#fff"><path d="m28.13 71.25h22.5v3.75h-22.5z"/><path d="m26.44 42.81v-.07a12.7 12.7 0 0 1 12.97-12.74 13.62 13.62 0 0 1 9.73 3.39l-3.43 4.14a9.13 9.13 0 0 0 -6.47-2.53c-4 0-7.11 3.5-7.11 7.7v.07c0 4.53 3.11 7.85 7.5 7.85a8.67 8.67 0 0 0 5.12-1.49v-3.56h-5.48v-4.7h10.73v10.74a16.07 16.07 0 0 1 -10.57 3.93c-7.69 0-12.99-5.41-12.99-12.73z"/><path d="m52.3 42.81v-.07a12.79 12.79 0 0 1 13.19-12.74c7.63 0 13.11 5.69 13.11 12.72v.07a12.79 12.79 0 0 1 -13.18 12.8c-7.64-.05-13.12-5.74-13.12-12.78zm20.61 0v-.07a7.51 7.51 0 0 0 -7.49-7.74 7.4 7.4 0 0 0 -7.42 7.67v.07a7.52 7.52 0 0 0 7.5 7.78 7.4 7.4 0 0 0 7.41-7.71z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+36
View File
@@ -7,6 +7,7 @@ import (
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
gonanoid "github.com/matoous/go-nanoid/v2" gonanoid "github.com/matoous/go-nanoid/v2"
"math"
"os" "os"
"path" "path"
"sort" "sort"
@@ -332,6 +333,41 @@ func (d *Downloader) Continue(id string) (err error) {
return d.doContinue(task) return d.doContinue(task)
} }
func (d *Downloader) PauseAll() (err error) {
d.lock.Lock()
defer d.lock.Unlock()
for _, task := range d.tasks {
if task.Status == base.DownloadStatusRunning {
if err = d.doPause(task, base.DownloadStatusPause); err != nil {
return
}
}
}
return
}
func (d *Downloader) ContinueAll() (err error) {
d.lock.Lock()
defer d.lock.Unlock()
runningCount := 0
continueTasks := make([]*Task, 0)
for _, task := range d.tasks {
if task.Status == base.DownloadStatusRunning {
runningCount++
} else if task.Status != base.DownloadStatusDone {
continueTasks = append(continueTasks, task)
}
}
// calculate how many tasks can be continued, can't exceed maxRunning
continueCount := int(math.Min(float64(d.cfg.MaxRunning-runningCount), float64(len(continueTasks))))
for i := 0; i < continueCount; i++ {
if err = d.doContinue(continueTasks[i]); err != nil {
return
}
}
return
}
func (d *Downloader) Delete(id string, force bool) (err error) { func (d *Downloader) Delete(id string, force bool) (err error) {
d.lock.Lock() d.lock.Lock()
defer d.lock.Unlock() defer d.lock.Unlock()
+16
View File
@@ -74,6 +74,22 @@ func ContinueTask(w http.ResponseWriter, r *http.Request) {
WriteJson(w, model.NewNilResult()) WriteJson(w, model.NewNilResult())
} }
func PauseAllTask(w http.ResponseWriter, r *http.Request) {
if err := Downloader.PauseAll(); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func ContinueAllTask(w http.ResponseWriter, r *http.Request) {
if err := Downloader.ContinueAll(); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func DeleteTask(w http.ResponseWriter, r *http.Request) { func DeleteTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
taskId := vars["id"] taskId := vars["id"]
+2
View File
@@ -85,6 +85,8 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask) r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask) r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask) r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask)
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask) r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask) r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks) r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
+43
View File
@@ -144,6 +144,39 @@ func TestPauseAndContinueTask(t *testing.T) {
}) })
} }
func TestPauseAllAndContinueALLTasks(t *testing.T) {
doTest(func() {
cfg, err := Downloader.GetConfig()
if err != nil {
t.Fatal(err)
}
createAndPause := func() {
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
}
total := cfg.MaxRunning + 2
for i := 0; i < total; i++ {
createAndPause()
}
// continue all
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/continue", nil)
tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusRunning), nil)
if len(tasks) != cfg.MaxRunning {
t.Errorf("ContinueAllTasks() got = %v, want %v", len(tasks), cfg.MaxRunning)
}
// pause all
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/pause", nil)
tasks = httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s,%s", base.DownloadStatusReady, base.DownloadStatusPause), nil)
if len(tasks) != total {
t.Errorf("PauseAllTasks() got = %v, want %v", len(tasks), total)
}
})
}
func TestDeleteTask(t *testing.T) { func TestDeleteTask(t *testing.T) {
doTest(func() { doTest(func() {
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq) taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
@@ -275,6 +308,16 @@ func doTest(handler func()) {
Stop() Stop()
}() }()
defer Downloader.Clear() defer Downloader.Clear()
defer func() {
tasks := Downloader.GetTasks()
taskIds := make([]string, len(tasks))
for i, task := range tasks {
taskIds[i] = task.ID
}
for _, id := range taskIds {
Downloader.Delete(id, true)
}
}()
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
handler() handler()
} }
@@ -12,9 +12,54 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true" android:exported="true"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:launchMode="singleTop" android:launchMode="singleInstance"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="file"
android:host="*"
android:pathPattern=".*torrent" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="file"
android:host="*"
android:mimeType="application/x-bittorrent" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="content"
android:host="*"
android:pathPattern=".*torrent" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="content"
android:host="*"
android:mimeType="application/x-bittorrent" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="magnet" />
</intent-filter>
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
+4
View File
@@ -9,6 +9,8 @@
"downloading": "downloading", "downloading": "downloading",
"downloaded": "downloaded", "downloaded": "downloaded",
"setting": "Setting", "setting": "Setting",
"donate": "Donate",
"exit": "Exit",
"create": "Create Task", "create": "Create Task",
"downloadLink": "Download Link", "downloadLink": "Download Link",
"downloadLinkValid": "Please enter the download link", "downloadLinkValid": "Please enter the download link",
@@ -48,6 +50,8 @@
"notSet": "NS", "notSet": "NS",
"set": "SET", "set": "SET",
"effectAfterRestart": "Effect after restart", "effectAfterRestart": "Effect after restart",
"startAll": "Start All",
"pauseAll": "Pause All",
"deleteTask": "Delete Task", "deleteTask": "Delete Task",
"deleteTaskTip": "Keep downloaded files", "deleteTaskTip": "Keep downloaded files",
"delete": "Delete", "delete": "Delete",
+4
View File
@@ -7,6 +7,8 @@
"selectAll": "انتخاب همه", "selectAll": "انتخاب همه",
"task": "کار", "task": "کار",
"setting": "تنظیمات", "setting": "تنظیمات",
"donate": "کمک مالی",
"exit": "خروج",
"create": "ایجاد کار", "create": "ایجاد کار",
"downloading": "دانلود کردن", "downloading": "دانلود کردن",
"downloaded": "دانلود شدن", "downloaded": "دانلود شدن",
@@ -45,6 +47,8 @@
"notSet": "NS", "notSet": "NS",
"set": "SET", "set": "SET",
"effectAfterRestart": "Effect after restart", "effectAfterRestart": "Effect after restart",
"startAll": "شروع همه",
"pauseAll": "توقف همه",
"deleteTask": "پاک کردن کار", "deleteTask": "پاک کردن کار",
"deleteTaskTip": "فایل های دانلود شده را نگه دارد", "deleteTaskTip": "فایل های دانلود شده را نگه دارد",
"delete": "پاک کردن", "delete": "پاک کردن",
+4
View File
@@ -9,6 +9,8 @@
"downloading": "Загрузка", "downloading": "Загрузка",
"downloaded": "Загружено", "downloaded": "Загружено",
"setting": "Настройки", "setting": "Настройки",
"donate": "Пожертвовать",
"exit": "Выход",
"create": "Создать задачу", "create": "Создать задачу",
"downloadLink": "Ссылка для скачивание", "downloadLink": "Ссылка для скачивание",
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания", "downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
@@ -48,6 +50,8 @@
"notSet": "Не установлено", "notSet": "Не установлено",
"set": "Установлено", "set": "Установлено",
"effectAfterRestart": "Эффект после перезагрузки", "effectAfterRestart": "Эффект после перезагрузки",
"startAll": "Запустить все",
"pauseAll": "Приостановить все",
"deleteTask": "Удалить задачу", "deleteTask": "Удалить задачу",
"deleteTaskTip": "Сохранить загруженные файлы", "deleteTaskTip": "Сохранить загруженные файлы",
"delete": "Удалить", "delete": "Удалить",
+4
View File
@@ -9,6 +9,8 @@
"downloading": "下载中", "downloading": "下载中",
"downloaded": "已完成", "downloaded": "已完成",
"setting": "设置", "setting": "设置",
"donate": "打赏",
"exit": "退出",
"create": "创建任务", "create": "创建任务",
"downloadLink": "下载链接", "downloadLink": "下载链接",
"downloadLinkValid": "请输入下载链接", "downloadLinkValid": "请输入下载链接",
@@ -48,6 +50,8 @@
"notSet": "未设置", "notSet": "未设置",
"set": "已设置", "set": "已设置",
"effectAfterRestart": "此配置项将在重启应用后生效", "effectAfterRestart": "此配置项将在重启应用后生效",
"startAll": "全部开始",
"pauseAll": "全部暂停",
"deleteTask": "删除任务", "deleteTask": "删除任务",
"deleteTaskTip": "保留已下载的文件", "deleteTaskTip": "保留已下载的文件",
"delete": "删除", "delete": "删除",
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+8
View File
@@ -111,6 +111,14 @@ Future<void> continueTask(String id) async {
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null); return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
} }
Future<void> pauseAllTasks() async {
return _parse(() => _client.dio.put("/api/v1/tasks/pause"), null);
}
Future<void> continueAllTasks() async {
return _parse(() => _client.dio.put("/api/v1/tasks/continue"), null);
}
Future<void> deleteTask(String id, bool force) async { Future<void> deleteTask(String id, bool force) async {
return _parse( return _parse(
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null); () => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
@@ -1,10 +1,16 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.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:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';
import '../../../../api/api.dart'; import '../../../../api/api.dart';
import '../../../../api/model/downloader_config.dart'; import '../../../../api/model/downloader_config.dart';
@@ -12,7 +18,9 @@ import '../../../../core/common/start_config.dart';
import '../../../../generated/locales.g.dart'; import '../../../../generated/locales.g.dart';
import '../../../../util/localeManager.dart'; import '../../../../util/localeManager.dart';
import '../../../../util/log_util.dart'; import '../../../../util/log_util.dart';
import '../../../../util/package_info.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../routes/app_pages.dart';
const _startConfigNetwork = "start.network"; const _startConfigNetwork = "start.network";
const _startConfigAddress = "start.address"; const _startConfigAddress = "start.address";
@@ -55,13 +63,153 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
return ret; return ret;
}); });
class AppController extends GetxController { class AppController extends GetxController with WindowListener, TrayListener {
static StartConfig? _defaultStartConfig; static StartConfig? _defaultStartConfig;
final startConfig = StartConfig().obs; final startConfig = StartConfig().obs;
final runningPort = 0.obs; final runningPort = 0.obs;
final downloaderConfig = DownloaderConfig().obs; final downloaderConfig = DownloaderConfig().obs;
late AppLinks _appLinks;
StreamSubscription<Uri>? _linkSubscription;
@override
void onReady() {
super.onReady();
try {
_initDeepLinks();
} catch (e) {
logger.w("initDeepLinks error", e);
}
try {
_initWindows();
} catch (e) {
logger.w("initWindows 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<void> _initDeepLinks() async {
// currently only support android
if (!Util.isAndroid()) {
return;
}
_appLinks = AppLinks();
// Handle link when app is in warm state (front or background)
_linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
await _toCreate(uri);
});
// Check initial link if app was in cold state (terminated)
final uri = await _appLinks.getInitialAppLink();
if (uri != null) {
await _toCreate(uri);
}
}
Future<void> _initWindows() async {
if (!Util.isDesktop()) {
return;
}
windowManager.addListener(this);
}
Future<void> _initTray() async {
if (!Util.isDesktop()) {
return;
}
if (Util.isWindows()) {
await trayManager.setIcon('assets/tray_icon/icon.ico');
} else if (Util.isMacos()) {
await trayManager.setIcon('assets/tray_icon/icon_mac.png',
isTemplate: true);
} else {
await trayManager.setIcon('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.separator(),
MenuItem(
label: "startAll".tr,
onClick: (menuItem) async => {continueAllTasks()},
),
MenuItem(
label: "pauseAll".tr,
onClick: (menuItem) async => {pauseAllTasks()},
),
MenuItem(
label: 'setting'.tr,
onClick: (menuItem) async => {
await windowManager.show(),
await Get.rootDelegate.offAndToNamed(Routes.SETTING),
},
),
MenuItem.separator(),
MenuItem(
label: 'donate'.tr,
onClick: (menuItem) => {
launchUrl(
Uri.parse(
"https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate"),
mode: LaunchMode.externalApplication)
},
),
MenuItem(
label: '${"version".tr}(${packageInfo.version})',
),
MenuItem.separator(),
MenuItem(
label: 'exit'.tr,
onClick: (menuItem) => {windowManager.destroy()},
),
]);
await trayManager.setContextMenu(menu);
trayManager.addListener(this);
}
Future<void> _toCreate(Uri uri) async {
final path = uri.scheme == "magnet"
? uri.toString()
: (await toFile(uri.toString())).path;
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
}
String runningAddress() { String runningAddress() {
if (startConfig.value.network == 'unix') { if (startConfig.value.network == 'unix') {
return startConfig.value.address; return startConfig.value.address;
@@ -5,4 +5,5 @@ class CreateController extends GetxController {
final RxList fileInfos = [].obs; final RxList fileInfos = [].obs;
final RxList selectedIndexes = [].obs; final RxList selectedIndexes = [].obs;
final RxList openedFolders = [].obs; final RxList openedFolders = [].obs;
final isResolving = false.obs;
} }
@@ -1,4 +1,5 @@
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../../api/model/resolve_result.dart'; import '../../../../api/model/resolve_result.dart';
@@ -11,8 +12,8 @@ import '../../../../api/model/request.dart';
import '../../../../util/message.dart'; import '../../../../util/message.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import '../../../views/views/directory_selector.dart'; import '../../../views/directory_selector.dart';
import '../../../views/views/file_list_view.dart'; import '../../../views/file_list_view.dart';
import '../../app/controllers/app_controller.dart'; import '../../app/controllers/app_controller.dart';
import '../controllers/create_controller.dart'; import '../controllers/create_controller.dart';
@@ -26,6 +27,14 @@ class CreateView extends GetView<CreateController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String? filePath = Get.rootDelegate.arguments();
if (_urlController.text.isEmpty && filePath != null) {
_urlController.text = filePath;
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _doResolve();
});
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
@@ -45,21 +54,42 @@ class CreateView extends GetView<CreateController> {
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
child: Column( child: Column(
children: [ children: [
TextFormField( Row(
autofocus: true, children: [
controller: _urlController, Expanded(
minLines: 1, child: TextFormField(
maxLines: 30, autofocus: true,
decoration: InputDecoration( controller: _urlController,
hintText: _hitText(), minLines: 1,
hintStyle: const TextStyle(fontSize: 12), maxLines: 30,
labelText: 'downloadLink'.tr, decoration: InputDecoration(
icon: const Icon(Icons.link)), hintText: _hitText(),
validator: (v) { hintStyle: const TextStyle(fontSize: 12),
return v!.trim().isNotEmpty labelText: 'downloadLink'.tr,
? null icon: const Icon(Icons.link),
: 'downloadLinkValid'.tr; suffixIcon: IconButton(
}), onPressed: _urlController.clear,
icon: const Icon(Icons.clear),
),
),
validator: (v) {
return v!.trim().isNotEmpty
? null
: 'downloadLinkValid'.tr;
}),
),
IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () async {
var pr = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ["torrent"]);
if (pr != null) {
_urlController.text = pr.files[0].path ?? "";
}
}),
],
),
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(15.0), padding: const EdgeInsets.all(15.0),
@@ -70,21 +100,7 @@ class CreateView extends GetView<CreateController> {
), ),
child: RoundedLoadingButton( child: RoundedLoadingButton(
color: Get.theme.colorScheme.secondary, color: Get.theme.colorScheme.secondary,
onPressed: () async { onPressed: _doResolve,
try {
_confirmController.start();
if (_resolveFormKey.currentState!.validate()) {
final rr = await resolve(Request(
url: _urlController.text,
));
await _showResolveDialog(rr);
}
} catch (e) {
showErrorMessage(e);
} finally {
_confirmController.reset();
}
},
controller: _confirmController, controller: _confirmController,
child: Text('confirm'.tr), child: Text('confirm'.tr),
), ),
@@ -98,6 +114,27 @@ class CreateView extends GetView<CreateController> {
); );
} }
Future<void> _doResolve() async {
if (controller.isResolving.value) {
return;
}
controller.isResolving.value = true;
try {
_confirmController.start();
if (_resolveFormKey.currentState!.validate()) {
final rr = await resolve(Request(
url: _urlController.text,
));
await _showResolveDialog(rr);
}
} catch (e) {
showErrorMessage(e);
} finally {
_confirmController.reset();
controller.isResolving.value = false;
}
}
String _hitText() { String _hitText() {
return 'downloadLinkHit'.trParams({ return 'downloadLinkHit'.trParams({
'append': Util.isDesktop() ? 'downloadLinkHitDesktop'.tr : '', 'append': Util.isDesktop() ? 'downloadLinkHitDesktop'.tr : '',
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import '../../../views/views/responsive_builder.dart'; import '../../../views/responsive_builder.dart';
import '../controllers/home_controller.dart'; import '../controllers/home_controller.dart';
class HomeView extends GetView<HomeController> { class HomeView extends GetView<HomeController> {
@@ -11,6 +11,15 @@ class HomeView extends GetView<HomeController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) { 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( return Scaffold(
// extendBody: true, // extendBody: true,
body: Row( body: Row(
@@ -1,21 +1,21 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../../util/message.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:badges/badges.dart' as badges;
import '../../../../generated/locales.g.dart'; import '../../../../generated/locales.g.dart';
import '../../../../util/localeManager.dart'; import '../../../../util/localeManager.dart';
import '../../../../util/message.dart';
import '../../../../util/package_info.dart'; import '../../../../util/package_info.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../views/views/check_list_view.dart'; import '../../../views/check_list_view.dart';
import '../../../views/views/directory_selector.dart'; import '../../../views/directory_selector.dart';
import '../../../views/views/outlined_button_loading.dart'; import '../../../views/outlined_button_loading.dart';
import '../../app/controllers/app_controller.dart'; import '../../app/controllers/app_controller.dart';
import '../controllers/setting_controller.dart'; import '../controllers/setting_controller.dart';
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../views/views/buid_task_list_view.dart'; import '../../../views/buid_task_list_view.dart';
import '../controllers/task_downloaded_controller.dart'; import '../controllers/task_downloaded_controller.dart';
class TaskDownloadedView extends GetView<TaskDownloadedController> { class TaskDownloadedView extends GetView<TaskDownloadedController> {
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../views/views/buid_task_list_view.dart'; import '../../../views/buid_task_list_view.dart';
import '../controllers/task_downloading_controller.dart'; import '../controllers/task_downloading_controller.dart';
class TaskDownloadingView extends GetView<TaskDownloadingController> { class TaskDownloadingView extends GetView<TaskDownloadingController> {
@@ -3,14 +3,14 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../../../api/api.dart'; import '../../api/api.dart';
import '../../../api/model/task.dart'; import '../../api/model/task.dart';
import '../../../util/file_explorer.dart'; import '../../util/file_explorer.dart';
import '../../../util/file_icon.dart'; import '../../util/file_icon.dart';
import '../../../util/icons.dart'; import '../../util/icons.dart';
import '../../../util/message.dart'; import '../../util/message.dart';
import '../../../util/util.dart'; import '../../util/util.dart';
import '../../routes/app_pages.dart'; import '../routes/app_pages.dart';
class BuildTaskListView extends GetView { class BuildTaskListView extends GetView {
final List<Task> tasks; final List<Task> tasks;
@@ -2,8 +2,8 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../util/mac_secure_util.dart'; import '../../util/mac_secure_util.dart';
import '../../../util/util.dart'; import '../../util/util.dart';
class DirectorySelector extends StatefulWidget { class DirectorySelector extends StatefulWidget {
final TextEditingController controller; final TextEditingController controller;
@@ -35,19 +35,17 @@ class _DirectorySelectorState extends State<DirectorySelector> {
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr; return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
}, },
)), )),
!Util.isDesktop() Util.isDesktop() || Util.isAndroid()
? null ? IconButton(
: IconButton(
icon: const Icon(Icons.folder_open), icon: const Icon(Icons.folder_open),
onPressed: () async { onPressed: () async {
if (GetPlatform.isDesktop) { var dir = await FilePicker.platform.getDirectoryPath();
var dir = await FilePicker.platform.getDirectoryPath(); if (dir != null) {
if (dir != null) { widget.controller.text = dir;
widget.controller.text = dir; MacSecureUtil.saveBookmark(dir);
MacSecureUtil.saveBookmark(dir);
}
} }
}) })
: null
].where((e) => e != null).map((e) => e!).toList(), ].where((e) => e != null).map((e) => e!).toList(),
); );
} }
@@ -3,14 +3,14 @@ import 'dart:ui' as ui;
import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:fluent_ui/fluent_ui.dart' as fluent;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../api/model/resource.dart'; import '../../api/model/resource.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../../../util/file_icon.dart'; import '../../util/file_icon.dart';
import '../../../util/icons.dart'; import '../../util/icons.dart';
import '../../../util/util.dart'; import '../../util/util.dart';
import '../../modules/app/controllers/app_controller.dart'; import '../modules/app/controllers/app_controller.dart';
import '../../modules/create/controllers/create_controller.dart'; import '../modules/create/controllers/create_controller.dart';
class FileListView extends GetView { class FileListView extends GetView {
final List<FileInfo> files; final List<FileInfo> files;
+20
View File
@@ -23,6 +23,8 @@ class LocaleKeys {
static const downloading = 'downloading'; static const downloading = 'downloading';
static const downloaded = 'downloaded'; static const downloaded = 'downloaded';
static const setting = 'setting'; static const setting = 'setting';
static const donate = 'donate';
static const exit = 'exit';
static const create = 'create'; static const create = 'create';
static const downloadLink = 'downloadLink'; static const downloadLink = 'downloadLink';
static const downloadLinkValid = 'downloadLinkValid'; static const downloadLinkValid = 'downloadLinkValid';
@@ -62,6 +64,8 @@ class LocaleKeys {
static const notSet = 'notSet'; static const notSet = 'notSet';
static const set = 'set'; static const set = 'set';
static const effectAfterRestart = 'effectAfterRestart'; static const effectAfterRestart = 'effectAfterRestart';
static const startAll = 'startAll';
static const pauseAll = 'pauseAll';
static const deleteTask = 'deleteTask'; static const deleteTask = 'deleteTask';
static const deleteTaskTip = 'deleteTaskTip'; static const deleteTaskTip = 'deleteTaskTip';
static const delete = 'delete'; static const delete = 'delete';
@@ -82,6 +86,8 @@ class Locales {
'downloading': 'downloading', 'downloading': 'downloading',
'downloaded': 'downloaded', 'downloaded': 'downloaded',
'setting': 'Setting', 'setting': 'Setting',
'donate': 'Donate',
'exit': 'Exit',
'create': 'Create Task', 'create': 'Create Task',
'downloadLink': 'Download Link', 'downloadLink': 'Download Link',
'downloadLinkValid': 'Please enter the download link', 'downloadLinkValid': 'Please enter the download link',
@@ -123,6 +129,8 @@ class Locales {
'notSet': 'NS', 'notSet': 'NS',
'set': 'SET', 'set': 'SET',
'effectAfterRestart': 'Effect after restart', 'effectAfterRestart': 'Effect after restart',
'startAll': 'Start All',
'pauseAll': 'Pause All',
'deleteTask': 'Delete Task', 'deleteTask': 'Delete Task',
'deleteTaskTip': 'Keep downloaded files', 'deleteTaskTip': 'Keep downloaded files',
'delete': 'Delete', 'delete': 'Delete',
@@ -139,6 +147,8 @@ class Locales {
'selectAll': 'انتخاب همه', 'selectAll': 'انتخاب همه',
'task': 'کار', 'task': 'کار',
'setting': 'تنظیمات', 'setting': 'تنظیمات',
'donate': 'کمک مالی',
'exit': 'خروج',
'create': 'ایجاد کار', 'create': 'ایجاد کار',
'downloading': 'دانلود کردن', 'downloading': 'دانلود کردن',
'downloaded': 'دانلود شدن', 'downloaded': 'دانلود شدن',
@@ -178,6 +188,8 @@ class Locales {
'notSet': 'NS', 'notSet': 'NS',
'set': 'SET', 'set': 'SET',
'effectAfterRestart': 'Effect after restart', 'effectAfterRestart': 'Effect after restart',
'startAll': 'شروع همه',
'pauseAll': 'توقف همه',
'deleteTask': 'پاک کردن کار', 'deleteTask': 'پاک کردن کار',
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد', 'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
'delete': 'پاک کردن', 'delete': 'پاک کردن',
@@ -196,6 +208,8 @@ class Locales {
'downloading': 'Загрузка', 'downloading': 'Загрузка',
'downloaded': 'Загружено', 'downloaded': 'Загружено',
'setting': 'Настройки', 'setting': 'Настройки',
'donate': 'Пожертвовать',
'exit': 'Выход',
'create': 'Создать задачу', 'create': 'Создать задачу',
'downloadLink': 'Ссылка для скачивание', 'downloadLink': 'Ссылка для скачивание',
'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания', 'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания',
@@ -237,6 +251,8 @@ class Locales {
'notSet': 'Не установлено', 'notSet': 'Не установлено',
'set': 'Установлено', 'set': 'Установлено',
'effectAfterRestart': 'Эффект после перезагрузки', 'effectAfterRestart': 'Эффект после перезагрузки',
'startAll': 'Запустить все',
'pauseAll': 'Приостановить все',
'deleteTask': 'Удалить задачу', 'deleteTask': 'Удалить задачу',
'deleteTaskTip': 'Сохранить загруженные файлы', 'deleteTaskTip': 'Сохранить загруженные файлы',
'delete': 'Удалить', 'delete': 'Удалить',
@@ -255,6 +271,8 @@ class Locales {
'downloading': '下载中', 'downloading': '下载中',
'downloaded': '已完成', 'downloaded': '已完成',
'setting': '设置', 'setting': '设置',
'donate': '打赏',
'exit': '退出',
'create': '创建任务', 'create': '创建任务',
'downloadLink': '下载链接', 'downloadLink': '下载链接',
'downloadLinkValid': '请输入下载链接', 'downloadLinkValid': '请输入下载链接',
@@ -294,6 +312,8 @@ class Locales {
'notSet': '未设置', 'notSet': '未设置',
'set': '已设置', 'set': '已设置',
'effectAfterRestart': '此配置项将在重启应用后生效', 'effectAfterRestart': '此配置项将在重启应用后生效',
'startAll': '全部开始',
'pauseAll': '全部暂停',
'deleteTask': '删除任务', 'deleteTask': '删除任务',
'deleteTaskTip': '保留已下载的文件', 'deleteTaskTip': '保留已下载的文件',
'delete': '删除', 'delete': '删除',
+15
View File
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:window_manager/window_manager.dart';
import 'api/api.dart' as api; import 'api/api.dart' as api;
import 'app/modules/app/controllers/app_controller.dart'; import 'app/modules/app/controllers/app_controller.dart';
@@ -11,6 +12,7 @@ import 'util/localeManager.dart';
import 'util/log_util.dart'; import 'util/log_util.dart';
import 'util/mac_secure_util.dart'; import 'util/mac_secure_util.dart';
import 'util/package_info.dart'; import 'util/package_info.dart';
import 'util/util.dart';
void main() async { void main() async {
await init(); await init();
@@ -21,6 +23,19 @@ void main() async {
Future<void> init() async { Future<void> init() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
if (Util.isDesktop()) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(1024, 768),
center: true,
skipTaskbar: false,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
await windowManager.setPreventClose(true);
});
}
final controller = Get.put(AppController()); final controller = Get.put(AppController());
try { try {
+4
View File
@@ -41,6 +41,10 @@ class Util {
return Platform.isWindows || Platform.isLinux || Platform.isMacOS; return Platform.isWindows || Platform.isLinux || Platform.isMacOS;
} }
static isWindows() {
return !kIsWeb && Platform.isWindows;
}
static isMacos() { static isMacos() {
return !kIsWeb && Platform.isMacOS; return !kIsWeb && Platform.isMacOS;
} }
+13
View File
@@ -4,6 +4,19 @@ import FlutterMacOS
@NSApplicationMain @NSApplicationMain
class AppDelegate: FlutterAppDelegate { class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 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 return true
} }
} }
+56
View File
@@ -17,6 +17,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.0" version: "5.0.0"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: d572dcdff49c4cfcfa6f315e2683e518ec6eb54e084d01e51d9631a4dcc1b5e8
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.2"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@@ -518,6 +526,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.4.0" 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: meta:
dependency: transitive dependency: transitive
description: description:
@@ -710,6 +726,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.27.5" 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: scroll_pos:
dependency: transitive dependency: transitive
description: description:
@@ -798,6 +822,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.0.2" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -891,6 +923,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.0.0" 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: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -899,6 +939,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
uri_to_file:
dependency: "direct main"
description:
name: uri_to_file
sha256: "84afd633b1492fc465c768141e1a29edd519061bf99935b6b4d0d5de8ec7c108"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -995,6 +1043,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.1.4" 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: xdg_directories:
dependency: transitive dependency: transitive
description: description:
+7 -4
View File
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.3.1+1 version: 1.3.2+1
environment: environment:
sdk: ">=2.17.6 <3.0.0" sdk: ">=2.17.6 <3.0.0"
@@ -53,6 +53,10 @@ dependencies:
fluent_ui: ^4.4.0 fluent_ui: ^4.4.0
path: any path: any
badges: ^3.0.3 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: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@@ -83,9 +87,8 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - macos/Frameworks/libgopeed.dylib - assets/tray_icon/
# - macos/lib/libgopeed.dylib
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware
+6
View File
@@ -7,6 +7,12 @@
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) { _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 // Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger. // new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {