Compare commits

..
15 Commits
33 changed files with 501 additions and 340 deletions

No files matched your search

+16 -110
View File
@@ -223,126 +223,32 @@ jobs:
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
- run: |
sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev libappindicator3-dev
sudo apt update -y
# flutter build dependencies
sudo apt install -y ninja-build libgtk-3-dev libappindicator3-dev
# rpm build dependencies
sudo apt install -y rpm patchelf
# appimage build dependencies
sudo apt install -y libfuse2 locate
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x appimagetool
sudo mv appimagetool /usr/local/bin/
- name: Build
env:
VERSION: ${{ needs.get-release.outputs.tag_name }}
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
cd ui/flutter
flutter build linux
mkdir -p build/dist
# Build portable
tar -C build/linux/x64/release/bundle -czaf build/dist/Gopeed-$VERSION-linux-amd64-portable.tar.gz ./
# Build deb package
mkdir -p debian/gopeed/opt/gopeed
mkdir -p debian/gopeed/usr/share/applications
mkdir -p debian/gopeed/usr/share/icons/hicolor/scalable/apps
mkdir -p debian/gopeed/DEBIAN
cp -r build/linux/x64/release/bundle/* debian/gopeed/opt/gopeed/
cp assets/icon/icon.svg debian/gopeed/usr/share/icons/hicolor/scalable/apps/com.gopeed.Gopeed.svg
cat>debian/gopeed/DEBIAN/postinst<<EOF
#!/bin/bash
ln -sf /opt/gopeed/gopeed /usr/bin/gopeed
EOF
cat>debian/gopeed/DEBIAN/postrm<<EOF
#!/bin/bash
rm -rf /usr/bin/gopeed
EOF
chmod 0755 debian/gopeed/DEBIAN/postinst
chmod 0755 debian/gopeed/DEBIAN/postrm
cat>debian/gopeed/DEBIAN/control<<EOF
Package: gopeed
Version: ${VERSION:1}
Architecture: amd64
Essential: no
Priority: optional
Depends:
Maintainer: monkeyWie
Description: High speed downloader that supports all platforms.
EOF
cp linux/assets/com.gopeed.Gopeed.desktop debian/gopeed/usr/share/applications/gopeed.desktop
dpkg-deb --build debian/gopeed
mv debian/gopeed.deb build/dist/Gopeed-$VERSION-linux-amd64.deb
# Build AppImage
sudo apt-get -y install libfuse2
wget -O appimage-builder https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
chmod +x appimage-builder
mkdir AppDir
cp -r build/linux/x64/release/bundle/* AppDir
mkdir -p AppDir/usr/share/icons/hicolor/scalable/apps/
cp assets/icon/icon.svg AppDir/usr/share/icons/hicolor/scalable/apps/gopeed.svg
cat>AppImageBuilder.yml<<EOF
version: 1
AppDir:
path: ${PWD}/AppDir
app_info:
id: com.gopeed.gopeed
name: Gopeed
icon: gopeed
version: ${VERSION:1}
exec: gopeed
exec_args: \$@
apt:
arch: amd64
allow_unauthenticated: true
sources:
- sourceline: deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted
universe multiverse
- sourceline: deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main
restricted universe multiverse
- sourceline: deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports
main restricted universe multiverse
- sourceline: deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted
universe multiverse
include: []
files:
include: []
exclude:
- usr/share/man
- usr/share/doc/*/README.*
- usr/share/doc/*/changelog.*
- usr/share/doc/*/NEWS.*
- usr/share/doc/*/TODO.*
test:
fedora-30:
image: appimagecrafters/tests-env:fedora-30
command: ./AppRun
debian-stable:
image: appimagecrafters/tests-env:debian-stable
command: ./AppRun
archlinux-latest:
image: appimagecrafters/tests-env:archlinux-latest
command: ./AppRun
centos-7:
image: appimagecrafters/tests-env:centos-7
command: ./AppRun
ubuntu-xenial:
image: appimagecrafters/tests-env:ubuntu-xenial
command: ./AppRun
AppImage:
arch: x86_64
update-information: guess
EOF
./appimage-builder --skip-tests --recipe AppImageBuilder.yml
mv Gopeed-*-x86_64.AppImage build/dist/Gopeed-$VERSION-linux-amd64.AppImage
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
flutter_distributor package --platform linux --targets appimage,deb
cd dist/*
mv gopeed-*-linux.AppImage Gopeed-${VERSION}-linux-x86_64.AppImage
mv gopeed-*-linux.deb Gopeed-${VERSION}-linux-amd64.deb
- name: Upload
uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ui/flutter/build/dist/*
asset_path: ui/flutter/dist/*/*
overwrite: true
build-snap:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
+1 -1
View File
@@ -4,7 +4,7 @@
*.dll
*.so
*.dylib
ui/flutter/dist/
# Test binary, built with `go test -c`
*.test
+2
View File
@@ -21,6 +21,8 @@ type Fetcher interface {
Pause() error
Close() error
// Stats refreshes health statistics and returns the latest information
Stats() any
// Meta returns the meta information of the download.
Meta() *FetcherMeta
// Progress returns the progress of the download.
+11
View File
@@ -128,6 +128,7 @@ func (f *Fetcher) Start() (err error) {
file.Download()
}
}
return
}
@@ -188,6 +189,16 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta
}
func (f *Fetcher) Stats() any {
stats := f.torrent.Stats()
baseStats := &bt.Stats{
TotalPeers: stats.TotalPeers,
ActivePeers: stats.ActivePeers,
ConnectedSeeders: stats.ConnectedSeeders,
}
return baseStats
}
func (f *Fetcher) Progress() fetcher.Progress {
if !f.torrentReady.Load() {
return f.progress
+8 -2
View File
@@ -18,6 +18,7 @@ import (
"os"
"path"
"strconv"
"strings"
"time"
)
@@ -104,8 +105,8 @@ func (f *Fetcher) Resolve(req *base.Request) error {
Range: false,
Files: []*base.FileInfo{},
}
if base.HttpCodePartialContent == httpResp.StatusCode {
// 返回206响应码表示支持断点下载
if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) {
// 1.返回206响应码表示支持断点下载 2.不返回206但是Accept-Ranges首部并且等于bytes也表示支持断点下载
res.Range = true
// 解析资源大小: bytes 0-1000/1001 => 1001
contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange))
@@ -217,6 +218,11 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta
}
func (f *Fetcher) Stats() any {
// todo http download health
return &fhttp.Stats{}
}
func (f *Fetcher) Progress() fetcher.Progress {
p := make(fetcher.Progress, 0)
if len(f.chunks) > 0 {
+2
View File
@@ -16,10 +16,12 @@ const (
HttpCodePartialContent = 206
HttpHeaderRange = "Range"
HttpHeaderAcceptRanges = "Accept-Ranges"
HttpHeaderContentLength = "Content-Length"
HttpHeaderContentRange = "Content-Range"
HttpHeaderContentDisposition = "Content-Disposition"
HttpHeaderUserAgent = "Usr-Agent"
HttpHeaderBytes = "bytes"
HttpHeaderRangeFormat = "bytes=%d-%d"
)
+21
View File
@@ -6,6 +6,7 @@ import (
"github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/internal/logger"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/protocol/http"
"github.com/GopeedLab/gopeed/pkg/util"
gonanoid "github.com/matoous/go-nanoid/v2"
"github.com/rs/zerolog"
@@ -594,6 +595,26 @@ func (d *Downloader) watch(task *Task) {
d.emit(EventKeyDone, task)
d.emit(EventKeyFinally, task, err)
d.notifyRunning()
if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok {
downloadFilePath := task.Meta.SingleFilepath()
if e.AutoTorrent && strings.HasSuffix(downloadFilePath, ".torrent") {
go func() {
_, err2 := d.CreateDirect(
&base.Request{
URL: downloadFilePath,
},
&base.Options{
Path: task.Meta.Opts.Path,
SelectFiles: make([]int, 0),
})
if err2 != nil {
d.Logger.Error().Err(err2).Msgf("auto create torrent task failed, task id: %s", task.ID)
}
}()
}
}
}
func (d *Downloader) doOnError(task *Task, err error) {
+9
View File
@@ -3,3 +3,12 @@ package bt
type ReqExtra struct {
Trackers []string `json:"trackers"`
}
// Stats for download
type Stats struct {
// bt stats
// health indicators of torrents, from large to small, ConnectedSeeders are also the key to the health of seed resources
TotalPeers int `json:"totalPeers"`
ActivePeers int `json:"activePeers"`
ConnectedSeeders int `json:"connectedSeeders"`
}
+8
View File
@@ -8,4 +8,12 @@ type ReqExtra struct {
type OptsExtra struct {
Connections int `json:"connections"`
// AutoTorrent when task download complete, and it is a .torrent file, it will be auto create a new task for the torrent file
AutoTorrent bool `json:"autoTorrent"`
}
// Stats for download
type Stats struct {
// http stats
// health indicators of http
}
+1
View File
@@ -0,0 +1 @@
output: dist/
+1
View File
@@ -25,6 +25,7 @@ class Options {
@JsonSerializable()
class OptsExtraHttp {
int connections = 0;
bool autoTorrent = false;
OptsExtraHttp();
+4 -1
View File
@@ -32,9 +32,12 @@ Map<String, dynamic> _$OptionsToJson(Options instance) {
}
OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json) =>
OptsExtraHttp()..connections = json['connections'] as int;
OptsExtraHttp()
..connections = json['connections'] as int
..autoTorrent = json['autoTorrent'] as bool;
Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance) =>
<String, dynamic>{
'connections': instance.connections,
'autoTorrent': instance.autoTorrent,
};
@@ -6,6 +6,7 @@ import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tray_manager/tray_manager.dart';
@@ -21,6 +22,7 @@ import '../../../../util/locale_manager.dart';
import '../../../../util/log_util.dart';
import '../../../../util/package_info.dart';
import '../../../../util/util.dart';
import '../../../../util/window_util.dart' as windows_util;
import '../../../routes/app_pages.dart';
const _startConfigNetwork = "start.network";
@@ -49,7 +51,12 @@ const allTrackerCdns = [
[
"https://ghproxy.com/https://raw.githubusercontent.com",
r".*github.com(/.*)/raw(/.*)"
]
],
// mirror.ghproxy.com: https://mirror.ghproxy.com/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt
[
"https://mirror.ghproxy.com/https://raw.githubusercontent.com",
r".*github.com(/.*)/raw(/.*)"
],
];
final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
key: (v) => v as String,
@@ -118,6 +125,26 @@ class AppController extends GetxController with WindowListener, TrayListener {
trayManager.popUpContextMenu();
}
@override
void onWindowMaximize() {
windows_util.saveState(isMaximized: true);
}
@override
void onWindowUnmaximize() {
windows_util.saveState(isMaximized: false);
}
final _windowsResizeSave = Util.debounce(() async {
final size = await windowManager.getSize();
windows_util.saveState(width: size.width, height: size.height);
}, 500);
@override
void onWindowResize() {
_windowsResizeSave();
}
Future<void> _initDeepLinks() async {
// currently only support android
if (!Util.isAndroid()) {
@@ -391,9 +418,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
if (Util.isDesktop()) {
config.downloadDir = (await getDownloadsDirectory())?.path ?? "./";
} else if (Util.isAndroid()) {
config.downloadDir = (await getExternalStorageDirectory())?.path ??
(await getApplicationDocumentsDirectory()).path;
return;
final downloadDir = (await DownloadsPath.downloadsDirectory())?.path;
if (downloadDir != null) {
config.downloadDir = '$downloadDir/Gopeed';
} else {
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
}
} else if (Util.isIOS()) {
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
} else {
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
@@ -14,22 +15,24 @@ class AppView extends GetView<AppController> {
@override
Widget build(BuildContext context) {
final config = controller.downloaderConfig.value;
return GetMaterialApp.router(
useInheritedMediaQuery: true,
debugShowCheckedModeBanner: false,
theme: GopeedTheme.light,
darkTheme: GopeedTheme.dark,
themeMode: ThemeMode.values.byName(config.extra.themeMode),
translations: messages,
locale: toLocale(config.extra.locale),
fallbackLocale: fallbackLocale,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(),
getPages: AppPages.routes,
return WithForegroundTask(
child: GetMaterialApp.router(
useInheritedMediaQuery: true,
debugShowCheckedModeBanner: false,
theme: GopeedTheme.light,
darkTheme: GopeedTheme.dark,
themeMode: ThemeMode.values.byName(config.extra.themeMode),
translations: messages,
locale: toLocale(config.extra.locale),
fallbackLocale: fallbackLocale,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(),
getPages: AppPages.routes,
),
);
}
}
@@ -4,16 +4,14 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:gopeed/app/modules/history/controller/history_controller.dart';
import 'package:gopeed/app/modules/history/views/history_view.dart';
import 'package:path/path.dart' as path;
import 'package:rounded_loading_button/rounded_loading_button.dart';
import '../../../../api/api.dart';
import '../../../../api/model/create_task.dart';
import '../../../../api/model/options.dart';
import '../../../../api/model/request.dart';
import '../../../../api/model/resolve_result.dart';
import '../../../../api/model/resource.dart';
import '../../../../util/input_formatter.dart';
import '../../../../util/message.dart';
import '../../../../util/util.dart';
@@ -21,6 +19,8 @@ import '../../../routes/app_pages.dart';
import '../../../views/directory_selector.dart';
import '../../../views/file_list_view.dart';
import '../../app/controllers/app_controller.dart';
import '../../history/controller/history_controller.dart';
import '../../history/views/history_view.dart';
import '../controllers/create_controller.dart';
class CreateView extends GetView<CreateController> {
@@ -409,15 +409,20 @@ class CreateView extends GetView<CreateController> {
try {
_confirmController.start();
if (_confirmFormKey.currentState!.validate()) {
final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty
final isWebFileChosen =
Util.isWeb() && controller.fileDataUri.isNotEmpty;
final submitUrl = isWebFileChosen
? controller.fileDataUri.value
: _urlController.text;
final urls = Util.textToLines(submitUrl);
// Add url to the history
for (final url in urls) {
_historyController.addHistory(url);
if (!isWebFileChosen) {
for (final url in urls) {
_historyController.addHistory(url);
}
}
/*
Check if is direct download, there has two ways to direct download
1. Direct download option is checked
@@ -433,7 +438,7 @@ class CreateView extends GetView<CreateController> {
name: isMultiLine ? "" : _renameController.text,
path: _pathController.text,
selectFiles: [],
extra: parseReqOpts())));
extra: parseReqOptsExtra())));
}));
Get.rootDelegate.offNamed(Routes.TASK);
} else {
@@ -471,11 +476,10 @@ class CreateView extends GetView<CreateController> {
return reqExtra;
}
Object? parseReqOpts() {
return _connectionsController.text.isEmpty
? null
: (OptsExtraHttp()
..connections = int.parse(_connectionsController.text));
Object? parseReqOptsExtra() {
return OptsExtraHttp()
..connections = int.tryParse(_connectionsController.text) ?? 0
..autoTorrent = true;
}
String _hitText() {
@@ -541,7 +545,7 @@ class CreateView extends GetView<CreateController> {
showMessage('tip'.tr, 'noFileSelected'.tr);
return;
}
final optExtra = parseReqOpts();
final optExtra = parseReqOptsExtra();
if (createFormKey.currentState!.validate()) {
if (rr.id.isEmpty) {
// from extension resolve result
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
import '../../../routes/app_pages.dart';
@@ -12,7 +11,7 @@ class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) {
switch (currentRoute?.location) {
switch (currentRoute?.uri.path) {
case Routes.EXTENSION:
controller.currentIndex.value = 1;
break;
@@ -24,141 +23,139 @@ class HomeView extends GetView<HomeController> {
break;
}
return WithForegroundTask(
child: Scaffold(
// extendBody: true,
body: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
!ResponsiveBuilder.isNarrow(context)
? NavigationRail(
extended: true,
labelType: NavigationRailLabelType.none,
minExtendedWidth: 170,
groupAlignment: 0,
// useIndicator: false,
onDestinationSelected: (int index) {
controller.currentIndex.value = index;
switch (index) {
case 0:
delegate.offAndToNamed(Routes.TASK);
break;
case 1:
delegate.offAndToNamed(Routes.EXTENSION);
break;
case 2:
delegate.offAndToNamed(Routes.SETTING);
break;
}
},
destinations: [
NavigationRailDestination(
icon: const Icon(Icons.task),
selectedIcon: const Icon(Icons.task),
label: Text('task'.tr),
),
NavigationRailDestination(
icon: const Icon(Icons.extension),
selectedIcon: const Icon(Icons.extension),
label: Text('extensions'.tr),
),
NavigationRailDestination(
icon: const Icon(Icons.settings),
selectedIcon: const Icon(Icons.settings),
label: Text('setting'.tr),
),
],
selectedIndex: controller.currentIndex.value,
leading: const Icon(Icons.menu),
// trailing: const Icon(Icons.info_outline),
)
: const SizedBox.shrink(),
Expanded(
child: GetRouterOutlet(
initialRoute: Routes.TASK,
// anchorRoute: '/',
// filterPages: (afterAnchor) {
// logger.w(afterAnchor);
// logger.w(afterAnchor.take(1));
// return afterAnchor.take(1);
// },
))
]),
bottomNavigationBar: ResponsiveBuilder.isNarrow(context)
? BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: const Icon(Icons.task),
label: 'task'.tr,
),
BottomNavigationBarItem(
icon: const Icon(Icons.extension),
label: 'extensions'.tr,
),
BottomNavigationBarItem(
icon: const Icon(Icons.settings),
label: 'setting'.tr,
),
],
currentIndex: controller.currentIndex.value,
// selectedItemColor: Get.theme.highlightColor,
onTap: (index) {
controller.currentIndex.value = index;
switch (index) {
case 0:
delegate.offAndToNamed(Routes.TASK);
break;
case 1:
delegate.offAndToNamed(Routes.EXTENSION);
break;
case 2:
delegate.offAndToNamed(Routes.SETTING);
break;
}
},
)
// StylishBottomBar(
// option: AnimatedBarOptions(
// iconSize: 32,
// barAnimation: BarAnimation.blink,
// iconStyle: IconStyle.Default,
// opacity: 0.3,
// ),
// items: [
// BottomBarItem(
// icon: const Icon(Icons.file_download),
// selectedColor: Get.theme.primaryColor,
// title: Text('downloading'.tr)),
// BottomBarItem(
// icon: const Icon(Icons.done),
// selectedColor: Get.theme.primaryColor,
// title: Text('downloaded'.tr)),
// BottomBarItem(
// icon: const Icon(Icons.settings),
// selectedColor: Get.theme.primaryColor,
// title: Text('setting'.tr)),
// ],
// // hasNotch: true,
// currentIndex: controller.currentIndex.value,
// onTap: (index) {
// switch (index) {
// case 0:
// delegate.toNamed(Routes.DOWNLOADING);
// controller.currentIndex.value = 0;
// break;
// case 1:
// delegate.toNamed(Routes.DOWNLOADED);
// controller.currentIndex.value = 1;
// break;
// case 2:
// delegate.toNamed(Routes.SETTING);
// controller.currentIndex.value = 2;
// break;
// }
// },
// )
: const SizedBox.shrink(),
),
return Scaffold(
// extendBody: true,
body: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
!ResponsiveBuilder.isNarrow(context)
? NavigationRail(
extended: true,
labelType: NavigationRailLabelType.none,
minExtendedWidth: 170,
groupAlignment: 0,
// useIndicator: false,
onDestinationSelected: (int index) {
controller.currentIndex.value = index;
switch (index) {
case 0:
delegate.offAndToNamed(Routes.TASK);
break;
case 1:
delegate.offAndToNamed(Routes.EXTENSION);
break;
case 2:
delegate.offAndToNamed(Routes.SETTING);
break;
}
},
destinations: [
NavigationRailDestination(
icon: const Icon(Icons.task),
selectedIcon: const Icon(Icons.task),
label: Text('task'.tr),
),
NavigationRailDestination(
icon: const Icon(Icons.extension),
selectedIcon: const Icon(Icons.extension),
label: Text('extensions'.tr),
),
NavigationRailDestination(
icon: const Icon(Icons.settings),
selectedIcon: const Icon(Icons.settings),
label: Text('setting'.tr),
),
],
selectedIndex: controller.currentIndex.value,
leading: const Icon(Icons.menu),
// trailing: const Icon(Icons.info_outline),
)
: const SizedBox.shrink(),
Expanded(
child: GetRouterOutlet(
initialRoute: Routes.TASK,
// anchorRoute: '/',
// filterPages: (afterAnchor) {
// logger.w(afterAnchor);
// logger.w(afterAnchor.take(1));
// return afterAnchor.take(1);
// },
))
]),
bottomNavigationBar: ResponsiveBuilder.isNarrow(context)
? BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: const Icon(Icons.task),
label: 'task'.tr,
),
BottomNavigationBarItem(
icon: const Icon(Icons.extension),
label: 'extensions'.tr,
),
BottomNavigationBarItem(
icon: const Icon(Icons.settings),
label: 'setting'.tr,
),
],
currentIndex: controller.currentIndex.value,
// selectedItemColor: Get.theme.highlightColor,
onTap: (index) {
controller.currentIndex.value = index;
switch (index) {
case 0:
delegate.offAndToNamed(Routes.TASK);
break;
case 1:
delegate.offAndToNamed(Routes.EXTENSION);
break;
case 2:
delegate.offAndToNamed(Routes.SETTING);
break;
}
},
)
// StylishBottomBar(
// option: AnimatedBarOptions(
// iconSize: 32,
// barAnimation: BarAnimation.blink,
// iconStyle: IconStyle.Default,
// opacity: 0.3,
// ),
// items: [
// BottomBarItem(
// icon: const Icon(Icons.file_download),
// selectedColor: Get.theme.primaryColor,
// title: Text('downloading'.tr)),
// BottomBarItem(
// icon: const Icon(Icons.done),
// selectedColor: Get.theme.primaryColor,
// title: Text('downloaded'.tr)),
// BottomBarItem(
// icon: const Icon(Icons.settings),
// selectedColor: Get.theme.primaryColor,
// title: Text('setting'.tr)),
// ],
// // hasNotch: true,
// currentIndex: controller.currentIndex.value,
// onTap: (index) {
// switch (index) {
// case 0:
// delegate.toNamed(Routes.DOWNLOADING);
// controller.currentIndex.value = 0;
// break;
// case 1:
// delegate.toNamed(Routes.DOWNLOADED);
// controller.currentIndex.value = 1;
// break;
// case 2:
// delegate.toNamed(Routes.SETTING);
// controller.currentIndex.value = 2;
// break;
// }
// },
// )
: const SizedBox.shrink(),
);
});
}
@@ -365,6 +365,18 @@ class SettingView extends GetView<SettingController> {
);
}
buildThanks() {
const thankPage =
'https://github.com/GopeedLab/gopeed/graphs/contributors';
return ListTile(
title: Text('thanks'.tr),
subtitle: Text('thanksDesc'.tr),
onTap: () {
launchUrl(Uri.parse(thankPage), mode: LaunchMode.externalApplication);
},
);
}
// advanced config proxy items start
final buildProxy = _buildConfigItem(
'proxy',
@@ -416,14 +428,14 @@ class SettingView extends GetView<SettingController> {
);
final arr = proxy.host.split(':');
var ip = '';
var host = '';
var port = '';
if (arr.length > 1) {
ip = arr[0];
host = arr[0];
port = arr[1];
}
final ipController = TextEditingController(text: ip);
final ipController = TextEditingController(text: host);
final portController = TextEditingController(text: port);
updateAddress() async {
final newAddress = '${ipController.text}:${portController.text}';
@@ -436,18 +448,14 @@ class SettingView extends GetView<SettingController> {
ipController.addListener(updateAddress);
portController.addListener(updateAddress);
final host = [
final server = [
Flexible(
child: TextFormField(
controller: ipController,
decoration: const InputDecoration(
labelText: 'IP',
contentPadding: EdgeInsets.all(0.0),
decoration: InputDecoration(
labelText: 'server'.tr,
contentPadding: const EdgeInsets.all(0.0),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
],
),
),
const Padding(padding: EdgeInsets.only(left: 10)),
@@ -478,10 +486,6 @@ class SettingView extends GetView<SettingController> {
labelText: 'username'.tr,
contentPadding: const EdgeInsets.all(0.0),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
],
),
),
const Padding(padding: EdgeInsets.only(left: 10)),
@@ -492,11 +496,6 @@ class SettingView extends GetView<SettingController> {
labelText: 'password'.tr,
contentPadding: const EdgeInsets.all(0.0),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
NumericalRangeFormatter(min: 0, max: 65535),
],
),
),
];
@@ -508,7 +507,7 @@ class SettingView extends GetView<SettingController> {
switcher,
scheme,
Row(
children: host,
children: server,
),
Row(
children: auth,
@@ -702,6 +701,7 @@ class SettingView extends GetView<SettingController> {
children: _addDivider([
buildHomepage(),
buildVersion(),
buildThanks(),
]),
)),
]),
@@ -35,7 +35,7 @@ class _DirectorySelectorState extends State<DirectorySelector> {
return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr;
},
)),
!Util.isWeb() && !Util.isIOS()
!Util.isWeb() && !Util.isAndroid() && !Util.isIOS()
? IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () async {
+4
View File
@@ -79,7 +79,11 @@ const enUS = {
'serviceText': 'Running',
'network': 'Network',
'proxy': 'Proxy',
'server': 'Server',
'username': 'Username',
'password': 'Password',
'thanks': 'Thanks',
'thanksDesc':
'Thanks to all the contributors who have helped build and develop the Gopeed community!',
},
};
+3
View File
@@ -57,5 +57,8 @@ const faIR = {
'newVersionTitle': '@version عنوان: کشف نسخه جدید',
'newVersionUpdate': 'بروزرسانی',
'newVersionLater': 'بعداً',
'thanks': 'تشکر',
'thanksDesc':
'از تمامی افرادی که در ساخت و توسعه‌ی جامعه‌ی Gopeed مشارکت کرده‌اند، سپاسگزاری می‌کنیم!',
}
};
+2
View File
@@ -59,5 +59,7 @@ const jaJP = {
'newVersionTitle': '新しいバージョン @version を発見する',
'newVersionUpdate': 'アップデート',
'newVersionLater': '後で',
'thanks': '感謝',
'thanksDesc': 'Gopeedコミュニティの建設に協力してくださったすべての貢献者の方々に感謝します!',
}
};
+3
View File
@@ -73,5 +73,8 @@ const ruRU = {
'history': 'История',
'clearHistory': 'Очистить историю',
'noHistoryFound': 'История не найдена',
'thanks': 'Благодарности',
'thanksDesc':
'Благодарим всех участников за их вклад в строительство и развитие сообщества Gopeed!',
}
};
+3
View File
@@ -77,7 +77,10 @@ const zhCN = {
'serviceText': '运行中',
'network': '网络',
'proxy': '代理',
'server': '服务器',
'username': '用户名',
'password': '密码',
'thanks': '鸣谢',
'thanksDesc': '感谢所有为 Gopeed 社区建设添砖加瓦的贡献者们!',
}
};
+4 -1
View File
@@ -24,7 +24,7 @@ const zhTW = {
'noStoragePermission': '沒有存儲權限',
'selectFile': '選擇文件',
'rename': '重命名',
'basic': '基礎',
'basic': '一般',
'advanced': '進階',
'general': '通用設定',
'downloadDir': '下載目録',
@@ -77,7 +77,10 @@ const zhTW = {
'serviceText': '執行中',
'network': '網路',
'proxy': '代理',
'server': '伺服器',
'username': '名稱',
'password': '密碼',
'thanks': '鳴謝',
'thanksDesc': '感謝所有為 Gopeed 社群建設添磚加瓦的貢獻者們!'
}
};
+10 -3
View File
@@ -13,6 +13,7 @@ import 'util/log_util.dart';
import 'util/mac_secure_util.dart';
import 'util/package_info.dart';
import 'util/util.dart';
import 'util/window_util.dart' as windows_util;
void main() async {
await init();
@@ -25,15 +26,21 @@ Future<void> init() async {
WidgetsFlutterBinding.ensureInitialized();
if (Util.isDesktop()) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(800, 600),
final windowState = await windows_util.loadState();
final windowOptions = WindowOptions(
size: Size(windowState.width, windowState.height),
center: true,
skipTaskbar: false,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
await windowManager.setPreventClose(true);
// windows_manager has a bug where when window to be maximized, it will be unmaximized immediately, so can't implement this feature currently.
// https://github.com/leanflutter/window_manager/issues/412
// if (windowState.isMaximized) {
// await windowManager.maximize();
// }
});
}
+8
View File
@@ -143,4 +143,12 @@ class Util {
}
return completer.future;
}
static void Function() debounce(Function() fn, int ms) {
Timer? timer;
return () {
timer?.cancel();
timer = Timer(Duration(milliseconds: ms), fn);
};
}
}
+39
View File
@@ -0,0 +1,39 @@
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
class WindowState {
late bool isMaximized;
late double width;
late double height;
WindowState(this.isMaximized, this.width, this.height);
}
const String _windowStateIsMaximized = "window.isMaximized";
const String _windowStateWidth = "window.width";
const String _windowStateHeight = "window.height";
/// Save window state to storage.
void saveState({bool? isMaximized, double? width, double? height}) async {
final prefs = await SharedPreferences.getInstance();
if (isMaximized != null) {
await prefs.setBool(_windowStateIsMaximized, isMaximized);
}
if (width != null) {
await prefs.setDouble(_windowStateWidth, width);
}
if (height != null) {
await prefs.setDouble(_windowStateHeight, height);
}
}
/// Load window state from storage.
Future<WindowState> loadState() async {
final prefs = await SharedPreferences.getInstance();
return WindowState(
prefs.getBool(_windowStateIsMaximized) ?? false,
prefs.getDouble(_windowStateWidth) ?? 800,
prefs.getDouble(_windowStateHeight) ?? 600,
);
}
@@ -11,4 +11,4 @@ Exec=gopeed
Icon=com.gopeed.Gopeed
Type=Application
Categories=Utility;Internet
Keywords=Bittorrent;Downloader;
Keywords=Bittorrent;Downloader;
@@ -0,0 +1,17 @@
display_name: Gopeed
icon: assets/icon/icon.svg
keywords:
- Application
- DownloadManager
- Network
- Utility
generic_name: Download Manager
categories:
- Network
- Utility
startup_notify: true
@@ -0,0 +1,31 @@
display_name: Gopeed
package_name: gopeed
maintainer:
name: monkeyWie
email: [email protected]
co_authors:
- name: madoka773
email: [email protected]
priority: optional
section: x11
installed_size: 52360
essential: false
icon: assets/icon/icon.svg
dependencies:
- libappindicator3-1 | libayatana-appindicator3-1
- gir1.2-appindicator3-0.1 | gir1.2-ayatanaappindicator3-0.1
keywords:
- Application
- DownloadManager
- Network
- Utility
generic_name: Download Manager
categories:
- Network
- Utility
startup_notify: true
@@ -0,0 +1,24 @@
display_name: Gopeed
icon: assets/icon/icon.svg
summary: A modern download manager that supports all platforms. Built with Golang and Flutter.
group: Applications/Internet
vendor: monkeyWie
packager: madoka773
packagerEmail: [email protected]
license: GPL-3.0-or-later
url: https://github.com/GopeedLab/gopeed
description: A modern download manager that supports all platforms. Built with Golang and Flutter.
keywords:
- Application
- DownloadManager
- Network
- Utility
generic_name: Download Manager
categories:
- Network
- Utility
startup_notify: true
+40 -25
View File
@@ -181,10 +181,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.17.1"
version: "1.18.0"
context_menus:
dependency: "direct main"
description:
@@ -441,10 +441,10 @@ packages:
dependency: transitive
description:
name: intl
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.18.0"
version: "0.18.1"
io:
dependency: transitive
description:
@@ -477,6 +477,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.7.1"
lecle_downloads_path_provider:
dependency: "direct main"
description:
name: lecle_downloads_path_provider
sha256: "111f6d6d21e18acddda515eba8fd445658aa14defcbd59513d6d1b02b9ed47a0"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.2+8"
lint:
dependency: transitive
description:
@@ -537,18 +545,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
version: "0.5.0"
math_expressions:
dependency: transitive
description:
@@ -569,10 +577,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.1"
version: "1.10.0"
mime:
dependency: transitive
description:
@@ -894,10 +902,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.1"
version: "1.10.0"
sprintf:
dependency: transitive
description:
@@ -910,18 +918,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@@ -958,10 +966,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@@ -973,12 +981,11 @@ packages:
tray_manager:
dependency: "direct main"
description:
path: "."
ref: "fix/libayatana-set-icon-deprecation"
resolved-ref: "056da3fef20e75877e8d2a4ae73d93f51a0f77cc"
url: "https://github.com/provokateurin/tray_manager.git"
source: git
version: "0.2.0"
name: tray_manager
sha256: "4ab709d70a4374af172f8c39e018db33a4271265549c6fc9d269a65e5f4b0225"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1"
typed_data:
dependency: transitive
description:
@@ -1083,6 +1090,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.0"
web_socket_channel:
dependency: transitive
description:
@@ -1140,5 +1155,5 @@ packages:
source: hosted
version: "2.1.1"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.10.0"
+2 -4
View File
@@ -56,16 +56,14 @@ dependencies:
app_links: ^3.4.2
uri_to_file: ^0.2.0
window_manager: ^0.3.4
tray_manager:
git:
url: https://github.com/provokateurin/tray_manager.git
ref: fix/libayatana-set-icon-deprecation
autoscale_tabbarview: ^1.0.2
share_plus: ^7.1.0
flutter_form_builder: ^9.1.1
form_builder_validators: ^9.0.0
flutter_foreground_task: ^6.1.2
open_filex: ^4.3.4
tray_manager: ^0.2.1
lecle_downloads_path_provider: ^0.0.2+8
dev_dependencies:
flutter_test:
sdk: flutter