mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 10:30:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4840c4b8a4 | ||
|
|
e72b097aa2 | ||
|
|
af52c4ce7e | ||
|
|
aae79c2449 | ||
|
|
83838afce9 | ||
|
|
4690107e32 | ||
|
|
74b08b3631 | ||
|
|
953d348c0c | ||
|
|
00cea68b6e |
No files matched your search
@@ -32,6 +32,10 @@ version-resolver:
|
||||
- 'patch'
|
||||
default: patch
|
||||
template: |
|
||||
## Changes
|
||||
# Changes
|
||||
|
||||
$CHANGES
|
||||
|
||||
# 更新日志
|
||||
|
||||
$CHANGES
|
||||
+160
-5
@@ -2,6 +2,11 @@ name: build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
platform:
|
||||
description: 'Build platform'
|
||||
required: true
|
||||
default: 'all'
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.19"
|
||||
@@ -24,6 +29,7 @@ jobs:
|
||||
myToken: ${{ github.token }}
|
||||
|
||||
build-windows:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
|
||||
runs-on: windows-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
@@ -124,6 +130,7 @@ jobs:
|
||||
asset_path: ui/flutter/build/windows/Output/*
|
||||
overwrite: true
|
||||
build-macos-arm64-lib:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
@@ -134,13 +141,14 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
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
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos-arm64-lib
|
||||
path: libgopeed.dylib
|
||||
build-macos:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
|
||||
runs-on: macos-latest
|
||||
needs: [ get-release, build-macos-arm64-lib ]
|
||||
steps:
|
||||
@@ -155,7 +163,9 @@ jobs:
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
- name: Install appdmg
|
||||
run: npm install -g appdmg
|
||||
run: |
|
||||
python3 -m pip install setuptools
|
||||
npm install -g appdmg
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: macos-arm64-lib
|
||||
@@ -197,6 +207,7 @@ jobs:
|
||||
asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
|
||||
overwrite: true
|
||||
build-linux:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
@@ -210,6 +221,10 @@ jobs:
|
||||
- run: |
|
||||
sudo apt-get update -y
|
||||
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
|
||||
env:
|
||||
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
|
||||
cd ui/flutter
|
||||
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
|
||||
cp assets/icon/icon_1024.png debian/gui/gopeed.png
|
||||
cat>debian/debian.yaml<<EOF
|
||||
flutter_app:
|
||||
command: gopeed
|
||||
arch: x64
|
||||
parent: /usr/local/lib
|
||||
parent: /usr/local/bin
|
||||
control:
|
||||
Package: gopeed
|
||||
Version: ${VERSION:1}
|
||||
@@ -241,6 +263,8 @@ jobs:
|
||||
Name=Gopeed
|
||||
GenericName=Gopeed
|
||||
Comment=High speed downloader that supports all platforms.
|
||||
Exec=gopeed
|
||||
Icon=gopeed.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility
|
||||
@@ -249,14 +273,142 @@ jobs:
|
||||
|
||||
dart pub global activate 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
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ needs.get-release.outputs.upload_url }}
|
||||
asset_path: ui/flutter/debian/packages/*
|
||||
asset_path: ui/flutter/build/dist/*
|
||||
overwrite: true
|
||||
build-android:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
@@ -294,6 +446,7 @@ jobs:
|
||||
asset_path: ui/flutter/dist/*
|
||||
overwrite: true
|
||||
build-ios:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
|
||||
runs-on: macos-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
@@ -326,6 +479,7 @@ jobs:
|
||||
asset_path: ui/flutter/dist/*
|
||||
overwrite: true
|
||||
build-web:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
@@ -373,6 +527,7 @@ jobs:
|
||||
asset_path: dist/zip/*
|
||||
overwrite: true
|
||||
build-docker:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ get-release ]
|
||||
steps:
|
||||
|
||||
+14
-1
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/download"
|
||||
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
||||
@@ -47,16 +48,28 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
printProgress(emptyTask, "downloading...")
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
var (
|
||||
lastLineLen = 0
|
||||
sb = new(strings.Builder)
|
||||
emptyTask = &download.Task{
|
||||
Progress: &download.Progress{},
|
||||
Meta: &fetcher.FetcherMeta{
|
||||
Res: &base.Resource{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
speed := util.ByteFmt(task.Progress.Speed)
|
||||
totalSize := util.ByteFmt(task.Meta.Res.Size)
|
||||
|
||||
@@ -87,7 +87,6 @@ func (f *Fetcher) Create(opts *base.Options) (err error) {
|
||||
if f.meta.Res != nil {
|
||||
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
|
||||
}
|
||||
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -101,6 +100,14 @@ func (f *Fetcher) Start() (err error) {
|
||||
ft.setTorrentDir(f.meta.FolderPath())
|
||||
}
|
||||
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) {
|
||||
f.torrent.DownloadAll()
|
||||
} else {
|
||||
@@ -134,7 +141,7 @@ func (f *Fetcher) safeDrop() {
|
||||
|
||||
func (f *Fetcher) Wait() (err error) {
|
||||
for {
|
||||
if f.torrentReady.Load() {
|
||||
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
|
||||
done := true
|
||||
for _, selectIndex := range f.meta.Opts.SelectFiles {
|
||||
file := f.torrent.Files()[selectIndex]
|
||||
@@ -154,7 +161,7 @@ func (f *Fetcher) Wait() (err error) {
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -195,7 +202,7 @@ func (f *Fetcher) updateRes() {
|
||||
Size: file.Length(),
|
||||
}
|
||||
}
|
||||
res.CalcSize()
|
||||
res.CalcSize(nil)
|
||||
f.meta.Res = res
|
||||
if f.meta.Opts != nil {
|
||||
f.meta.Opts.InitSelectFiles(len(res.Files))
|
||||
|
||||
+6
-3
@@ -3,6 +3,7 @@ package base
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/GopeedLab/gopeed/pkg/util"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Request download request
|
||||
@@ -47,10 +48,12 @@ func (r *Resource) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resource) CalcSize() {
|
||||
func (r *Resource) CalcSize(selectFiles []int) {
|
||||
var size int64
|
||||
for _, file := range r.Files {
|
||||
size += file.Size
|
||||
for i, file := range r.Files {
|
||||
if len(selectFiles) == 0 || slices.Contains(selectFiles, i) {
|
||||
size += file.Size
|
||||
}
|
||||
}
|
||||
r.Size = size
|
||||
}
|
||||
|
||||
+19
-12
@@ -540,11 +540,17 @@ func (d *Downloader) GetTask(id string) *Task {
|
||||
}
|
||||
|
||||
func (d *Downloader) GetTasks() []*Task {
|
||||
// reverse tasks, so that the latest task is in the front
|
||||
tasks := make([]*Task, len(d.tasks))
|
||||
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
|
||||
}
|
||||
|
||||
@@ -579,16 +585,16 @@ func (d *Downloader) watch(task *Task) {
|
||||
d.emit(EventKeyError, task, err)
|
||||
} else {
|
||||
task.Progress.Used = task.timer.Used()
|
||||
if task.Size == 0 {
|
||||
task.Size = task.fetcher.Progress().TotalDownloaded()
|
||||
task.Meta.Res.Size = task.Size
|
||||
if task.Meta.Res.Size == 0 {
|
||||
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
|
||||
}
|
||||
used := task.Progress.Used / int64(time.Second)
|
||||
if used == 0 {
|
||||
used = 1
|
||||
}
|
||||
task.Progress.Speed = task.Size / used
|
||||
task.Progress.Downloaded = task.Size
|
||||
totalSize := task.Meta.Res.Size
|
||||
task.Progress.Speed = totalSize / used
|
||||
task.Progress.Downloaded = totalSize
|
||||
task.Status = base.DownloadStatusDone
|
||||
d.storage.Put(bucketTask, task.ID, task.clone())
|
||||
d.emit(EventKeyDone, task)
|
||||
@@ -741,6 +747,8 @@ func (d *Downloader) doStart(task *Task) (err error) {
|
||||
}
|
||||
task.Meta.Opts.Name = newName
|
||||
}
|
||||
|
||||
task.Meta.Res.CalcSize(task.Meta.Opts.SelectFiles)
|
||||
}
|
||||
|
||||
task.Progress.Speed = 0
|
||||
@@ -848,12 +856,11 @@ func (b *boot) Listener(listener Listener) *boot {
|
||||
}
|
||||
|
||||
func (b *boot) Create(opts *base.Options) (string, error) {
|
||||
rr, err := b.Resolve()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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 {
|
||||
|
||||
@@ -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())
|
||||
return
|
||||
}
|
||||
ctx.Res.CalcSize()
|
||||
ctx.Res.CalcSize(nil)
|
||||
}
|
||||
res = ctx.Res
|
||||
},
|
||||
|
||||
@@ -22,7 +22,6 @@ type Task struct {
|
||||
Meta *fetcher.FetcherMeta `json:"meta"`
|
||||
Status base.Status `json:"status"`
|
||||
Progress *Progress `json:"progress"`
|
||||
Size int64 `json:"size"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
fetcherBuilder fetcher.FetcherBuilder
|
||||
@@ -50,7 +49,6 @@ func (t *Task) clone() *Task {
|
||||
Meta: t.Meta,
|
||||
Status: t.Status,
|
||||
Progress: t.Progress,
|
||||
Size: t.Size,
|
||||
CreatedAt: t.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ android {
|
||||
applicationId "com.gopeed"
|
||||
// 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.
|
||||
minSdkVersion 19
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
@@ -72,6 +72,12 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</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.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"selectAll": "Select All",
|
||||
"task": "Task",
|
||||
"task": "Tasks",
|
||||
"downloading": "downloading",
|
||||
"downloaded": "downloaded",
|
||||
"setting": "Setting",
|
||||
"setting": "Settings",
|
||||
"donate": "Donate",
|
||||
"exit": "Exit",
|
||||
"create": "Create Task",
|
||||
"directDownload": "Direct Download",
|
||||
"advancedOptions": "Advanced Options",
|
||||
"downloadLink": "Download Link",
|
||||
"downloadLinkValid": "Please enter the download link",
|
||||
@@ -66,8 +67,11 @@
|
||||
"extensionUpdateSuccess": "Updated successfully",
|
||||
"extensionDelete": "Delete Extension",
|
||||
"extensionAlreadyLatest": "It's already the latest version",
|
||||
"extensionFind": "Find Extensions",
|
||||
"extensionDevelop": "Develop Extensions",
|
||||
"history": "History",
|
||||
"clearHistory": "Clear History",
|
||||
"noHistoryFound": "No History Found"
|
||||
}
|
||||
|
||||
"noHistoryFound": "No History Found",
|
||||
"serviceTitle": "Download Service",
|
||||
"serviceText": "Running"
|
||||
}
|
||||
@@ -12,14 +12,16 @@
|
||||
"donate": "Пожертвовать",
|
||||
"exit": "Выход",
|
||||
"create": "Создать задачу",
|
||||
"downloadLink": "Ссылка для скачивание",
|
||||
"advancedOptions": "Расширенные опции",
|
||||
"downloadLink": "Ссылка на скачивание",
|
||||
"downloadLinkValid": "Пожалуйста, введите ссылку для скачивания",
|
||||
"downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append",
|
||||
"downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append",
|
||||
"downloadLinkHitDesktop": ", или перетащите сюда торрент-файл",
|
||||
"download": "Скачать",
|
||||
"noFileSelected": "Файл не выбран",
|
||||
"noStoragePermission": "Требуется доступ к хранилищу",
|
||||
"selectFile": "Выберать файл",
|
||||
"selectFile": "Выбрать файл",
|
||||
"rename": "Переименовать",
|
||||
"basic": "Основные параметры",
|
||||
"advanced": "Расширенные",
|
||||
"general": "Общие",
|
||||
@@ -57,5 +59,15 @@
|
||||
"delete": "Удалить",
|
||||
"newVersionTitle": "Обнаружена новая версия @version",
|
||||
"newVersionUpdate": "Обновить",
|
||||
"newVersionLater": "позже"
|
||||
}
|
||||
"newVersionLater": "позже",
|
||||
"extensions": "Расширения",
|
||||
"extensionInstallSuccess": "Установка завершена",
|
||||
"extensionUpdateSuccess": "Обновление завершено",
|
||||
"extensionDelete": "Удалить расширение",
|
||||
"extensionAlreadyLatest": "Это последняя версия",
|
||||
"extensionFind": "Найти расширения",
|
||||
"extensionDevelop": "Разработка расширений",
|
||||
"history": "История",
|
||||
"clearHistory": "Очистить историю",
|
||||
"noHistoryFound": "История не найдена"
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
"donate": "打赏",
|
||||
"exit": "退出",
|
||||
"create": "创建任务",
|
||||
"directDownload": "直接下载",
|
||||
"advancedOptions": "高级选项",
|
||||
"downloadLink": "下载链接",
|
||||
"downloadLinkValid": "请输入下载链接",
|
||||
@@ -66,7 +67,11 @@
|
||||
"extensionUpdateSuccess": "更新成功",
|
||||
"extensionDelete": "删除扩展",
|
||||
"extensionAlreadyLatest": "已经是最新版本",
|
||||
"extensionFind": "获取扩展",
|
||||
"extensionDevelop": "开发扩展",
|
||||
"history": "历史记录",
|
||||
"clearHistory": "清空历史记录",
|
||||
"noHistoryFound": "暂无历史记录"
|
||||
"noHistoryFound": "暂无历史记录",
|
||||
"serviceTitle": "下载服务",
|
||||
"serviceText": "运行中"
|
||||
}
|
||||
@@ -33,6 +33,15 @@ import Libgopeed
|
||||
})
|
||||
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
|
||||
if #available(iOS 10.0, *) {
|
||||
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
|
||||
}
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
||||
func registerPlugins(registry: FlutterPluginRegistry) {
|
||||
GeneratedPluginRegistrant.register(with: registry)
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
#import <flutter_foreground_task/FlutterForegroundTaskPlugin.h>
|
||||
@@ -12,7 +12,6 @@ class Task {
|
||||
Meta meta;
|
||||
Status status;
|
||||
Progress progress;
|
||||
int size;
|
||||
DateTime createdAt;
|
||||
|
||||
Task({
|
||||
@@ -20,7 +19,6 @@ class Task {
|
||||
required this.meta,
|
||||
required this.status,
|
||||
required this.progress,
|
||||
required this.size,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
|
||||
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
|
||||
status: $enumDecode(_$StatusEnumMap, json['status']),
|
||||
progress: Progress.fromJson(json['progress'] as Map<String, dynamic>),
|
||||
size: json['size'] as int,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
);
|
||||
|
||||
@@ -20,7 +19,6 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
|
||||
'meta': instance.meta.toJson(),
|
||||
'status': _$StatusEnumMap[instance.status]!,
|
||||
'progress': instance.progress.toJson(),
|
||||
'size': instance.size,
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:ui';
|
||||
|
||||
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:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -79,21 +80,18 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
@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);
|
||||
}
|
||||
|
||||
_initDeepLinks().onError((error, stackTrace) =>
|
||||
logger.w("initDeepLinks error", error, stackTrace));
|
||||
|
||||
_initWindows().onError((error, stackTrace) =>
|
||||
logger.w("initWindows error", error, stackTrace));
|
||||
|
||||
_initTray().onError(
|
||||
(error, stackTrace) => logger.w("initTray error", error, stackTrace));
|
||||
|
||||
_initForegroundTask().onError((error, stackTrace) =>
|
||||
logger.w("initForegroundTask error", error, stackTrace));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -210,6 +208,42 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
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 {
|
||||
final path = uri.scheme == "magnet"
|
||||
? uri.toString()
|
||||
|
||||
@@ -10,8 +10,9 @@ class CreateController extends GetxController
|
||||
final RxList fileInfos = [].obs;
|
||||
final RxList openedFolders = [].obs;
|
||||
final selectedIndexes = <int>[].obs;
|
||||
final isResolving = false.obs;
|
||||
final isConfirming = false.obs;
|
||||
final showAdvanced = false.obs;
|
||||
final directDownload = false.obs;
|
||||
late TabController advancedTabController;
|
||||
final oldUrl = "".obs;
|
||||
final fileDataUri = "".obs;
|
||||
|
||||
@@ -24,9 +24,12 @@ import '../../app/controllers/app_controller.dart';
|
||||
import '../controllers/create_controller.dart';
|
||||
|
||||
class CreateView extends GetView<CreateController> {
|
||||
final _resolveFormKey = GlobalKey<FormState>();
|
||||
final _confirmFormKey = GlobalKey<FormState>();
|
||||
|
||||
final _urlController = TextEditingController();
|
||||
final _renameController = TextEditingController();
|
||||
final _connectionsController = TextEditingController();
|
||||
final _pathController = TextEditingController();
|
||||
final _confirmController = RoundedLoadingButtonController();
|
||||
final _httpUaController = TextEditingController();
|
||||
final _httpCookieController = TextEditingController();
|
||||
@@ -40,6 +43,17 @@ class CreateView extends GetView<CreateController> {
|
||||
|
||||
@override
|
||||
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();
|
||||
if (_urlController.text.isEmpty) {
|
||||
if (filePath?.isNotEmpty ?? false) {
|
||||
@@ -91,214 +105,253 @@ class CreateView extends GetView<CreateController> {
|
||||
onTap: () {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Form(
|
||||
key: _resolveFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
controller: _urlController,
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
hintText: _hitText(),
|
||||
hintStyle: const TextStyle(fontSize: 12),
|
||||
labelText: 'downloadLink'.tr,
|
||||
icon: const Icon(Icons.link),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
_urlController.clear();
|
||||
controller.clearFileDataUri();
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Form(
|
||||
key: _confirmFormKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
controller: _urlController,
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
hintText: _hitText(),
|
||||
hintStyle: const TextStyle(fontSize: 12),
|
||||
labelText: 'downloadLink'.tr,
|
||||
icon: const Icon(Icons.link),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
_urlController.clear();
|
||||
controller.clearFileDataUri();
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty
|
||||
? null
|
||||
: 'downloadLinkValid'.tr;
|
||||
},
|
||||
onChanged: (v) async {
|
||||
controller.clearFileDataUri();
|
||||
if (controller.oldUrl.value.isEmpty) {
|
||||
recognizeMagnetUri(v);
|
||||
}
|
||||
controller.oldUrl.value = v;
|
||||
},
|
||||
),
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty
|
||||
? null
|
||||
: 'downloadLinkValid'.tr;
|
||||
},
|
||||
onChanged: (v) async {
|
||||
controller.clearFileDataUri();
|
||||
if (controller.oldUrl.value.isEmpty) {
|
||||
recognizeMagnetUri(v);
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.folder_open),
|
||||
onPressed: () async {
|
||||
var pr = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["torrent"]);
|
||||
if (pr != null) {
|
||||
if (!Util.isWeb()) {
|
||||
_urlController.text = pr.files[0].path ?? "";
|
||||
return;
|
||||
}
|
||||
_urlController.text = pr.files[0].name;
|
||||
controller.setFileDataUri(pr.files[0].bytes!);
|
||||
}
|
||||
controller.oldUrl.value = v;
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.folder_open),
|
||||
onPressed: () async {
|
||||
var pr = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["torrent"]);
|
||||
if (pr != null) {
|
||||
if (!Util.isWeb()) {
|
||||
_urlController.text = pr.files[0].path ?? "";
|
||||
return;
|
||||
}
|
||||
_urlController.text = pr.files[0].name;
|
||||
controller.setFileDataUri(pr.files[0].bytes!);
|
||||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
onPressed: () async {
|
||||
List<String> resultOfHistories =
|
||||
await _historyController.getAllHistory();
|
||||
// reversing: display last entered history first
|
||||
List<String> reverseResultOfHistories =
|
||||
resultOfHistories.reversed.toList();
|
||||
// show dialog box to list history
|
||||
if (context.mounted) {
|
||||
showGeneralDialog(
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
transitionBuilder: (context, a1, a2, widget) {
|
||||
return Transform.scale(
|
||||
scale: a1.value,
|
||||
child: Opacity(
|
||||
opacity: a1.value,
|
||||
child: HistoryView(
|
||||
isHistoryListEmpty:
|
||||
reverseResultOfHistories.isEmpty,
|
||||
historyList: ListView.builder(
|
||||
itemCount: reverseResultOfHistories.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_urlController.text =
|
||||
reverseResultOfHistories[index];
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
margin:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.background,
|
||||
borderRadius:
|
||||
BorderRadius.circular(10.0),
|
||||
),
|
||||
child: Text(
|
||||
reverseResultOfHistories[index]
|
||||
.toString(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
onPressed: () async {
|
||||
List<String> resultOfHistories =
|
||||
await _historyController.getAllHistory();
|
||||
// reversing: display last entered history first
|
||||
List<String> reverseResultOfHistories =
|
||||
resultOfHistories.reversed.toList();
|
||||
// show dialog box to list history
|
||||
if (context.mounted) {
|
||||
showGeneralDialog(
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
transitionBuilder: (context, a1, a2, widget) {
|
||||
return Transform.scale(
|
||||
scale: a1.value,
|
||||
child: Opacity(
|
||||
opacity: a1.value,
|
||||
child: HistoryView(
|
||||
isHistoryListEmpty:
|
||||
reverseResultOfHistories.isEmpty,
|
||||
historyList: ListView.builder(
|
||||
itemCount:
|
||||
reverseResultOfHistories.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_urlController.text =
|
||||
reverseResultOfHistories[
|
||||
index];
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
margin:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.background,
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
10.0),
|
||||
),
|
||||
child: Text(
|
||||
reverseResultOfHistories[
|
||||
index]
|
||||
.toString(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionDuration:
|
||||
const Duration(milliseconds: 250),
|
||||
barrierDismissible: true,
|
||||
barrierLabel: '',
|
||||
context: context,
|
||||
pageBuilder: (context, animation1, animation2) {
|
||||
return const Text('PAGE BUILDER');
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
//.where((e) => e != null).map((e) => e!).toList(),
|
||||
);
|
||||
},
|
||||
transitionDuration:
|
||||
const Duration(milliseconds: 250),
|
||||
barrierDismissible: true,
|
||||
barrierLabel: '',
|
||||
context: context,
|
||||
pageBuilder: (context, animation1, animation2) {
|
||||
return const Text('PAGE BUILDER');
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: controller.showAdvanced.value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 40, top: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: controller.advancedTabController,
|
||||
tabs: const [
|
||||
Tab(
|
||||
text: 'HTTP',
|
||||
),
|
||||
Tab(
|
||||
text: 'BitTorrent',
|
||||
)
|
||||
],
|
||||
),
|
||||
AutoScaleTabBarView(
|
||||
controller: controller.advancedTabController,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _httpUaController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'User-Agent',
|
||||
)),
|
||||
TextFormField(
|
||||
controller: _httpCookieController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Cookie',
|
||||
)),
|
||||
TextFormField(
|
||||
controller: _httpRefererController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Referer',
|
||||
)),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _btTrackerController,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Trakers',
|
||||
hintText: 'addTrackerHit'.tr,
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
]),
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
controller.showAdvanced.value =
|
||||
!controller.showAdvanced.value;
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
DirectorySelector(
|
||||
controller: _pathController,
|
||||
),
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: controller.showAdvanced.value,
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: controller.advancedTabController,
|
||||
tabs: const [
|
||||
Tab(
|
||||
text: 'HTTP',
|
||||
),
|
||||
Tab(
|
||||
text: 'BitTorrent',
|
||||
)
|
||||
],
|
||||
),
|
||||
AutoScaleTabBarView(
|
||||
controller: controller.advancedTabController,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _httpUaController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'User-Agent',
|
||||
)),
|
||||
TextFormField(
|
||||
controller: _httpCookieController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Cookie',
|
||||
)),
|
||||
TextFormField(
|
||||
controller: _httpRefererController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Referer',
|
||||
)),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _btTrackerController,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Trakers',
|
||||
hintText: 'addTrackerHit'.tr,
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
).paddingOnly(top: 16),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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: () {
|
||||
controller.showAdvanced.value =
|
||||
!controller.showAdvanced.value;
|
||||
},
|
||||
child: Row(children: [
|
||||
Obx(() => Checkbox(
|
||||
value: controller.showAdvanced.value,
|
||||
onChanged: (bool? value) {
|
||||
@@ -308,22 +361,24 @@ class CreateView extends GetView<CreateController> {
|
||||
)),
|
||||
Text('advancedOptions'.tr),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: RoundedLoadingButton(
|
||||
color: Get.theme.colorScheme.secondary,
|
||||
onPressed: _doResolve,
|
||||
controller: _confirmController,
|
||||
child: Text('confirm'.tr),
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: RoundedLoadingButton(
|
||||
color: Get.theme.colorScheme.secondary,
|
||||
onPressed: _doConfirm,
|
||||
controller: _confirmController,
|
||||
child: Text('confirm'.tr),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -346,45 +401,54 @@ class CreateView extends GetView<CreateController> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _doResolve() async {
|
||||
if (controller.isResolving.value) {
|
||||
Future<void> _doConfirm() async {
|
||||
if (controller.isConfirming.value) {
|
||||
return;
|
||||
}
|
||||
controller.isResolving.value = true;
|
||||
controller.isConfirming.value = true;
|
||||
try {
|
||||
_confirmController.start();
|
||||
if (_resolveFormKey.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()));
|
||||
if (_confirmFormKey.currentState!.validate()) {
|
||||
final submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty
|
||||
? controller.fileDataUri.value
|
||||
: _urlController.text;
|
||||
|
||||
final urls = Util.textToLines(submitUrl);
|
||||
// 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 submitUrl = Util.isWeb() && controller.fileDataUri.isNotEmpty
|
||||
? controller.fileDataUri.value
|
||||
: _urlController.text;
|
||||
// add final submitUrl to the history
|
||||
_historyController.addHistory(submitUrl);
|
||||
rr = await resolve(Request(
|
||||
final rr = await resolve(Request(
|
||||
url: submitUrl,
|
||||
extra: parseReqExtra(_urlController.text),
|
||||
));
|
||||
await _showResolveDialog(rr);
|
||||
}
|
||||
await _showResolveDialog(rr, isMultiLine);
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorMessage(e);
|
||||
} finally {
|
||||
_confirmController.reset();
|
||||
controller.isResolving.value = false;
|
||||
controller.isConfirming.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +471,13 @@ class CreateView extends GetView<CreateController> {
|
||||
return reqExtra;
|
||||
}
|
||||
|
||||
Object? parseReqOpts() {
|
||||
return _connectionsController.text.isEmpty
|
||||
? null
|
||||
: (OptsExtraHttp()
|
||||
..connections = int.parse(_connectionsController.text));
|
||||
}
|
||||
|
||||
String _hitText() {
|
||||
return 'downloadLinkHit'.trParams({
|
||||
'append':
|
||||
@@ -414,14 +485,8 @@ class CreateView extends GetView<CreateController> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showResolveDialog(ResolveResult rr, bool isMuiltiLine) async {
|
||||
final appController = Get.find<AppController>();
|
||||
|
||||
Future<void> _showResolveDialog(ResolveResult rr) async {
|
||||
final createFormKey = GlobalKey<FormState>();
|
||||
final nameController = TextEditingController();
|
||||
final connectionsController = TextEditingController();
|
||||
final pathController = TextEditingController(
|
||||
text: appController.downloaderConfig.value.downloadDir);
|
||||
final downloadController = RoundedLoadingButtonController();
|
||||
return showDialog<void>(
|
||||
context: Get.context!,
|
||||
@@ -440,31 +505,7 @@ class CreateView extends GetView<CreateController> {
|
||||
child: Form(
|
||||
key: createFormKey,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
child: Column(
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
child: FileListView(files: rr.res.files)),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -500,25 +541,19 @@ class CreateView extends GetView<CreateController> {
|
||||
showMessage('tip'.tr, 'noFileSelected'.tr);
|
||||
return;
|
||||
}
|
||||
Object? optExtra = connectionsController.text.isEmpty
|
||||
? null
|
||||
: (OptsExtraHttp()
|
||||
..connections =
|
||||
int.parse(connectionsController.text));
|
||||
final optExtra = parseReqOpts();
|
||||
if (createFormKey.currentState!.validate()) {
|
||||
if (rr.id.isEmpty) {
|
||||
// create task batch, there has two ways to batch create task
|
||||
// 1. from multi line urls
|
||||
// 2. from extension resolve result
|
||||
// from extension resolve result
|
||||
await Future.wait(
|
||||
controller.selectedIndexes.map((index) {
|
||||
final file = rr.res.files[index];
|
||||
return createTask(CreateTask(
|
||||
req: file.req!,
|
||||
opt: Options(
|
||||
name: isMuiltiLine ? "" : file.name,
|
||||
name: file.name,
|
||||
path: path.join(
|
||||
pathController.text, rr.res.name),
|
||||
_pathController.text, rr.res.name),
|
||||
selectFiles: [],
|
||||
extra: optExtra)));
|
||||
}));
|
||||
@@ -526,8 +561,8 @@ class CreateView extends GetView<CreateController> {
|
||||
await createTask(CreateTask(
|
||||
rid: rr.id,
|
||||
opt: Options(
|
||||
name: nameController.text,
|
||||
path: pathController.text,
|
||||
name: _renameController.text,
|
||||
path: _pathController.text,
|
||||
selectFiles: controller.selectedIndexes,
|
||||
extra: optExtra)));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,31 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -90,7 +114,7 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
: Container()
|
||||
],
|
||||
)),
|
||||
const SizedBox(height: 32),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Obx(() => ListView.builder(
|
||||
itemCount: controller.extensions.length,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../routes/app_pages.dart';
|
||||
@@ -23,139 +24,141 @@ class HomeView extends GetView<HomeController> {
|
||||
break;
|
||||
}
|
||||
|
||||
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(),
|
||||
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(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class LocaleKeys {
|
||||
static const donate = 'donate';
|
||||
static const exit = 'exit';
|
||||
static const create = 'create';
|
||||
static const directDownload = 'directDownload';
|
||||
static const advancedOptions = 'advancedOptions';
|
||||
static const downloadLink = 'downloadLink';
|
||||
static const downloadLinkValid = 'downloadLinkValid';
|
||||
@@ -82,9 +83,13 @@ class LocaleKeys {
|
||||
static const extensionUpdateSuccess = 'extensionUpdateSuccess';
|
||||
static const extensionDelete = 'extensionDelete';
|
||||
static const extensionAlreadyLatest = 'extensionAlreadyLatest';
|
||||
static const extensionFind = 'extensionFind';
|
||||
static const extensionDevelop = 'extensionDevelop';
|
||||
static const history = 'history';
|
||||
static const clearHistory = 'clearHistory';
|
||||
static const noHistoryFound = 'noHistoryFound';
|
||||
static const serviceTitle = 'serviceTitle';
|
||||
static const serviceText = 'serviceText';
|
||||
}
|
||||
|
||||
class Locales {
|
||||
@@ -95,13 +100,14 @@ class Locales {
|
||||
'confirm': 'Confirm',
|
||||
'cancel': 'Cancel',
|
||||
'selectAll': 'Select All',
|
||||
'task': 'Task',
|
||||
'task': 'Tasks',
|
||||
'downloading': 'downloading',
|
||||
'downloaded': 'downloaded',
|
||||
'setting': 'Setting',
|
||||
'setting': 'Settings',
|
||||
'donate': 'Donate',
|
||||
'exit': 'Exit',
|
||||
'create': 'Create Task',
|
||||
'directDownload': 'Direct Download',
|
||||
'advancedOptions': 'Advanced Options',
|
||||
'downloadLink': 'Download Link',
|
||||
'downloadLinkValid': 'Please enter the download link',
|
||||
@@ -158,9 +164,13 @@ class Locales {
|
||||
'extensionUpdateSuccess': 'Updated successfully',
|
||||
'extensionDelete': 'Delete Extension',
|
||||
'extensionAlreadyLatest': 'It\'s already the latest version',
|
||||
'extensionFind': 'Find Extensions',
|
||||
'extensionDevelop': 'Develop Extensions',
|
||||
'history': 'History',
|
||||
'clearHistory': 'Clear History',
|
||||
'noHistoryFound': 'No History Found',
|
||||
'serviceTitle': 'Download Service',
|
||||
'serviceText': 'Running',
|
||||
};
|
||||
static const fa_IR = {
|
||||
'label': 'فارسی',
|
||||
@@ -296,15 +306,17 @@ class Locales {
|
||||
'donate': 'Пожертвовать',
|
||||
'exit': 'Выход',
|
||||
'create': 'Создать задачу',
|
||||
'downloadLink': 'Ссылка для скачивание',
|
||||
'advancedOptions': 'Расширенные опции',
|
||||
'downloadLink': 'Ссылка на скачивание',
|
||||
'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания',
|
||||
'downloadLinkHit':
|
||||
'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append',
|
||||
'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append',
|
||||
'downloadLinkHitDesktop': ', или перетащите сюда торрент-файл',
|
||||
'download': 'Скачать',
|
||||
'noFileSelected': 'Файл не выбран',
|
||||
'noStoragePermission': 'Требуется доступ к хранилищу',
|
||||
'selectFile': 'Выберать файл',
|
||||
'selectFile': 'Выбрать файл',
|
||||
'rename': 'Переименовать',
|
||||
'basic': 'Основные параметры',
|
||||
'advanced': 'Расширенные',
|
||||
'general': 'Общие',
|
||||
@@ -344,6 +356,16 @@ class Locales {
|
||||
'newVersionTitle': 'Обнаружена новая версия @version',
|
||||
'newVersionUpdate': 'Обновить',
|
||||
'newVersionLater': 'позже',
|
||||
'extensions': 'Расширения',
|
||||
'extensionInstallSuccess': 'Установка завершена',
|
||||
'extensionUpdateSuccess': 'Обновление завершено',
|
||||
'extensionDelete': 'Удалить расширение',
|
||||
'extensionAlreadyLatest': 'Это последняя версия',
|
||||
'extensionFind': 'Найти расширения',
|
||||
'extensionDevelop': 'Разработка расширений',
|
||||
'history': 'История',
|
||||
'clearHistory': 'Очистить историю',
|
||||
'noHistoryFound': 'История не найдена',
|
||||
};
|
||||
static const zh_CN = {
|
||||
'label': '中文(简体)',
|
||||
@@ -359,6 +381,7 @@ class Locales {
|
||||
'donate': '打赏',
|
||||
'exit': '退出',
|
||||
'create': '创建任务',
|
||||
'directDownload': '直接下载',
|
||||
'advancedOptions': '高级选项',
|
||||
'downloadLink': '下载链接',
|
||||
'downloadLinkValid': '请输入下载链接',
|
||||
@@ -413,9 +436,13 @@ class Locales {
|
||||
'extensionUpdateSuccess': '更新成功',
|
||||
'extensionDelete': '删除扩展',
|
||||
'extensionAlreadyLatest': '已经是最新版本',
|
||||
'extensionFind': '获取扩展',
|
||||
'extensionDevelop': '开发扩展',
|
||||
'history': '历史记录',
|
||||
'clearHistory': '清空历史记录',
|
||||
'noHistoryFound': '没有历史记录',
|
||||
'noHistoryFound': '暂无历史记录',
|
||||
'serviceTitle': '下载服务',
|
||||
'serviceText': '运行中',
|
||||
};
|
||||
static const zh_TW = {
|
||||
'label': '中文 (正體) ',
|
||||
|
||||
+239
-230
File diff suppressed because it is too large.
Load diff
@@ -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.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.4.3+1
|
||||
version: 1.4.4+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@@ -56,12 +56,16 @@ dependencies:
|
||||
app_links: ^3.4.2
|
||||
uri_to_file: ^0.2.0
|
||||
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
|
||||
share_plus: ^7.1.0
|
||||
open_file: ^3.3.2
|
||||
flutter_form_builder: ^9.1.1
|
||||
form_builder_validators: ^9.0.0
|
||||
flutter_foreground_task: ^6.1.2
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in new issue
Block a user