Compare commits

...
9 Commits
Author SHA1 Message Date
liwei 4840c4b8a4 ci: fix build env 2023-11-09 18:49:35 +08:00
Levi e72b097aa2 release: v1.4.4 2023-11-09 17:09:15 +08:00
Levi af52c4ce7e feat: support create task directly (#262) 2023-11-09 16:52:02 +08:00
Levi aae79c2449 feat: running foreground service on mobile (#261) 2023-11-07 17:52:10 +08:00
Levi 83838afce9 feat: support more linux distributions (#254) 2023-11-01 14:54:24 +08:00
Levi 4690107e32 fix: gopeed cli BT download (#258) 2023-10-31 10:16:28 +08:00
liwei 74b08b3631 chore: build locales.g.dart 2023-10-31 09:24:55 +08:00
Kiber_kotan 953d348c0c feat: improve Russian translation (#257) 2023-10-30 11:27:43 +08:00
Levi 00cea68b6e feat: add extension intro links (#252) 2023-10-26 18:35:22 +08:00
25 changed files with 741 additions and 384 deletions

No files matched your search

+5 -1
View File
@@ -32,6 +32,10 @@ version-resolver:
- 'patch' - 'patch'
default: patch default: patch
template: | template: |
## Changes # Changes
$CHANGES
# 更新日志
$CHANGES $CHANGES
+160 -5
View File
@@ -2,6 +2,11 @@ name: build
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
platform:
description: 'Build platform'
required: true
default: 'all'
env: env:
GO_VERSION: "1.19" GO_VERSION: "1.19"
@@ -24,6 +29,7 @@ jobs:
myToken: ${{ github.token }} myToken: ${{ github.token }}
build-windows: build-windows:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
runs-on: windows-latest runs-on: windows-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
@@ -124,6 +130,7 @@ jobs:
asset_path: ui/flutter/build/windows/Output/* asset_path: ui/flutter/build/windows/Output/*
overwrite: true overwrite: true
build-macos-arm64-lib: build-macos-arm64-lib:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
@@ -134,13 +141,14 @@ jobs:
- name: Build - name: Build
run: | run: |
go install src.techknowlogick.com/xgo@latest go install src.techknowlogick.com/xgo@latest
xgo -go go-1.19.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed . xgo -go go-$GO_VERSION.x --targets=darwin/arm64 -tags="nosqlite" -ldflags="-w -s" -buildmode=c-shared -pkg=bind/desktop -out=libgopeed .
mv libgopeed-*.dylib libgopeed.dylib mv libgopeed-*.dylib libgopeed.dylib
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: macos-arm64-lib name: macos-arm64-lib
path: libgopeed.dylib path: libgopeed.dylib
build-macos: build-macos:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: macos-latest runs-on: macos-latest
needs: [ get-release, build-macos-arm64-lib ] needs: [ get-release, build-macos-arm64-lib ]
steps: steps:
@@ -155,7 +163,9 @@ jobs:
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install appdmg - name: Install appdmg
run: npm install -g appdmg run: |
python3 -m pip install setuptools
npm install -g appdmg
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
name: macos-arm64-lib name: macos-arm64-lib
@@ -197,6 +207,7 @@ jobs:
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/* asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
overwrite: true overwrite: true
build-linux: build-linux:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
@@ -210,6 +221,10 @@ jobs:
- run: | - run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev
- name: Setup LXD
uses: canonical/[email protected]
with:
channel: latest/stable
- name: Build - name: Build
env: env:
VERSION: ${{ needs.get-release.outputs.tag_name }} VERSION: ${{ needs.get-release.outputs.tag_name }}
@@ -217,13 +232,20 @@ jobs:
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
cd ui/flutter cd ui/flutter
flutter build linux flutter build linux
mkdir -p build/dist
# Build portable
tar -czaf build/dist/Gopeed-$VERSION-linux-amd64-portable.tar.gz build/linux/x64/release/bundle
# Build deb package
mkdir -p debian/gui mkdir -p debian/gui
cp assets/icon/icon_1024.png debian/gui/gopeed.png cp assets/icon/icon_1024.png debian/gui/gopeed.png
cat>debian/debian.yaml<<EOF cat>debian/debian.yaml<<EOF
flutter_app: flutter_app:
command: gopeed command: gopeed
arch: x64 arch: x64
parent: /usr/local/lib parent: /usr/local/bin
control: control:
Package: gopeed Package: gopeed
Version: ${VERSION:1} Version: ${VERSION:1}
@@ -241,6 +263,8 @@ jobs:
Name=Gopeed Name=Gopeed
GenericName=Gopeed GenericName=Gopeed
Comment=High speed downloader that supports all platforms. Comment=High speed downloader that supports all platforms.
Exec=gopeed
Icon=gopeed.png
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Utility Categories=Utility
@@ -249,14 +273,142 @@ jobs:
dart pub global activate flutter_to_debian dart pub global activate flutter_to_debian
flutter_to_debian flutter_to_debian
mv debian/packages/gopeed*.deb debian/packages/Gopeed-$VERSION-linux-amd64.deb mv debian/packages/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
cp assets/icon/icon_1024.png AppDir/usr/share/icons/gopeed.png
cat>AppImageBuilder.yml<<EOF
version: 1
AppDir:
path: ${PWD}/AppDir
app_info:
id: com.gopeed.gopeed
name: Gopeed
icon: gopeed.png
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
# Build snap
sudo snap install snapcraft --classic
rm -rf build/linux
mkdir -p snap/gui
cp assets/icon/icon_1024.png snap/gui/gopeed.png
cat>snap/snapcraft.yaml<<EOF
name: gopeed
version: ${VERSION:1}
summary: A modern download manager
description: High speed downloader that supports all platforms.
confinement: strict
base: core22
grade: stable
architectures:
- build-on: amd64
build-for: amd64
slots:
dbus-gopeed:
interface: dbus
bus: session
name: com.gopeed.gopeed
apps:
gopeed:
command: gopeed
extensions: [gnome]
plugs:
- network
slots:
- dbus-gopeed
parts:
gopeed:
source: .
plugin: flutter
stage-packages:
- libayatana-appindicator3-dev
flutter-target: lib/main.dart
EOF
cat>snap/gui/gopeed.desktop<<EOF
[Desktop Entry]
Version=${VERSION:1}
Name=Gopeed
GenericName=Gopeed
Comment=High speed downloader that supports all platforms.
Exec=gopeed
Icon=\${SNAP}/meta/gui/gopeed.png
Terminal=false
Type=Application
Categories=Utility
Keywords=Flutter;Downloader;
EOF
snapcraft --use-lxd
# Snapcraft login
export SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.SNAP_STORE_LOGIN }}
snapcraft upload --release=stable gopeed_${VERSION:1}_amd64.snap
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
upload_url: ${{ needs.get-release.outputs.upload_url }} upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ui/flutter/debian/packages/* asset_path: ui/flutter/build/dist/*
overwrite: true overwrite: true
build-android: build-android:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
@@ -294,6 +446,7 @@ jobs:
asset_path: ui/flutter/dist/* asset_path: ui/flutter/dist/*
overwrite: true overwrite: true
build-ios: build-ios:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
runs-on: macos-latest runs-on: macos-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
@@ -326,6 +479,7 @@ jobs:
asset_path: ui/flutter/dist/* asset_path: ui/flutter/dist/*
overwrite: true overwrite: true
build-web: build-web:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
@@ -373,6 +527,7 @@ jobs:
asset_path: dist/zip/* asset_path: dist/zip/*
overwrite: true overwrite: true
build-docker: build-docker:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ get-release ] needs: [ get-release ]
steps: steps:
+14 -1
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/GopeedLab/gopeed/internal/fetcher"
"github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/download" "github.com/GopeedLab/gopeed/pkg/download"
"github.com/GopeedLab/gopeed/pkg/protocol/http" "github.com/GopeedLab/gopeed/pkg/protocol/http"
@@ -47,16 +48,28 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
printProgress(emptyTask, "downloading...")
wg.Wait() wg.Wait()
} }
var ( var (
lastLineLen = 0 lastLineLen = 0
sb = new(strings.Builder) sb = new(strings.Builder)
emptyTask = &download.Task{
Progress: &download.Progress{},
Meta: &fetcher.FetcherMeta{
Res: &base.Resource{},
},
}
) )
func printProgress(task *download.Task, title string) { func printProgress(task *download.Task, title string) {
rate := float64(task.Progress.Downloaded) / float64(task.Meta.Res.Size) var rate float64
if task.Meta.Res.Size <= 0 {
rate = 0
} else {
rate = float64(task.Progress.Downloaded) / float64(task.Meta.Res.Size)
}
completeWidth := int(progressWidth * rate) completeWidth := int(progressWidth * rate)
speed := util.ByteFmt(task.Progress.Speed) speed := util.ByteFmt(task.Progress.Speed)
totalSize := util.ByteFmt(task.Meta.Res.Size) totalSize := util.ByteFmt(task.Meta.Res.Size)
+11 -4
View File
@@ -87,7 +87,6 @@ func (f *Fetcher) Create(opts *base.Options) (err error) {
if f.meta.Res != nil { if f.meta.Res != nil {
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath() torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
} }
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
return nil return nil
} }
@@ -101,6 +100,14 @@ func (f *Fetcher) Start() (err error) {
ft.setTorrentDir(f.meta.FolderPath()) ft.setTorrentDir(f.meta.FolderPath())
} }
files := f.torrent.Files() files := f.torrent.Files()
// If the user does not specify the file to download, all files will be downloaded by default
if len(f.meta.Opts.SelectFiles) == 0 {
f.meta.Opts.SelectFiles = make([]int, len(files))
for i := range files {
f.meta.Opts.SelectFiles[i] = i
}
}
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
if len(f.meta.Opts.SelectFiles) == len(files) { if len(f.meta.Opts.SelectFiles) == len(files) {
f.torrent.DownloadAll() f.torrent.DownloadAll()
} else { } else {
@@ -134,7 +141,7 @@ func (f *Fetcher) safeDrop() {
func (f *Fetcher) Wait() (err error) { func (f *Fetcher) Wait() (err error) {
for { for {
if f.torrentReady.Load() { if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
done := true done := true
for _, selectIndex := range f.meta.Opts.SelectFiles { for _, selectIndex := range f.meta.Opts.SelectFiles {
file := f.torrent.Files()[selectIndex] file := f.torrent.Files()[selectIndex]
@@ -154,7 +161,7 @@ func (f *Fetcher) Wait() (err error) {
} }
} }
if !selected { if !selected {
util.SafeRemove(filepath.Join(f.meta.Opts.Path, file.Path())) util.SafeRemove(filepath.Join(f.meta.Opts.Path, f.meta.Res.Name, file.Path()))
} }
} }
break break
@@ -195,7 +202,7 @@ func (f *Fetcher) updateRes() {
Size: file.Length(), Size: file.Length(),
} }
} }
res.CalcSize() res.CalcSize(nil)
f.meta.Res = res f.meta.Res = res
if f.meta.Opts != nil { if f.meta.Opts != nil {
f.meta.Opts.InitSelectFiles(len(res.Files)) f.meta.Opts.InitSelectFiles(len(res.Files))
+5 -2
View File
@@ -3,6 +3,7 @@ package base
import ( import (
"fmt" "fmt"
"github.com/GopeedLab/gopeed/pkg/util" "github.com/GopeedLab/gopeed/pkg/util"
"golang.org/x/exp/slices"
) )
// Request download request // Request download request
@@ -47,11 +48,13 @@ func (r *Resource) Validate() error {
return nil return nil
} }
func (r *Resource) CalcSize() { func (r *Resource) CalcSize(selectFiles []int) {
var size int64 var size int64
for _, file := range r.Files { for i, file := range r.Files {
if len(selectFiles) == 0 || slices.Contains(selectFiles, i) {
size += file.Size size += file.Size
} }
}
r.Size = size r.Size = size
} }
+19 -12
View File
@@ -540,11 +540,17 @@ func (d *Downloader) GetTask(id string) *Task {
} }
func (d *Downloader) GetTasks() []*Task { func (d *Downloader) GetTasks() []*Task {
// reverse tasks, so that the latest task is in the front
tasks := make([]*Task, len(d.tasks)) tasks := make([]*Task, len(d.tasks))
for i := 0; i < len(d.tasks); i++ { for i := 0; i < len(d.tasks); i++ {
tasks[i] = d.tasks[len(d.tasks)-i-1] tasks[i] = d.tasks[i]
} }
// sort tasks by status, order by Status(if(running,1,0)) desc, CreatedAt desc
sort.Slice(tasks, func(i, j int) bool {
if tasks[i].Status != tasks[j].Status {
return tasks[i].Status == base.DownloadStatusRunning
}
return tasks[i].CreatedAt.After(tasks[j].CreatedAt)
})
return tasks return tasks
} }
@@ -579,16 +585,16 @@ func (d *Downloader) watch(task *Task) {
d.emit(EventKeyError, task, err) d.emit(EventKeyError, task, err)
} else { } else {
task.Progress.Used = task.timer.Used() task.Progress.Used = task.timer.Used()
if task.Size == 0 { if task.Meta.Res.Size == 0 {
task.Size = task.fetcher.Progress().TotalDownloaded() task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
task.Meta.Res.Size = task.Size
} }
used := task.Progress.Used / int64(time.Second) used := task.Progress.Used / int64(time.Second)
if used == 0 { if used == 0 {
used = 1 used = 1
} }
task.Progress.Speed = task.Size / used totalSize := task.Meta.Res.Size
task.Progress.Downloaded = task.Size task.Progress.Speed = totalSize / used
task.Progress.Downloaded = totalSize
task.Status = base.DownloadStatusDone task.Status = base.DownloadStatusDone
d.storage.Put(bucketTask, task.ID, task.clone()) d.storage.Put(bucketTask, task.ID, task.clone())
d.emit(EventKeyDone, task) d.emit(EventKeyDone, task)
@@ -741,6 +747,8 @@ func (d *Downloader) doStart(task *Task) (err error) {
} }
task.Meta.Opts.Name = newName task.Meta.Opts.Name = newName
} }
task.Meta.Res.CalcSize(task.Meta.Opts.SelectFiles)
} }
task.Progress.Speed = 0 task.Progress.Speed = 0
@@ -848,12 +856,11 @@ func (b *boot) Listener(listener Listener) *boot {
} }
func (b *boot) Create(opts *base.Options) (string, error) { func (b *boot) Create(opts *base.Options) (string, error) {
rr, err := b.Resolve()
if err != nil {
return "", err
}
defaultDownloader.Listener(b.listener) defaultDownloader.Listener(b.listener)
return defaultDownloader.Create(rr.ID, opts) return defaultDownloader.CreateDirect(&base.Request{
URL: b.url,
Extra: b.extra,
}, opts)
} }
func Boot() *boot { func Boot() *boot {
+1 -1
View File
@@ -243,7 +243,7 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity()) gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
return return
} }
ctx.Res.CalcSize() ctx.Res.CalcSize(nil)
} }
res = ctx.Res res = ctx.Res
}, },
-2
View File
@@ -22,7 +22,6 @@ type Task struct {
Meta *fetcher.FetcherMeta `json:"meta"` Meta *fetcher.FetcherMeta `json:"meta"`
Status base.Status `json:"status"` Status base.Status `json:"status"`
Progress *Progress `json:"progress"` Progress *Progress `json:"progress"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
fetcherBuilder fetcher.FetcherBuilder fetcherBuilder fetcher.FetcherBuilder
@@ -50,7 +49,6 @@ func (t *Task) clone() *Task {
Meta: t.Meta, Meta: t.Meta,
Status: t.Status, Status: t.Status,
Progress: t.Progress, Progress: t.Progress,
Size: t.Size,
CreatedAt: t.CreatedAt, CreatedAt: t.CreatedAt,
} }
} }
+1 -1
View File
@@ -47,7 +47,7 @@ android {
applicationId "com.gopeed" applicationId "com.gopeed"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 19 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@@ -72,6 +72,12 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Add android:stopWithTask option only when necessary. -->
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:stopWithTask="true" />
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data
+8 -4
View File
@@ -5,13 +5,14 @@
"confirm": "Confirm", "confirm": "Confirm",
"cancel": "Cancel", "cancel": "Cancel",
"selectAll": "Select All", "selectAll": "Select All",
"task": "Task", "task": "Tasks",
"downloading": "downloading", "downloading": "downloading",
"downloaded": "downloaded", "downloaded": "downloaded",
"setting": "Setting", "setting": "Settings",
"donate": "Donate", "donate": "Donate",
"exit": "Exit", "exit": "Exit",
"create": "Create Task", "create": "Create Task",
"directDownload": "Direct Download",
"advancedOptions": "Advanced Options", "advancedOptions": "Advanced Options",
"downloadLink": "Download Link", "downloadLink": "Download Link",
"downloadLinkValid": "Please enter the download link", "downloadLinkValid": "Please enter the download link",
@@ -66,8 +67,11 @@
"extensionUpdateSuccess": "Updated successfully", "extensionUpdateSuccess": "Updated successfully",
"extensionDelete": "Delete Extension", "extensionDelete": "Delete Extension",
"extensionAlreadyLatest": "It's already the latest version", "extensionAlreadyLatest": "It's already the latest version",
"extensionFind": "Find Extensions",
"extensionDevelop": "Develop Extensions",
"history": "History", "history": "History",
"clearHistory": "Clear History", "clearHistory": "Clear History",
"noHistoryFound": "No History Found" "noHistoryFound": "No History Found",
"serviceTitle": "Download Service",
"serviceText": "Running"
} }
+16 -4
View File
@@ -12,14 +12,16 @@
"donate": "Пожертвовать", "donate": "Пожертвовать",
"exit": "Выход", "exit": "Выход",
"create": "Создать задачу", "create": "Создать задачу",
"downloadLink": "Ссылка для скачивание", "advancedOptions": "Расширенные опции",
"downloadLink": "Ссылка на скачивание",
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания", "downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
"downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append", "downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append",
"downloadLinkHitDesktop": ", или перетащите сюда торрент-файл", "downloadLinkHitDesktop": ", или перетащите сюда торрент-файл",
"download": "Скачать", "download": "Скачать",
"noFileSelected": "Файл не выбран", "noFileSelected": "Файл не выбран",
"noStoragePermission": "Требуется доступ к хранилищу", "noStoragePermission": "Требуется доступ к хранилищу",
"selectFile": "Выберать файл", "selectFile": "Выбрать файл",
"rename": "Переименовать",
"basic": "Основные параметры", "basic": "Основные параметры",
"advanced": "Расширенные", "advanced": "Расширенные",
"general": "Общие", "general": "Общие",
@@ -57,5 +59,15 @@
"delete": "Удалить", "delete": "Удалить",
"newVersionTitle": "Обнаружена новая версия @version", "newVersionTitle": "Обнаружена новая версия @version",
"newVersionUpdate": "Обновить", "newVersionUpdate": "Обновить",
"newVersionLater": "позже" "newVersionLater": "позже",
"extensions": "Расширения",
"extensionInstallSuccess": "Установка завершена",
"extensionUpdateSuccess": "Обновление завершено",
"extensionDelete": "Удалить расширение",
"extensionAlreadyLatest": "Это последняя версия",
"extensionFind": "Найти расширения",
"extensionDevelop": "Разработка расширений",
"history": "История",
"clearHistory": "Очистить историю",
"noHistoryFound": "История не найдена"
} }
+6 -1
View File
@@ -12,6 +12,7 @@
"donate": "打赏", "donate": "打赏",
"exit": "退出", "exit": "退出",
"create": "创建任务", "create": "创建任务",
"directDownload": "直接下载",
"advancedOptions": "高级选项", "advancedOptions": "高级选项",
"downloadLink": "下载链接", "downloadLink": "下载链接",
"downloadLinkValid": "请输入下载链接", "downloadLinkValid": "请输入下载链接",
@@ -66,7 +67,11 @@
"extensionUpdateSuccess": "更新成功", "extensionUpdateSuccess": "更新成功",
"extensionDelete": "删除扩展", "extensionDelete": "删除扩展",
"extensionAlreadyLatest": "已经是最新版本", "extensionAlreadyLatest": "已经是最新版本",
"extensionFind": "获取扩展",
"extensionDevelop": "开发扩展",
"history": "历史记录", "history": "历史记录",
"clearHistory": "清空历史记录", "clearHistory": "清空历史记录",
"noHistoryFound": "暂无历史记录" "noHistoryFound": "暂无历史记录",
"serviceTitle": "下载服务",
"serviceText": "运行中"
} }
+9
View File
@@ -33,6 +33,15 @@ import Libgopeed
}) })
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
} }
func registerPlugins(registry: FlutterPluginRegistry) {
GeneratedPluginRegistrant.register(with: registry)
}
@@ -1 +1,2 @@
#import "GeneratedPluginRegistrant.h" #import "GeneratedPluginRegistrant.h"
#import <flutter_foreground_task/FlutterForegroundTaskPlugin.h>
-2
View File
@@ -12,7 +12,6 @@ class Task {
Meta meta; Meta meta;
Status status; Status status;
Progress progress; Progress progress;
int size;
DateTime createdAt; DateTime createdAt;
Task({ Task({
@@ -20,7 +19,6 @@ class Task {
required this.meta, required this.meta,
required this.status, required this.status,
required this.progress, required this.progress,
required this.size,
required this.createdAt, required this.createdAt,
}); });
-2
View File
@@ -11,7 +11,6 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>), meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
status: $enumDecode(_$StatusEnumMap, json['status']), status: $enumDecode(_$StatusEnumMap, json['status']),
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>), progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
size: json['size'] as int,
createdAt: DateTime.parse(json['createdAt'] as String), createdAt: DateTime.parse(json['createdAt'] as String),
); );
@@ -20,7 +19,6 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
'meta': instance.meta.toJson(), 'meta': instance.meta.toJson(),
'status': _$StatusEnumMap[instance.status]!, 'status': _$StatusEnumMap[instance.status]!,
'progress': instance.progress.toJson(), 'progress': instance.progress.toJson(),
'size': instance.size,
'createdAt': instance.createdAt.toIso8601String(), 'createdAt': instance.createdAt.toIso8601String(),
}; };
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:app_links/app_links.dart'; import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.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';
@@ -79,21 +80,18 @@ class AppController extends GetxController with WindowListener, TrayListener {
@override @override
void onReady() { void onReady() {
super.onReady(); super.onReady();
try {
_initDeepLinks(); _initDeepLinks().onError((error, stackTrace) =>
} catch (e) { logger.w("initDeepLinks error", error, stackTrace));
logger.w("initDeepLinks error", e);
} _initWindows().onError((error, stackTrace) =>
try { logger.w("initWindows error", error, stackTrace));
_initWindows();
} catch (e) { _initTray().onError(
logger.w("initWindows error", e); (error, stackTrace) => logger.w("initTray error", error, stackTrace));
}
try { _initForegroundTask().onError((error, stackTrace) =>
_initTray(); logger.w("initForegroundTask error", error, stackTrace));
} catch (e) {
logger.w("initTray error", e);
}
} }
@override @override
@@ -210,6 +208,42 @@ class AppController extends GetxController with WindowListener, TrayListener {
trayManager.addListener(this); trayManager.addListener(this);
} }
Future<void> _initForegroundTask() async {
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'gopeed_service',
channelName: 'Gopeed Background Service',
channelImportance: NotificationChannelImportance.LOW,
showWhen: true,
priority: NotificationPriority.LOW,
iconData: const NotificationIconData(
resType: ResourceType.mipmap,
resPrefix: ResourcePrefix.ic,
name: 'launcher',
)),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: true,
playSound: false,
),
foregroundTaskOptions: const ForegroundTaskOptions(
interval: 5000,
isOnceEvent: false,
autoRunOnBoot: true,
allowWakeLock: true,
allowWifiLock: true,
),
);
if (await FlutterForegroundTask.isRunningService) {
FlutterForegroundTask.restartService();
} else {
FlutterForegroundTask.startService(
notificationTitle: "serviceTitle".tr,
notificationText: "serviceText".tr,
);
}
}
Future<void> _toCreate(Uri uri) async { Future<void> _toCreate(Uri uri) async {
final path = uri.scheme == "magnet" final path = uri.scheme == "magnet"
? uri.toString() ? uri.toString()
@@ -10,8 +10,9 @@ class CreateController extends GetxController
final RxList fileInfos = [].obs; final RxList fileInfos = [].obs;
final RxList openedFolders = [].obs; final RxList openedFolders = [].obs;
final selectedIndexes = <int>[].obs; final selectedIndexes = <int>[].obs;
final isResolving = false.obs; final isConfirming = false.obs;
final showAdvanced = false.obs; final showAdvanced = false.obs;
final directDownload = false.obs;
late TabController advancedTabController; late TabController advancedTabController;
final oldUrl = "".obs; final oldUrl = "".obs;
final fileDataUri = "".obs; final fileDataUri = "".obs;
@@ -24,9 +24,12 @@ import '../../app/controllers/app_controller.dart';
import '../controllers/create_controller.dart'; import '../controllers/create_controller.dart';
class CreateView extends GetView<CreateController> { class CreateView extends GetView<CreateController> {
final _resolveFormKey = GlobalKey<FormState>(); final _confirmFormKey = GlobalKey<FormState>();
final _urlController = TextEditingController(); final _urlController = TextEditingController();
final _renameController = TextEditingController();
final _connectionsController = TextEditingController();
final _pathController = TextEditingController();
final _confirmController = RoundedLoadingButtonController(); final _confirmController = RoundedLoadingButtonController();
final _httpUaController = TextEditingController(); final _httpUaController = TextEditingController();
final _httpCookieController = TextEditingController(); final _httpCookieController = TextEditingController();
@@ -40,6 +43,17 @@ class CreateView extends GetView<CreateController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appController = Get.find<AppController>();
if (_connectionsController.text.isEmpty) {
_connectionsController.text = appController
.downloaderConfig.value.protocolConfig.http.connections
.toString();
}
if (_pathController.text.isEmpty) {
_pathController.text = appController.downloaderConfig.value.downloadDir;
}
final String? filePath = Get.rootDelegate.arguments(); final String? filePath = Get.rootDelegate.arguments();
if (_urlController.text.isEmpty) { if (_urlController.text.isEmpty) {
if (filePath?.isNotEmpty ?? false) { if (filePath?.isNotEmpty ?? false) {
@@ -91,11 +105,12 @@ class CreateView extends GetView<CreateController> {
onTap: () { onTap: () {
FocusScope.of(context).requestFocus(FocusNode()); FocusScope.of(context).requestFocus(FocusNode());
}, },
child: SingleChildScrollView(
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Form( child: Form(
key: _resolveFormKey, key: _confirmFormKey,
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column( child: Column(
children: [ children: [
@@ -170,12 +185,14 @@ class CreateView extends GetView<CreateController> {
isHistoryListEmpty: isHistoryListEmpty:
reverseResultOfHistories.isEmpty, reverseResultOfHistories.isEmpty,
historyList: ListView.builder( historyList: ListView.builder(
itemCount: reverseResultOfHistories.length, itemCount:
reverseResultOfHistories.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
_urlController.text = _urlController.text =
reverseResultOfHistories[index]; reverseResultOfHistories[
index];
Navigator.pop(context); Navigator.pop(context);
}, },
child: MouseRegion( child: MouseRegion(
@@ -196,10 +213,12 @@ class CreateView extends GetView<CreateController> {
.colorScheme .colorScheme
.background, .background,
borderRadius: borderRadius:
BorderRadius.circular(10.0), BorderRadius.circular(
10.0),
), ),
child: Text( child: Text(
reverseResultOfHistories[index] reverseResultOfHistories[
index]
.toString(), .toString(),
), ),
), ),
@@ -223,14 +242,31 @@ class CreateView extends GetView<CreateController> {
} }
}, },
), ),
] ]),
//.where((e) => e != null).map((e) => e!).toList(), Padding(
padding: const EdgeInsets.only(left: 40),
child: Column(children: [
TextField(
controller: _renameController,
decoration: InputDecoration(labelText: 'rename'.tr),
),
TextField(
controller: _connectionsController,
decoration: InputDecoration(
labelText: 'connections'.tr,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
NumericalRangeFormatter(min: 1, max: 256),
],
),
DirectorySelector(
controller: _pathController,
), ),
Obx( Obx(
() => Visibility( () => Visibility(
visible: controller.showAdvanced.value, visible: controller.showAdvanced.value,
child: Padding(
padding: const EdgeInsets.only(left: 40, top: 15),
child: Column( child: Column(
children: [ children: [
TabBar( TabBar(
@@ -280,25 +316,42 @@ class CreateView extends GetView<CreateController> {
], ],
) )
], ],
).paddingOnly(top: 16),
), ),
), ),
), ]),
), ),
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: Column( child: Column(
children: [ children: [
Padding( Row(
padding: const EdgeInsets.only(right: 10), mainAxisSize: MainAxisSize.min,
child: TextButton( children: [
TextButton(
onPressed: () {
controller.directDownload.value =
!controller.directDownload.value;
},
child: Row(children: [
Obx(() => Checkbox(
value:
controller.directDownload.value,
onChanged: (bool? value) {
controller.directDownload.value =
value ?? false;
},
)),
Text('directDownload'.tr),
]),
),
TextButton(
onPressed: () { onPressed: () {
controller.showAdvanced.value = controller.showAdvanced.value =
!controller.showAdvanced.value; !controller.showAdvanced.value;
}, },
child: Row( child: Row(children: [
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Checkbox( Obx(() => Checkbox(
value: controller.showAdvanced.value, value: controller.showAdvanced.value,
onChanged: (bool? value) { onChanged: (bool? value) {
@@ -309,12 +362,13 @@ class CreateView extends GetView<CreateController> {
Text('advancedOptions'.tr), Text('advancedOptions'.tr),
]), ]),
), ),
],
), ),
SizedBox( SizedBox(
width: 150, width: 150,
child: RoundedLoadingButton( child: RoundedLoadingButton(
color: Get.theme.colorScheme.secondary, color: Get.theme.colorScheme.secondary,
onPressed: _doResolve, onPressed: _doConfirm,
controller: _confirmController, controller: _confirmController,
child: Text('confirm'.tr), child: Text('confirm'.tr),
), ),
@@ -329,6 +383,7 @@ class CreateView extends GetView<CreateController> {
), ),
), ),
), ),
),
); );
} }
@@ -346,45 +401,54 @@ class CreateView extends GetView<CreateController> {
} }
} }
Future<void> _doResolve() async { Future<void> _doConfirm() async {
if (controller.isResolving.value) { if (controller.isConfirming.value) {
return; return;
} }
controller.isResolving.value = true; controller.isConfirming.value = true;
try { try {
_confirmController.start(); _confirmController.start();
if (_resolveFormKey.currentState!.validate()) { if (_confirmFormKey.currentState!.validate()) {
// check if is multi line urls
final urls = Util.textToLines(_urlController.text);
ResolveResult rr;
bool isMultiLine = false;
if (urls.length > 1) {
isMultiLine = true;
rr = ResolveResult(
res: Resource(
files: urls
.map((u) => FileInfo(
name: u,
req: Request(url: u, extra: parseReqExtra(u))))
.toList()));
} else {
final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty
? controller.fileDataUri.value ? controller.fileDataUri.value
: _urlController.text; : _urlController.text;
// add final submitUrl to the history
_historyController.addHistory(submitUrl); final urls = Util.textToLines(submitUrl);
rr = await resolve(Request( // Add url to the history
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
2. Muli line urls
*/
final isMultiLine = urls.length > 1;
final isDirect = controller.directDownload.value || isMultiLine;
if (isDirect) {
await Future.wait(urls.map((url) {
return createTask(CreateTask(
req: Request(url: url, extra: parseReqExtra(url)),
opt: Options(
name: isMultiLine ? "" : _renameController.text,
path: _pathController.text,
selectFiles: [],
extra: parseReqOpts())));
}));
Get.rootDelegate.offNamed(Routes.TASK);
} else {
final rr = await resolve(Request(
url: submitUrl, url: submitUrl,
extra: parseReqExtra(_urlController.text), extra: parseReqExtra(_urlController.text),
)); ));
await _showResolveDialog(rr);
} }
await _showResolveDialog(rr, isMultiLine);
} }
} catch (e) { } catch (e) {
showErrorMessage(e); showErrorMessage(e);
} finally { } finally {
_confirmController.reset(); _confirmController.reset();
controller.isResolving.value = false; controller.isConfirming.value = false;
} }
} }
@@ -407,6 +471,13 @@ class CreateView extends GetView<CreateController> {
return reqExtra; return reqExtra;
} }
Object? parseReqOpts() {
return _connectionsController.text.isEmpty
? null
: (OptsExtraHttp()
..connections = int.parse(_connectionsController.text));
}
String _hitText() { String _hitText() {
return 'downloadLinkHit'.trParams({ return 'downloadLinkHit'.trParams({
'append': 'append':
@@ -414,14 +485,8 @@ class CreateView extends GetView<CreateController> {
}); });
} }
Future<void> _showResolveDialog(ResolveResult rr, bool isMuiltiLine) async { Future<void> _showResolveDialog(ResolveResult rr) async {
final appController = Get.find<AppController>();
final createFormKey = GlobalKey<FormState>(); final createFormKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final connectionsController = TextEditingController();
final pathController = TextEditingController(
text: appController.downloaderConfig.value.downloadDir);
final downloadController = RoundedLoadingButtonController(); final downloadController = RoundedLoadingButtonController();
return showDialog<void>( return showDialog<void>(
context: Get.context!, context: Get.context!,
@@ -440,31 +505,7 @@ class CreateView extends GetView<CreateController> {
child: Form( child: Form(
key: createFormKey, key: createFormKey,
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
child: Column( child: FileListView(files: rr.res.files)),
children: [
Expanded(child: FileListView(files: rr.res.files)),
TextFormField(
controller: nameController,
decoration: InputDecoration(
labelText: 'rename'.tr,
),
),
TextFormField(
controller: connectionsController,
decoration: InputDecoration(
labelText: 'connections'.tr,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
NumericalRangeFormatter(min: 1, max: 256),
],
),
DirectorySelector(
controller: pathController,
),
],
)),
); );
}, },
), ),
@@ -500,25 +541,19 @@ class CreateView extends GetView<CreateController> {
showMessage('tip'.tr, 'noFileSelected'.tr); showMessage('tip'.tr, 'noFileSelected'.tr);
return; return;
} }
Object? optExtra = connectionsController.text.isEmpty final optExtra = parseReqOpts();
? null
: (OptsExtraHttp()
..connections =
int.parse(connectionsController.text));
if (createFormKey.currentState!.validate()) { if (createFormKey.currentState!.validate()) {
if (rr.id.isEmpty) { if (rr.id.isEmpty) {
// create task batch, there has two ways to batch create task // from extension resolve result
// 1. from multi line urls
// 2. from extension resolve result
await Future.wait( await Future.wait(
controller.selectedIndexes.map((index) { controller.selectedIndexes.map((index) {
final file = rr.res.files[index]; final file = rr.res.files[index];
return createTask(CreateTask( return createTask(CreateTask(
req: file.req!, req: file.req!,
opt: Options( opt: Options(
name: isMuiltiLine ? "" : file.name, name: file.name,
path: path.join( path: path.join(
pathController.text, rr.res.name), _pathController.text, rr.res.name),
selectFiles: [], selectFiles: [],
extra: optExtra))); extra: optExtra)));
})); }));
@@ -526,8 +561,8 @@ class CreateView extends GetView<CreateController> {
await createTask(CreateTask( await createTask(CreateTask(
rid: rr.id, rid: rr.id,
opt: Options( opt: Options(
name: nameController.text, name: _renameController.text,
path: pathController.text, path: _pathController.text,
selectFiles: controller.selectedIndexes, selectFiles: controller.selectedIndexes,
extra: optExtra))); extra: optExtra)));
} }
@@ -36,7 +36,31 @@ class ExtensionView extends GetView<ExtensionController> {
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 20), Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
onPressed: () {
launchUrl(
Uri.parse(
'https://github.com/search?q=topic%3Agopeed-extension&type=repositories'),
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.search),
label: Text('extensionFind'.tr),
),
const SizedBox(width: 16),
TextButton.icon(
onPressed: () {
launchUrl(
Uri.parse('https://docs.gopeed.com/dev-extension.html'),
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.edit),
label: Text('extensionDevelop'.tr),
),
],
),
Obx(() => Row( Obx(() => Row(
children: [ children: [
Expanded( Expanded(
@@ -90,7 +114,7 @@ class ExtensionView extends GetView<ExtensionController> {
: Container() : Container()
], ],
)), )),
const SizedBox(height: 32), const SizedBox(height: 16),
Expanded( Expanded(
child: Obx(() => ListView.builder( child: Obx(() => ListView.builder(
itemCount: controller.extensions.length, itemCount: controller.extensions.length,
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
@@ -23,7 +24,8 @@ class HomeView extends GetView<HomeController> {
break; break;
} }
return Scaffold( return WithForegroundTask(
child: Scaffold(
// extendBody: true, // extendBody: true,
body: Row( body: Row(
// crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
@@ -156,6 +158,7 @@ class HomeView extends GetView<HomeController> {
// }, // },
// ) // )
: const SizedBox.shrink(), : const SizedBox.shrink(),
),
); );
}); });
} }
+33 -6
View File
@@ -28,6 +28,7 @@ class LocaleKeys {
static const donate = 'donate'; static const donate = 'donate';
static const exit = 'exit'; static const exit = 'exit';
static const create = 'create'; static const create = 'create';
static const directDownload = 'directDownload';
static const advancedOptions = 'advancedOptions'; static const advancedOptions = 'advancedOptions';
static const downloadLink = 'downloadLink'; static const downloadLink = 'downloadLink';
static const downloadLinkValid = 'downloadLinkValid'; static const downloadLinkValid = 'downloadLinkValid';
@@ -82,9 +83,13 @@ class LocaleKeys {
static const extensionUpdateSuccess = 'extensionUpdateSuccess'; static const extensionUpdateSuccess = 'extensionUpdateSuccess';
static const extensionDelete = 'extensionDelete'; static const extensionDelete = 'extensionDelete';
static const extensionAlreadyLatest = 'extensionAlreadyLatest'; static const extensionAlreadyLatest = 'extensionAlreadyLatest';
static const extensionFind = 'extensionFind';
static const extensionDevelop = 'extensionDevelop';
static const history = 'history'; static const history = 'history';
static const clearHistory = 'clearHistory'; static const clearHistory = 'clearHistory';
static const noHistoryFound = 'noHistoryFound'; static const noHistoryFound = 'noHistoryFound';
static const serviceTitle = 'serviceTitle';
static const serviceText = 'serviceText';
} }
class Locales { class Locales {
@@ -95,13 +100,14 @@ class Locales {
'confirm': 'Confirm', 'confirm': 'Confirm',
'cancel': 'Cancel', 'cancel': 'Cancel',
'selectAll': 'Select All', 'selectAll': 'Select All',
'task': 'Task', 'task': 'Tasks',
'downloading': 'downloading', 'downloading': 'downloading',
'downloaded': 'downloaded', 'downloaded': 'downloaded',
'setting': 'Setting', 'setting': 'Settings',
'donate': 'Donate', 'donate': 'Donate',
'exit': 'Exit', 'exit': 'Exit',
'create': 'Create Task', 'create': 'Create Task',
'directDownload': 'Direct Download',
'advancedOptions': 'Advanced Options', 'advancedOptions': 'Advanced Options',
'downloadLink': 'Download Link', 'downloadLink': 'Download Link',
'downloadLinkValid': 'Please enter the download link', 'downloadLinkValid': 'Please enter the download link',
@@ -158,9 +164,13 @@ class Locales {
'extensionUpdateSuccess': 'Updated successfully', 'extensionUpdateSuccess': 'Updated successfully',
'extensionDelete': 'Delete Extension', 'extensionDelete': 'Delete Extension',
'extensionAlreadyLatest': 'It\'s already the latest version', 'extensionAlreadyLatest': 'It\'s already the latest version',
'extensionFind': 'Find Extensions',
'extensionDevelop': 'Develop Extensions',
'history': 'History', 'history': 'History',
'clearHistory': 'Clear History', 'clearHistory': 'Clear History',
'noHistoryFound': 'No History Found', 'noHistoryFound': 'No History Found',
'serviceTitle': 'Download Service',
'serviceText': 'Running',
}; };
static const fa_IR = { static const fa_IR = {
'label': 'فارسی', 'label': 'فارسی',
@@ -296,15 +306,17 @@ class Locales {
'donate': 'Пожертвовать', 'donate': 'Пожертвовать',
'exit': 'Выход', 'exit': 'Выход',
'create': 'Создать задачу', 'create': 'Создать задачу',
'downloadLink': 'Ссылка для скачивание', 'advancedOptions': 'Расширенные опции',
'downloadLink': 'Ссылка на скачивание',
'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания', 'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания',
'downloadLinkHit': 'downloadLinkHit':
'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append', 'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append',
'downloadLinkHitDesktop': ', или перетащите сюда торрент-файл', 'downloadLinkHitDesktop': ', или перетащите сюда торрент-файл',
'download': 'Скачать', 'download': 'Скачать',
'noFileSelected': 'Файл не выбран', 'noFileSelected': 'Файл не выбран',
'noStoragePermission': 'Требуется доступ к хранилищу', 'noStoragePermission': 'Требуется доступ к хранилищу',
'selectFile': 'Выберать файл', 'selectFile': 'Выбрать файл',
'rename': 'Переименовать',
'basic': 'Основные параметры', 'basic': 'Основные параметры',
'advanced': 'Расширенные', 'advanced': 'Расширенные',
'general': 'Общие', 'general': 'Общие',
@@ -344,6 +356,16 @@ class Locales {
'newVersionTitle': 'Обнаружена новая версия @version', 'newVersionTitle': 'Обнаружена новая версия @version',
'newVersionUpdate': 'Обновить', 'newVersionUpdate': 'Обновить',
'newVersionLater': 'позже', 'newVersionLater': 'позже',
'extensions': 'Расширения',
'extensionInstallSuccess': 'Установка завершена',
'extensionUpdateSuccess': 'Обновление завершено',
'extensionDelete': 'Удалить расширение',
'extensionAlreadyLatest': 'Это последняя версия',
'extensionFind': 'Найти расширения',
'extensionDevelop': 'Разработка расширений',
'history': 'История',
'clearHistory': 'Очистить историю',
'noHistoryFound': 'История не найдена',
}; };
static const zh_CN = { static const zh_CN = {
'label': '中文(简体)', 'label': '中文(简体)',
@@ -359,6 +381,7 @@ class Locales {
'donate': '打赏', 'donate': '打赏',
'exit': '退出', 'exit': '退出',
'create': '创建任务', 'create': '创建任务',
'directDownload': '直接下载',
'advancedOptions': '高级选项', 'advancedOptions': '高级选项',
'downloadLink': '下载链接', 'downloadLink': '下载链接',
'downloadLinkValid': '请输入下载链接', 'downloadLinkValid': '请输入下载链接',
@@ -413,9 +436,13 @@ class Locales {
'extensionUpdateSuccess': '更新成功', 'extensionUpdateSuccess': '更新成功',
'extensionDelete': '删除扩展', 'extensionDelete': '删除扩展',
'extensionAlreadyLatest': '已经是最新版本', 'extensionAlreadyLatest': '已经是最新版本',
'extensionFind': '获取扩展',
'extensionDevelop': '开发扩展',
'history': '历史记录', 'history': '历史记录',
'clearHistory': '清空历史记录', 'clearHistory': '清空历史记录',
'noHistoryFound': '没有历史记录', 'noHistoryFound': '暂无历史记录',
'serviceTitle': '下载服务',
'serviceText': '运行中',
}; };
static const zh_TW = { static const zh_TW = {
'label': '中文 (正體) ', 'label': '中文 (正體) ',
+239 -230
View File
File diff suppressed because it is too large. Load diff
+6 -2
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.4.3+1 version: 1.4.4+1
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@@ -56,12 +56,16 @@ dependencies:
app_links: ^3.4.2 app_links: ^3.4.2
uri_to_file: ^0.2.0 uri_to_file: ^0.2.0
window_manager: ^0.3.4 window_manager: ^0.3.4
tray_manager: ^0.2.0 tray_manager:
git:
url: https://github.com/provokateurin/tray_manager.git
ref: fix/libayatana-set-icon-deprecation
autoscale_tabbarview: ^1.0.2 autoscale_tabbarview: ^1.0.2
share_plus: ^7.1.0 share_plus: ^7.1.0
open_file: ^3.3.2 open_file: ^3.3.2
flutter_form_builder: ^9.1.1 flutter_form_builder: ^9.1.1
form_builder_validators: ^9.0.0 form_builder_validators: ^9.0.0
flutter_foreground_task: ^6.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter