Compare commits

..
Author SHA1 Message Date
Levi 5303e44ead temp 2025-11-16 19:36:39 +08:00
Levi 8835c2855a temp 2025-09-29 20:13:30 +08:00
Levi 2ab5a4bb53 TEMP 2025-04-10 22:28:41 +08:00
Levi bfad91dcab release: v1.6.11 2025-03-14 23:03:48 +08:00
Levi 61a959adda ci: apk build split abi (#962) 2025-03-14 22:58:28 +08:00
Levi f4b839fd11 fix: http download size exceeds file size (#959) 2025-03-14 20:07:34 +08:00
Levi 969aeb5e0a fix: : web auth priority when api token and basic auth coexist (#960) 2025-03-14 19:22:31 +08:00
Levi 68d42eb397 feat: web http file server caching and gzip support (#961) 2025-03-14 18:31:05 +08:00
Levi 08603f9510 feat: auto updater (#933) 2025-03-13 18:31:38 +08:00
Internetezoo b450bf67bd feat: Add Hungarian languages (#940) 2025-03-10 11:59:44 +08:00
Levi 03e26dbea8 feat: improve http download retry strategy (#951) 2025-03-07 12:01:52 +08:00
Levi 046a3ac9f9 fix: can not open url encode torrent file path (#945) 2025-02-27 00:00:25 +08:00
Levi 9c5aebaf36 fix: path url encode filename parse (#939) 2025-02-18 19:37:38 +08:00
Xiangxin a094715d69 fix: windows title bar theme switch (#936) 2025-02-17 19:32:09 +08:00
铺盖崽 69bb875172 ci: fix flathub build error (#937) 2025-02-17 09:15:42 +08:00
铺盖崽 589f10c7ef ci: add linux arm64 build (#935) 2025-02-16 10:54:25 +08:00
Levi 654e57e946 release: v1.6.10 2025-02-13 21:56:27 +08:00
Levi b94b99ae09 fix: gopeed scheme safe url params (#932) 2025-02-13 21:55:37 +08:00
Levi fba990a056 fix: http MIME encoded word filename parse (#931) 2025-02-13 21:54:13 +08:00
𝗦𝗵𝗟𝗲𝗿𝗣 cb07ce58bc feat: improve Turkish Locales (#917) 2025-02-07 08:05:38 +08:00
Levi 50beef7b08 docs: improve download links (#913) 2025-02-06 12:19:27 +08:00
Levi bd39e4110f feat: add ukraine language (#909) 2025-02-05 10:51:58 +08:00
Levi 86efcb98c5 fix: web white screen (#908) 2025-02-05 10:51:33 +08:00
Levi f3d9873395 release: v1.6.9 2025-02-03 18:50:25 +08:00
Levi ef228c1722 feat: update android target sdk to 34 (#902) 2025-02-03 16:15:13 +08:00
Levi 6863317e9d feat: support tcp protocol on android (#903) 2025-02-03 13:49:46 +08:00
Levi 2b83a735cb fix: advanced options used by default when creating task (#901) 2025-02-03 13:42:50 +08:00
Levi 447f599783 fix: firefox native host integration (#895) 2025-02-01 18:14:59 +08:00
liwei f986f6253d ci: fix download-artifact version 2025-01-31 20:00:45 +08:00
liwei 2e1b621250 ci: fix 2025-01-31 20:00:14 +08:00
Levi d50947b2e6 release: v1.6.8 2025-01-31 19:15:01 +08:00
Levi 520a72415c feat: browser extensions native messaging host (#890) 2025-01-31 17:57:34 +08:00
203 changed files with 9504 additions and 903 deletions

No files matched your search

+117 -67
View File
@@ -38,78 +38,128 @@ version-resolver:
default: patch default: patch
template: | template: |
<table> <table>
<thead>
<tr>
<th colspan="2">Windows</th>
<th>MacOS</th>
<th colspan="4">Linux</th>
<th>Android</th>
<th>iOS</th>
<th>Docker</th>
<th colspan="2">Qnap</th>
<th colspan="8">Web</th>
</tr>
<tr>
<td><code>EXE</code></td>
<td><code>Portable</code></td>
<td><code>DMG</code></td>
<td><code>Flathub</code></td>
<td><code>SNAP</code></td>
<td><code>DEB</code></td>
<td><code>AppImage</code></td>
<td><code>APK</code></td>
<td><code>IPA</code></td>
<td>-</td>
<td colspan="2"><code>QPKG</code></td>
<td colspan="3"><code>Windows</code></td>
<td colspan="2"><code>MacOS</code></td>
<td colspan="3"><code>Linux</code></td>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="2">🪟 Windows</td>
<td><code>EXE</code></td>
<td>amd64</td> <td>amd64</td>
<td>amd64</td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
<td>-</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>amd64</td>
<td>arm64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td> <td><code>Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64-portable.zip">📥</a></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td> </tr>
<td><a href="https://snapcraft.io/gopeed">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td> <td>🍎 MacOS</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td> <td><code>DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td> <td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos.dmg">📥</a></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Link</a></td> <td rowspan="6">🐧 Linux</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Link</a></td> <td><code>Flathub</code></td
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Link</a></td> <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Link</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Link</a></td> <td><code>SNAP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Link</a></td> <td><a href="https://snapcraft.io/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>DEB</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.deb">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.deb">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>AppImage</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.AppImage">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.AppImage">📥</a></td>
</tr>
<tr>
<td rowspan="4">🤖 Android</td>
<td rowspan="4"><code>APK</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android.apk">📥</a></td>
</tr>
<tr>
<td>armeabi-v7a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-armeabi-v7a.apk">📥</a></td>
</tr>
<tr>
<td>arm64-v8a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-arm64-v8a.apk">📥</a></td>
</tr>
<tr>
<td>x86_64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-x86_64.apk">📥</a></td>
</tr>
<tr>
<td>📱 iOS</td>
<td><code>IPA</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-ios.ipa">📥</a></td>
</tr>
<tr>
<td>🐳 Docker</td>
<td>-</td>
<td>universal</td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2">💾 Qnap</td>
<td rowspan="2"><code>QPKG</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-amd64.qpkg">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-arm64.qpkg">📥</a></td>
</tr>
<tr>
<td rowspan="8">🌐 Web</td>
<td rowspan="3"><code>Windows</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-386.zip">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>MacOS</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-arm64.zip">📥</a></td>
</tr>
<tr>
<td rowspan="3"><code>Linux</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-386.zip">📥</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
+72 -20
View File
@@ -52,10 +52,11 @@ jobs:
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\" mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
go build -ldflags="-w -s" -o ui/flutter/assets/host/host.exe github.com/GopeedLab/gopeed/cmd/host go build -ldflags="-w -s" -o ui/flutter/assets/exec/host.exe github.com/GopeedLab/gopeed/cmd/host
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater.exe github.com/GopeedLab/gopeed/cmd/updater
cd ui/flutter cd ui/flutter
$TAG = "v$env:VERSION" $TAG = "v$env:VERSION"
flutter build windows flutter build windows --dart-define="UPDATE_CHANNEL=windowsPortable"
$mingw = "C:\Program Files\Git\mingw64\bin" $mingw = "C:\Program Files\Git\mingw64\bin"
$system = "C:\Windows\System32" $system = "C:\Windows\System32"
$release = "build\windows\x64\runner\Release\" $release = "build\windows\x64\runner\Release\"
@@ -69,6 +70,7 @@ jobs:
New-Item -Path build\windows\Output -ItemType Directory New-Item -Path build\windows\Output -ItemType Directory
Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$TAG-windows-amd64-portable.zip" Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$TAG-windows-amd64-portable.zip"
flutter build windows --dart-define="UPDATE_CHANNEL=windowsInstaller"
cd build/windows cd build/windows
echo @" echo @"
; Script generated by the Inno Setup Script Wizard. ; Script generated by the Inno Setup Script Wizard.
@@ -152,12 +154,14 @@ jobs:
run: | run: |
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/host go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/host
go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/updater
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: macos-arm64-lib name: macos-arm64-lib
path: | path: |
libgopeed.dylib libgopeed.dylib
host host
updater
build-macos: build-macos:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }} if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
runs-on: macos-13 runs-on: macos-13
@@ -177,7 +181,7 @@ jobs:
run: | run: |
python3 -m pip install setuptools --break-system-packages python3 -m pip install setuptools --break-system-packages
npm install -g appdmg npm install -g appdmg
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v4
with: with:
name: macos-arm64-lib name: macos-arm64-lib
path: ui/flutter/lib-arm64 path: ui/flutter/lib-arm64
@@ -190,20 +194,24 @@ jobs:
# amd64 lib # amd64 lib
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/lib-amd64/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
go build -ldflags="-w -s" -o ui/flutter/lib-amd64/host github.com/GopeedLab/gopeed/cmd/host go build -ldflags="-w -s" -o ui/flutter/lib-amd64/host github.com/GopeedLab/gopeed/cmd/host
go build -ldflags="-w -s" -o ui/flutter/lib-amd64/updater github.com/GopeedLab/gopeed/cmd/updater
# universal binary # universal binary
mkdir -p ui/flutter/macos/Frameworks mkdir -p ui/flutter/macos/Frameworks
cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/amd64-lib
cp ui/flutter/lib-arm64/libgopeed.dylib ui/flutter/macos/Frameworks/arm64-lib cp ui/flutter/lib-arm64/libgopeed.dylib ui/flutter/macos/Frameworks/arm64-lib
cp ui/flutter/lib-amd64/host ui/flutter/macos/Frameworks/amd64-host cp ui/flutter/lib-amd64/host ui/flutter/macos/Frameworks/amd64-host
cp ui/flutter/lib-arm64/host ui/flutter/macos/Frameworks/arm64-host cp ui/flutter/lib-arm64/host ui/flutter/macos/Frameworks/arm64-host
cp ui/flutter/lib-amd64/updater ui/flutter/macos/Frameworks/amd64-updater
cp ui/flutter/lib-arm64/updater ui/flutter/macos/Frameworks/arm64-updater
cd ui/flutter/macos/Frameworks cd ui/flutter/macos/Frameworks
lipo -create -output libgopeed.dylib amd64-lib arm64-lib lipo -create -output libgopeed.dylib amd64-lib arm64-lib
lipo -create -output host amd64-host arm64-host lipo -create -output host amd64-host arm64-host
rm -rf amd64-lib arm64-lib amd64-host arm64-host lipo -create -output updater amd64-updater arm64-updater
mv host ../../assets/host/host rm -rf amd64-lib arm64-lib amd64-host arm64-host amd64-updater arm64-updater
mv host ../../assets/exec/host
cd $PROJECT_DIR/ui/flutter cd $PROJECT_DIR/ui/flutter
flutter build macos flutter build macos --dart-define="UPDATE_CHANNEL=macosDmg"
cd build/macos/Build/Products/Release cd build/macos/Build/Products/Release
cat>appdmg.json<<EOF cat>appdmg.json<<EOF
{ {
@@ -225,23 +233,34 @@ jobs:
overwrite: true overwrite: true
build-linux: build-linux:
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }} if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
runs-on: ubuntu-22.04 runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-22.04-arm]
needs: [get-release] needs: [get-release]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-go@v4 - uses: actions/setup-go@v4
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: subosito/flutter-action@v2 - if: matrix.os == 'ubuntu-22.04'
uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
- if: matrix.os == 'ubuntu-22.04-arm'
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: master
- run: | - run: |
sudo apt update -y sudo apt update -y
# flutter build dependencies # flutter build dependencies
sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev
# appimage build dependencies # appimage build dependencies
sudo apt install -y libfuse2 locate sudo apt install -y libfuse2 locate
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" arch=$(uname -m)
wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
chmod +x appimagetool chmod +x appimagetool
sudo mv appimagetool /usr/local/bin/ sudo mv appimagetool /usr/local/bin/
- name: Build - name: Build
@@ -249,13 +268,42 @@ jobs:
VERSION: ${{ needs.get-release.outputs.version }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
go build -ldflags="-w -s" -o ui/flutter/assets/host/host github.com/GopeedLab/gopeed/cmd/host go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
cd ui/flutter cd ui/flutter
cat << EOF > distribute_options.yaml
output: dist/
releases:
- name: linux
jobs:
- name: appimage
package:
platform: linux
target: appimage
build_args:
dart-define:
UPDATE_CHANNEL: linuxAppImage
- name: deb
package:
platform: linux
target: deb
build_args:
dart-define:
UPDATE_CHANNEL: linuxDeb
EOF
dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
flutter_distributor package --platform linux --targets appimage,deb flutter_distributor release --name linux
cd dist/* cd dist/*
mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-x86_64.AppImage
mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-amd64.deb # Get current running architecture, x86_64 -> amd64 and aarch64 -> arm64
ARCH="amd64"
if [[ "${{ matrix.os }}" == *-arm ]]; then
ARCH="arm64"
fi
mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-${ARCH}.AppImage
mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-${ARCH}.deb
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -280,7 +328,8 @@ jobs:
VERSION: ${{ needs.get-release.outputs.version }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
go build -ldflags="-w -s" -o ui/flutter/assets/host/host github.com/GopeedLab/gopeed/cmd/host go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
cd ui/flutter cd ui/flutter
sudo snap install snapcraft --classic sudo snap install snapcraft --classic
@@ -357,7 +406,7 @@ jobs:
set +e set +e
dart pub get dart pub get
set -eux set -eux
flutter build linux --release -v flutter build linux --dart-define="UPDATE_CHANNEL=linuxSnap"
mkdir -p \$CRAFT_PART_INSTALL/bin/ mkdir -p \$CRAFT_PART_INSTALL/bin/
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/ cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
gpu-2404: gpu-2404:
@@ -422,9 +471,13 @@ jobs:
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0 -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0 -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
flutter build apk flutter build apk --dart-define="UPDATE_CHANNEL=androidApk"
flutter build apk --split-per-abi --dart-define="UPDATE_CHANNEL=androidApk"
mkdir dist mkdir dist
cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk dist/Gopeed-v$VERSION-android-armeabi-v7a.apk
cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk dist/Gopeed-v$VERSION-android-arm64-v8a.apk
cp build/app/outputs/flutter-apk/app-x86_64-release.apk dist/Gopeed-v$VERSION-android-x86_64.apk
- name: Upload - name: Upload
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -452,7 +505,7 @@ jobs:
gomobile init gomobile init
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter cd ui/flutter
flutter build ios --no-codesign flutter build ios --no-codesign --dart-define="UPDATE_CHANNEL=iosIpa"
mkdir Payload mkdir Payload
cp -r build/ios/iphoneos/Runner.app Payload cp -r build/ios/iphoneos/Runner.app Payload
zip -r -y Payload.zip Payload/Runner.app zip -r -y Payload.zip Payload/Runner.app
@@ -480,7 +533,6 @@ jobs:
env: env:
VERSION: ${{ needs.get-release.outputs.version }} VERSION: ${{ needs.get-release.outputs.version }}
run: | run: |
VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified
cd ui/flutter cd ui/flutter
flutter build web --no-web-resources-cdn flutter build web --no-web-resources-cdn
cd ../../ cd ../../
@@ -526,7 +578,7 @@ jobs:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.8.18" python-version: "3.8.18"
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v4
with: with:
name: web-dist name: web-dist
path: dist/zip/ path: dist/zip/
@@ -598,7 +650,7 @@ jobs:
- name: Build flutter web - name: Build flutter web
run: | run: |
cd ui/flutter cd ui/flutter
flutter build web --no-web-resources-cdn flutter build web --no-web-resources-cdn --dart-define="UPDATE_CHANNEL=docker"
cd ../../ cd ../../
rm -rf cmd/web/dist rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist cp -r ui/flutter/build/web cmd/web/dist
+4 -3
View File
@@ -53,7 +53,7 @@ jobs:
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- name: Run Unit tests. - name: Run Unit tests.
run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/) -v -covermode=count -coverprofile=coverage.txt
- uses: codecov/codecov-action@v4 - uses: codecov/codecov-action@v4
with: with:
files: ./coverage.txt files: ./coverage.txt
@@ -63,7 +63,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ windows-latest, macos-latest, ubuntu-22.04 ] os: [ windows-latest, macos-latest, ubuntu-22.04, ubuntu-22.04-arm ]
needs: [ test ] needs: [ test ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -73,6 +73,7 @@ jobs:
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
channel: master
- if: runner.os == 'Windows' - if: runner.os == 'Windows'
run: | run: |
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
@@ -164,4 +165,4 @@ jobs:
cd ../../ cd ../../
rm -rf cmd/web/dist rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist cp -r ui/flutter/build/web cmd/web/dist
docker build -t gopeed . docker build -t gopeed .
+132 -82
View File
@@ -13,94 +13,144 @@
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## Introduction ## 🚀 Introduction
Gopeed (full name Go Speed), a high-speed downloader developed by `Golang` + `Flutter`, supports (HTTP, BitTorrent, Magnet) protocol, and supports all platforms. In addition to basic download functions, Gopeed is also a highly customizable downloader that supports implementing more features through integration with [APIs](https://docs.gopeed.com/dev-api.html) or installation and development of [extensions](https://docs.gopeed.com/dev-extension.html). Gopeed (full name Go Speed), a high-speed downloader developed by `Golang` + `Flutter`, supports (HTTP, BitTorrent, Magnet) protocol, and supports all platforms. In addition to basic download functions, Gopeed is also a highly customizable downloader that supports implementing more features through integration with [APIs](https://docs.gopeed.com/dev-api.html) or installation and development of [extensions](https://docs.gopeed.com/dev-extension.html).
Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://docs.gopeed.com) Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://docs.gopeed.com)
## Download ## ⬇️ Download
<table> <table>
<thead>
<tr>
<th colspan="2">Windows</th>
<th>MacOS</th>
<th colspan="4">Linux</th>
<th>Android</th>
<th>iOS</th>
<th>Docker</th>
<th colspan="2">Qnap</th>
<th colspan="8">Web</th>
</tr>
<tr>
<td><code>EXE</code></td>
<td><code>Portable</code></td>
<td><code>DMG</code></td>
<td><code>Flathub</code></td>
<td><code>SNAP</code></td>
<td><code>DEB</code></td>
<td><code>AppImage</code></td>
<td><code>APK</code></td>
<td><code>IPA</code></td>
<td>-</td>
<td colspan="2"><code>QPKG</code></td>
<td colspan="3"><code>Windows</code></td>
<td colspan="2"><code>MacOS</code></td>
<td colspan="3"><code>Linux</code></td>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="2">🪟 Windows</td>
<td><code>EXE</code></td>
<td>amd64</td> <td>amd64</td>
<td>amd64</td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
<td>-</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>amd64</td>
<td>arm64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td> <td><code>Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td> </tr>
<td><a href="https://snapcraft.io/gopeed">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td> <td>🍎 MacOS</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td> <td><code>DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td> <td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Link</a></td> <td rowspan="6">🐧 Linux</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Link</a></td> <td><code>Flathub</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Link</a></td> <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Link</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Link</a></td> <td><code>SNAP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Link</a></td> <td><a href="https://snapcraft.io/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>DEB</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>AppImage</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
</tr>
<tr>
<td rowspan="4">🤖 Android</td>
<td rowspan="4"><code>APK</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
</tr>
<tr>
<td>armeabi-v7a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
</tr>
<tr>
<td>arm64-v8a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
</tr>
<tr>
<td>x86_64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
</tr>
<tr>
<td>📱 iOS</td>
<td><code>IPA</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
</tr>
<tr>
<td>🐳 Docker</td>
<td>-</td>
<td>universal</td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2">💾 Qnap</td>
<td rowspan="2"><code>QPKG</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
</tr>
<tr>
<td rowspan="8">🌐 Web</td>
<td rowspan="3"><code>Windows</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>MacOS</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
</tr>
<tr>
<td rowspan="3"><code>Linux</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
More about installation, please refer to [Installation](https://docs.gopeed.com/install.html) More about installation, please refer to [Installation](https://docs.gopeed.com/install.html)
### Command tool ### 🛠️ Command tool
use `go install`: use `go install`:
@@ -108,40 +158,40 @@ use `go install`:
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
## Browser Extension ## 🔌 Browser Extension
Gopeed also provides a browser extension to take over browser downloads, supporting browsers such as Chrome, Edge, Firefox, etc., please refer to: [https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension) Gopeed also provides a browser extension to take over browser downloads, supporting browsers such as Chrome, Edge, Firefox, etc., please refer to: [https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
## Donate ## 💝 Donate
If you like this project, please consider [donating](https://docs.gopeed.com/donate.html) to support the development of this project, thank you! If you like this project, please consider [donating](https://docs.gopeed.com/donate.html) to support the development of this project, thank you!
## Showcase ## 🖼️ Showcase
![](_docs/img/ui-demo.png) ![](_docs/img/ui-demo.png)
## Development ## 👨‍💻 Development
This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used. This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used.
> The front code is located in the `ui/flutter` directory. > The front code is located in the `ui/flutter` directory.
### Environment ### 🌍 Environment
1. Golang 1.23+ 1. Golang 1.23+
2. Flutter 3.24+ 2. Flutter 3.24+
### Clone ### 📋 Clone
```bash ```bash
git clone [email protected]:GopeedLab/gopeed.git git clone [email protected]:GopeedLab/gopeed.git
``` ```
### Contributing ### 🤝 Contributing
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md)
### Build ### 🏗️ Build
#### Desktop #### Desktop
@@ -214,18 +264,18 @@ cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
``` ```
## Credits ## ❤️ Credits
### Contributors ### 👥 Contributors
<a href="https://github.com/GopeedLab/gopeed/graphs/contributors"> <a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" /> <img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a> </a>
### JetBrains ### 🏢 JetBrains
[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed) [![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)
## License ## 📄 License
[GPLv3](LICENSE) [GPLv3](LICENSE)
+129 -78
View File
@@ -13,123 +13,174 @@
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## はじめに ## 🚀 はじめに
Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。基本的なダウンロード機能に加え、[APIs](https://docs.gopeed.com/dev-api.html)との連動や[拡張機能](https://docs.gopeed.com/dev-extension.html)のインストール・開発による追加機能にも対応した、カスタマイズ性の高いダウンローダーです。 Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet) プロトコルをサポートし、すべてのプラットフォームをサポートします。基本的なダウンロード機能に加え、[APIs](https://docs.gopeed.com/dev-api.html)との連動や[拡張機能](https://docs.gopeed.com/dev-extension.html)のインストール・開発による追加機能にも対応した、カスタマイズ性の高いダウンローダーです。
見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com) 見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://docs.gopeed.com)
## インストール ## ⬇️ インストール
<table> <table>
<thead>
<tr>
<th colspan="2">Windows</th>
<th>MacOS</th>
<th colspan="4">Linux</th>
<th>Android</th>
<th>iOS</th>
<th>Docker</th>
<th colspan="2">Qnap</th>
<th colspan="8">Web</th>
</tr>
<tr>
<td><code>EXE</code></td>
<td><code>Portable</code></td>
<td><code>DMG</code></td>
<td><code>Flathub</code></td>
<td><code>SNAP</code></td>
<td><code>DEB</code></td>
<td><code>AppImage</code></td>
<td><code>APK</code></td>
<td><code>IPA</code></td>
<td>-</td>
<td colspan="2"><code>QPKG</code></td>
<td colspan="3"><code>Windows</code></td>
<td colspan="2"><code>MacOS</code></td>
<td colspan="3"><code>Linux</code></td>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="2">🪟 Windows</td>
<td><code>EXE</code></td>
<td>amd64</td> <td>amd64</td>
<td>amd64</td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
<td>-</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>amd64</td>
<td>arm64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Link</a></td> <td><code>Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Link</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Link</a></td> </tr>
<td><a href="https://snapcraft.io/gopeed">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Link</a></td> <td>🍎 MacOS</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Link</a></td> <td><code>DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Link</a></td> <td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Link</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Link</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Link</a></td> <td rowspan="6">🐧 Linux</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Link</a></td> <td><code>Flathub</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Link</a></td> <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Link</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Link</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Link</a></td> <td><code>SNAP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Link</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Link</a></td> <td><a href="https://snapcraft.io/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>DEB</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>AppImage</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
</tr>
<tr>
<td rowspan="4">🤖 Android</td>
<td rowspan="4"><code>APK</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
</tr>
<tr>
<td>armeabi-v7a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
</tr>
<tr>
<td>arm64-v8a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
</tr>
<tr>
<td>x86_64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
</tr>
<tr>
<td>📱 iOS</td>
<td><code>IPA</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
</tr>
<tr>
<td>🐳 Docker</td>
<td>-</td>
<td>universal</td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2">💾 Qnap</td>
<td rowspan="2"><code>QPKG</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
</tr>
<tr>
<td rowspan="8">🌐 Web</td>
<td rowspan="3"><code>Windows</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>MacOS</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
</tr>
<tr>
<td rowspan="3"><code>Linux</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。 インストールについての詳細は、[インストール](https://docs.gopeed.com/install.html)を参照してください。
## 寄付 ### 🛠️ コマンドツール
## 💝 寄付
もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](https://docs.gopeed.com/donate.html)をご検討ください! もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](https://docs.gopeed.com/donate.html)をご検討ください!
## ショーケース ## 🖼️ ショーケース
![](_docs/img/ui-demo.png) ![](_docs/img/ui-demo.png)
## 開発 ## 👨‍💻 開発
このプロジェクトは二つの部分に分かれており、フロントエンドでは `flutter` を、バックエンドでは `Golang` を使用し、両者は `http` プロトコルで通信する。ユニックスシステムでは `unix socket` を、ウィンドウズシステムでは `tcp` プロトコルを使用します。 このプロジェクトは二つの部分に分かれており、フロントエンドでは `flutter` を、バックエンドでは `Golang` を使用し、両者は `http` プロトコルで通信する。ユニックスシステムでは `unix socket` を、ウィンドウズシステムでは `tcp` プロトコルを使用します。
> フロントコードは `ui/flutter` ディレクトリにあります。 > フロントコードは `ui/flutter` ディレクトリにあります。
### 環境 ### 🌍 環境
1. Go 言語 1.23+ 1. Go 言語 1.23+
2. Flutter 3.24+ 2. Flutter 3.24+
### クローン ### 📋 クローン
```bash ```bash
git clone [email protected]:GopeedLab/gopeed.git git clone [email protected]:GopeedLab/gopeed.git
``` ```
### コントリビュート ### 🤝 コントリビュート
[CONTRIBUTING.md](/CONTRIBUTING_ja-JP.md) をご参照ください [CONTRIBUTING.md](/CONTRIBUTING_ja-JP.md) をご参照ください
### ビルド ### 🏗️ ビルド
#### デスクトップ #### デスクトップ
@@ -202,7 +253,7 @@ cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
``` ```
## 感謝 ## ❤️ 感謝
### コントリビューター ### コントリビューター
+128 -79
View File
@@ -13,94 +13,143 @@
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## Giới thiệu ## 🚀 Giới thiệu
Gopeed (tên đầy đủ Go Speed), một công cụ tải xuống tốc độ cao được phát triển bởi `Golang` + `Flutter`, hỗ trợ giao thức (HTTP, BitTorrent, Magnet) và hỗ trợ tất cả các nền tảng. Ngoài các chức năng tải xuống cơ bản, Gopeed còn là một công cụ tải xuống có thể tùy chỉnh cao cho phép triển khai thêm tính năng thông qua việc tích hợp với [APIs](https://docs.gopeed.com/dev-api.html) hoặc cài đặt và phát triển các [tiện ích mở rộng](https://docs.gopeed.com/dev-extension.html). Gopeed (tên đầy đủ Go Speed), một công cụ tải xuống tốc độ cao được phát triển bởi `Golang` + `Flutter`, hỗ trợ giao thức (HTTP, BitTorrent, Magnet) và hỗ trợ tất cả các nền tảng. Ngoài các chức năng tải xuống cơ bản, Gopeed còn là một công cụ tải xuống có thể tùy chỉnh cao cho phép triển khai thêm tính năng thông qua việc tích hợp với [APIs](https://docs.gopeed.com/dev-api.html) hoặc cài đặt và phát triển các [tiện ích mở rộng](https://docs.gopeed.com/dev-extension.html).
Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu chính thức](https://docs.gopeed.com) Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu chính thức](https://docs.gopeed.com)
## Tải về ## ⬇️ Tải về
<table> <table>
<thead>
<tr>
<th colspan="2">Windows</th>
<th>MacOS</th>
<th colspan="4">Linux</th>
<th>Android</th>
<th>iOS</th>
<th>Docker</th>
<th colspan="2">Qnap</th>
<th colspan="8">Web</th>
</tr>
<tr>
<td><code>EXE</code></td>
<td><code>Portable</code></td>
<td><code>DMG</code></td>
<td><code>Flathub</code></td>
<td><code>SNAP</code></td>
<td><code>DEB</code></td>
<td><code>AppImage</code></td>
<td><code>APK</code></td>
<td><code>IPA</code></td>
<td>-</td>
<td colspan="2"><code>QPKG</code></td>
<td colspan="3"><code>Windows</code></td>
<td colspan="2"><code>MacOS</code></td>
<td colspan="3"><code>Linux</code></td>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="2">🪟 Windows</td>
<td><code>EXE</code></td>
<td>amd64</td> <td>amd64</td>
<td>amd64</td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
<td>-</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>amd64</td>
<td>arm64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">Liên kết</a></td> <td><code>Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">Liên kết</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">Liên kết</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">Liên kết</a></td> </tr>
<td><a href="https://snapcraft.io/gopeed">Liên kết</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">Liên kết</a></td> <td>🍎 MacOS</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">Liên kết</a></td> <td><code>DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">Liên kết</a></td> <td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">Liên kết</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">Liên kết</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">Liên kết</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">Liên kết</a></td> <td rowspan="6">🐧 Linux</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">Liên kết</a></td> <td><code>Flathub</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">Liên kết</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">Liên kết</a></td> <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">Liên kết</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">Liên kết</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">Liên kết</a></td> <td><code>SNAP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">Liên kết</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">Liên kết</a></td> <td><a href="https://snapcraft.io/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>DEB</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>AppImage</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
</tr>
<tr>
<td rowspan="4">🤖 Android</td>
<td rowspan="4"><code>APK</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
</tr>
<tr>
<td>armeabi-v7a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
</tr>
<tr>
<td>arm64-v8a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
</tr>
<tr>
<td>x86_64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
</tr>
<tr>
<td>📱 iOS</td>
<td><code>IPA</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
</tr>
<tr>
<td>🐳 Docker</td>
<td>-</td>
<td>universal</td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2">💾 Qnap</td>
<td rowspan="2"><code>QPKG</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
</tr>
<tr>
<td rowspan="8">🌐 Web</td>
<td rowspan="3"><code>Windows</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>MacOS</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
</tr>
<tr>
<td rowspan="3"><code>Linux</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html) Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://docs.gopeed.com/install.html)
### Command tool ### 🛠️ Công cụ lệnh
Sử dụng `go install`: Sử dụng `go install`:
@@ -108,36 +157,36 @@ Sử dụng `go install`:
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
## Quyên góp ## 💝 Quyên góp
Nếu bạn thích dự án này, xin vui lòng xem xét [quyên góp](https://docs.gopeed.com/donate.html) để hỗ trợ phát triển dự án này, cảm ơn bạn! Nếu bạn thích dự án này, xin vui lòng xem xét [quyên góp](https://docs.gopeed.com/donate.html) để hỗ trợ phát triển dự án này, cảm ơn bạn!
## Trưng bày ## 🖼️ Trưng bày
![](_docs/img/ui-demo.png) ![](_docs/img/ui-demo.png)
## Development ## 👨‍💻 Development
Dự án này được chia thành hai phần, phần giao diện sử dụng `flutter`, phần backend sử dụng `Golang`, và hai phía giao tiếp thông qua giao thức `http`. Trên hệ thống unix, sử dụng `unix socket`, và trên hệ thống windows, sử dụng giao thức `tcp`. Dự án này được chia thành hai phần, phần giao diện sử dụng `flutter`, phần backend sử dụng `Golang`, và hai phía giao tiếp thông qua giao thức `http`. Trên hệ thống unix, sử dụng `unix socket`, và trên hệ thống windows, sử dụng giao thức `tcp`.
> Mã giao diện nằm trong thư mục `ui/flutter`. > Mã giao diện nằm trong thư mục `ui/flutter`.
### Environment ### 🌍 Environment
1. Golang 1.23+ 1. Golang 1.23+
2. Flutter 3.24+ 2. Flutter 3.24+
### Clone ### 📋 Clone
```bash ```bash
git clone [email protected]:GopeedLab/gopeed.git git clone [email protected]:GopeedLab/gopeed.git
``` ```
### Đóng góp ### 🤝 Đóng góp
Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md) Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md)
### Xây dựng ### 🏗️ Xây dựng
#### Desktop #### Desktop
@@ -210,7 +259,7 @@ cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
``` ```
## Tín dụng ## ❤️ Tín dụng
### Người đóng góp ### Người đóng góp
+129 -80
View File
@@ -13,94 +13,143 @@
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## 介绍 ## 🚀 介绍
Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不是狗屁下载器!),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://docs.gopeed.com/zh/dev-api.html)或者安装和开发[扩展](https://docs.gopeed.com/zh/dev-extension.html)来实现更多的功能。 Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不是狗屁下载器!),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://docs.gopeed.com/zh/dev-api.html)或者安装和开发[扩展](https://docs.gopeed.com/zh/dev-extension.html)来实现更多的功能。
访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [官方文档](https://docs.gopeed.com/zh/) 访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [官方文档](https://docs.gopeed.com/zh/)
## 下载 ## ⬇️ 下载
<table> <table>
<thead>
<tr>
<th colspan="2">Windows</th>
<th>MacOS</th>
<th colspan="4">Linux</th>
<th>Android</th>
<th>iOS</th>
<th>Docker</th>
<th colspan="2">Qnap</th>
<th colspan="8">Web</th>
</tr>
<tr>
<td><code>EXE</code></td>
<td><code>Portable</code></td>
<td><code>DMG</code></td>
<td><code>Flathub</code></td>
<td><code>SNAP</code></td>
<td><code>DEB</code></td>
<td><code>AppImage</code></td>
<td><code>APK</code></td>
<td><code>IPA</code></td>
<td>-</td>
<td colspan="2"><code>QPKG</code></td>
<td colspan="3"><code>Windows</code></td>
<td colspan="2"><code>MacOS</code></td>
<td colspan="3"><code>Linux</code></td>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="2">🪟 Windows</td>
<td><code>EXE</code></td>
<td>amd64</td> <td>amd64</td>
<td>amd64</td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
<td>-</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>amd64</td>
<td>arm64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td> <td><code>Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td> </tr>
<td><a href="https://snapcraft.io/gopeed">前往</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td> <td>🍎 MacOS</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td> <td><code>DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td> <td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">前往</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">前往</a></td> <td rowspan="6">🐧 Linux</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">前往</a></td> <td><code>Flathub</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">前往</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">前往</a></td> <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">前往</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">前往</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">前往</a></td> <td><code>SNAP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">前往</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">前往</a></td> <td><a href="https://snapcraft.io/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>DEB</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>AppImage</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
</tr>
<tr>
<td rowspan="4">🤖 Android</td>
<td rowspan="4"><code>APK</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
</tr>
<tr>
<td>armeabi-v7a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
</tr>
<tr>
<td>arm64-v8a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
</tr>
<tr>
<td>x86_64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
</tr>
<tr>
<td>📱 iOS</td>
<td><code>IPA</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
</tr>
<tr>
<td>🐳 Docker</td>
<td>-</td>
<td>universal</td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2">💾 Qnap</td>
<td rowspan="2"><code>QPKG</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
</tr>
<tr>
<td rowspan="8">🌐 Web</td>
<td rowspan="3"><code>Windows</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>MacOS</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
</tr>
<tr>
<td rowspan="3"><code>Linux</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html) 更多关于安装的内容请参考[安装文档](https://docs.gopeed.com/zh/install.html)
### 命令行工具 ### 🛠️ 命令行工具
使用`go install`安装: 使用`go install`安装:
@@ -108,40 +157,40 @@ Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
## 浏览器扩展 ## 🔌 浏览器扩展
Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension) Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)
## 赞助 ## 💝 赞助
如果觉得项目对你有帮助,请考虑[赞助](https://docs.gopeed.com/zh/donate)以支持这个项目的发展,非常感谢! 如果觉得项目对你有帮助,请考虑[赞助](https://docs.gopeed.com/zh/donate)以支持这个项目的发展,非常感谢!
## 界面展示 ## 🖼️ 界面展示
![](_docs/img/ui-demo.png) ![](_docs/img/ui-demo.png)
## 开发 ## 👨‍💻 开发
本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。 本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。
> 前端代码位于`ui/flutter`目录下。 > 前端代码位于`ui/flutter`目录下。
### 环境要求 ### 🌍 环境要求
1. Golang 1.23+ 1. Golang 1.23+
2. Flutter 3.24+ 2. Flutter 3.24+
### 克隆项目 ### 📋 克隆项目
```bash ```bash
git clone [email protected]:GopeedLab/gopeed.git git clone [email protected]:GopeedLab/gopeed.git
``` ```
### 贡献代码 ### 🤝 贡献代码
请参考[贡献指南](CONTRIBUTING_zh-CN.md) 请参考[贡献指南](CONTRIBUTING_zh-CN.md)
### 编译 ### 🏗️ 编译
#### 桌面端 #### 桌面端
@@ -214,7 +263,7 @@ cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
``` ```
## 感谢 ## ❤️ 感谢
### 贡献者 ### 贡献者
+128 -78
View File
@@ -13,94 +13,144 @@
[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md) [English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)
## 簡介 ## 🚀 簡介
Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速下載軟體,支援(HTTP、BitTorrent、Magnet)協定,同時支援所有的平台。 Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速下載軟體,支援(HTTP、BitTorrent、Magnet)協定,同時支援所有的平台。
前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/) 前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://docs.gopeed.com/zh/)
## 下載 ## ⬇️ 下載
<table> <table>
<thead>
<tr>
<th colspan="2">Windows</th>
<th>MacOS</th>
<th colspan="4">Linux</th>
<th>Android</th>
<th>iOS</th>
<th>Docker</th>
<th colspan="2">Qnap</th>
<th colspan="8">Web</th>
</tr>
<tr>
<td><code>EXE</code></td>
<td><code>Portable</code></td>
<td><code>DMG</code></td>
<td><code>Flathub</code></td>
<td><code>SNAP</code></td>
<td><code>DEB</code></td>
<td><code>AppImage</code></td>
<td><code>APK</code></td>
<td><code>IPA</code></td>
<td>-</td>
<td colspan="2"><code>QPKG</code></td>
<td colspan="3"><code>Windows</code></td>
<td colspan="2"><code>MacOS</code></td>
<td colspan="3"><code>Linux</code></td>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="2">🪟 Windows</td>
<td><code>EXE</code></td>
<td>amd64</td> <td>amd64</td>
<td>amd64</td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
<td>-</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>amd64</td>
<td>arm64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>amd64</td>
<td>386</td>
</tr> </tr>
<tr> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">前往</a></td> <td><code>Portable</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">前往</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">前往</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
<td><a href="https://flathub.org/apps/com.gopeed.Gopeed">前往</a></td> </tr>
<td><a href="https://snapcraft.io/gopeed">前往</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">前往</a></td> <td>🍎 MacOS</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-x86_64.AppImage">前往</a></td> <td><code>DMG</code></td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">前往</a></td> <td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">前往</a></td> <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">前往</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">前往</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">前往</a></td> <td rowspan="6">🐧 Linux</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">前往</a></td> <td><code>Flathub</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">前往</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">前往</a></td> <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">前往</a></td> </tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">前往</a></td> <tr>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">前往</a></td> <td><code>SNAP</code></td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">前往</a></td> <td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">前往</a></td> <td><a href="https://snapcraft.io/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>DEB</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>AppImage</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
</tr>
<tr>
<td rowspan="4">🤖 Android</td>
<td rowspan="4"><code>APK</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
</tr>
<tr>
<td>armeabi-v7a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
</tr>
<tr>
<td>arm64-v8a</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
</tr>
<tr>
<td>x86_64</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
</tr>
<tr>
<td>📱 iOS</td>
<td><code>IPA</code></td>
<td>universal</td>
<td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
</tr>
<tr>
<td>🐳 Docker</td>
<td>-</td>
<td>universal</td>
<td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
</tr>
<tr>
<td rowspan="2">💾 Qnap</td>
<td rowspan="2"><code>QPKG</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
</tr>
<tr>
<td rowspan="8">🌐 Web</td>
<td rowspan="3"><code>Windows</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
</tr>
<tr>
<td rowspan="2"><code>MacOS</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
</tr>
<tr>
<td rowspan="3"><code>Linux</code></td>
<td>amd64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
</tr>
<tr>
<td>arm64</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
</tr>
<tr>
<td>386</td>
<td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html) 更多關於安裝的內容請參考[安裝文檔](https://docs.gopeed.com/zh/install.html)
### 使用 CLI 安裝 ### 🛠️ 使用 CLI 安裝
使用`go install`安裝: 使用`go install`安裝:
@@ -108,36 +158,36 @@ Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
``` ```
## 贊助 ## 💝 贊助
如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝! 如果你認為該項目對你有所幫助,請考慮[贊助](https://docs.gopeed.com/zh/donate)以支持該項目的持續發展,謝謝!
## 軟體介面 ## 🖼️ 軟體介面
![](_docs/img/ui-demo.png) ![](_docs/img/ui-demo.png)
## 開發 ## 👨‍💻 開發
該項目分為前端與後端,前端使用`flutter`編寫,後端使用`Golang`編寫,兩邊通過`http`協定進行通訊,在 unix 系統下,則使用`unix socket`,在 windows 系統下,則使用`tcp`協定。 該項目分為前端與後端,前端使用`flutter`編寫,後端使用`Golang`編寫,兩邊通過`http`協定進行通訊,在 unix 系統下,則使用`unix socket`,在 windows 系統下,則使用`tcp`協定。
> 前端代碼位於`ui/flutter`目錄內。 > 前端代碼位於`ui/flutter`目錄內。
### 開發環境 ### 🌍 開發環境
1. Golang 1.23+ 1. Golang 1.23+
2. Flutter 3.24+ 2. Flutter 3.24+
### 克隆項目 ### 📋 克隆項目
```bash ```bash
git clone [email protected]:GopeedLab/gopeed.git git clone [email protected]:GopeedLab/gopeed.git
``` ```
### 協助開發 ### 🤝 協助開發
請參考[協助指南](CONTRIBUTING_zh-TW.md) 請參考[協助指南](CONTRIBUTING_zh-TW.md)
### 編譯 ### 🏗️ 編譯
#### 桌面端 #### 桌面端
@@ -210,7 +260,7 @@ cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
``` ```
## 感謝 ## ❤️ 感謝
### 貢獻者 ### 貢獻者
+2
View File
@@ -55,6 +55,8 @@ var apiMap = map[string]func(params json.RawMessage) (data any, err error){
}, },
} }
// go build -ldflags="-s -w" -o ui/flutter/assets/exec/ github.com/GopeedLab/gopeed/cmd/host
func main() { func main() {
for { for {
// Read message length (first 4 bytes) // Read message length (first 4 bytes)
+116
View File
@@ -0,0 +1,116 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"syscall"
"time"
"github.com/pkg/browser"
"github.com/pkg/errors"
)
// go build -ldflags="-s -w" -o ui/flutter/assets/exec/ github.com/GopeedLab/gopeed/cmd/updater
func main() {
pid := flag.Int("pid", 0, "PID of the process to update")
updateChannel := flag.String("channel", "", "Update channel")
packagePath := flag.String("asset", "", "Path to the package asset")
logPath := flag.String("log", "", "Log file path")
flag.Parse()
if *pid == 0 {
log.Println("Invalid PID")
os.Exit(1)
}
if *updateChannel == "" {
log.Println("Invalid update channel")
os.Exit(1)
}
if *logPath != "" {
logFile, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err == nil {
defer logFile.Close()
log.SetOutput(logFile)
}
}
var (
restart bool
err error
)
if restart, err = update(*pid, *updateChannel, *packagePath); err != nil {
log.Printf("Update failed: %v\n", err)
os.Exit(1)
}
// Restart the application
if restart {
browser.OpenURL("gopeed:///")
}
// Delete package asset
if *packagePath != "" {
os.Remove(*packagePath)
}
os.Exit(0)
}
func update(pid int, updateChannel, packagePath string) (restart bool, err error) {
killSignalChan := make(chan any, 1)
go func() {
<-killSignalChan
if err = killProcess(pid); err != nil {
log.Printf("Failed to kill process: %v\n", err)
}
if err = waitForProcessExit(pid); err != nil {
log.Printf("Failed to wait for process exit: %v\n", err)
}
}()
appDir := filepath.Dir(os.Args[0])
log.Printf("Updating process updateChannel=%s packagePath=%s appDir=%s\n", updateChannel, packagePath, appDir)
if restart, err = install(killSignalChan, updateChannel, packagePath, appDir); err != nil {
return false, errors.Wrap(err, "failed to install package")
}
return
}
func waitForProcessExit(pid int) error {
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
process, err := os.FindProcess(pid)
if err != nil {
// On some systems, error is returned if process doesn't exist
return nil
}
// Send null signal to test if process exists
err = process.Signal(syscall.Signal(0))
if err != nil {
// If error occurs, the process no longer exists
return nil
}
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("process %d still running after timeout", pid)
}
func killProcess(pid int) error {
process, err := os.FindProcess(pid)
if err != nil {
return err
}
return process.Kill()
}
+68
View File
@@ -0,0 +1,68 @@
//go:build darwin
package main
import (
"fmt"
"os/exec"
"path/filepath"
"strings"
)
func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
return true, installByDmg(killSignalChan, packagePath, destDir)
}
// installByDmg handles macOS dmg package installation
func installByDmg(killSignalChan chan<- any, packagePath, destDir string) error {
// /Applications/Gopeed.app/Contents/MacOS -> /Applications
appPath := getParentDir(getParentDir(getParentDir(destDir)))
output, err := exec.Command("hdiutil", "attach", packagePath, "-nobrowse").Output()
if err != nil {
return err
}
mountPoint := ""
for _, line := range strings.Split(string(output), "\n") {
if strings.Contains(line, "/Volumes/") {
fields := strings.Fields(line)
if len(fields) > 2 {
mountPoint = fields[len(fields)-1]
break
}
}
}
if mountPoint == "" {
return fmt.Errorf("failed to get mount point")
}
// Detach the mounted DMG
defer exec.Command("hdiutil", "detach", mountPoint, "-quiet").Run()
matches, err := filepath.Glob(filepath.Join(mountPoint, "*.app"))
if err != nil {
return err
}
if len(matches) == 0 {
return fmt.Errorf("no .app found in dmg")
}
killSignalChan <- nil
// Copy the new app to the destination
// cp -Rf /Volumes/GoPeed/GoPeed.app /Applications
if err := exec.Command("cp", "-Rf", matches[0], appPath).Run(); err != nil {
return err
}
return nil
}
// Get parent directory safely, handling trailing separators
func getParentDir(path string) string {
// Remove trailing separators if they exist
path = strings.TrimRight(path, string(filepath.Separator))
// Now get the parent directory
return filepath.Dir(path)
}
+72
View File
@@ -0,0 +1,72 @@
//go:build linux
package main
import (
"fmt"
"os/exec"
)
func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
killSignalChan <- nil
switch updateChannel {
case "linuxDeb":
return true, installByDeb(packagePath)
case "linuxFlathub":
return true, installByFlathub()
case "linuxSnap":
return true, installBySnap()
default:
return false, fmt.Errorf("unsupported update channel for Linux: %s", updateChannel)
}
}
// executeInTerminal tries to execute a command in one of several common terminal emulators
func executeInTerminal(command string) error {
terminals := []string{
"gnome-terminal", // GNOME
"konsole", // KDE
"xfce4-terminal", // XFCE
"xterm", // X11
}
command = fmt.Sprintf(`echo "Starting update..." && echo "[CMD] %s" && %s`, command, command)
for _, term := range terminals {
if _, err := exec.LookPath(term); err == nil {
var cmd *exec.Cmd
switch term {
case "gnome-terminal", "xfce4-terminal":
cmd = exec.Command(term, "--", "bash", "-c", command)
case "konsole":
cmd = exec.Command(term, "-e", "bash", "-c", command)
case "xterm":
cmd = exec.Command(term, "-e", command)
}
if err := cmd.Start(); err == nil {
return nil
}
}
}
return fmt.Errorf("no suitable terminal emulator found. Please install gnome-terminal, konsole, xfce4-terminal or xterm")
}
// installByDeb installs the .deb package
func installByDeb(packagePath string) error {
command := fmt.Sprintf(`sudo dpkg -i "%s"`, packagePath)
return executeInTerminal(command)
}
// installByFlathub updates the application via Flathub
func installByFlathub() error {
command := "flatpak update com.gopeed.Gopeed -y"
return executeInTerminal(command)
}
// installBySnap updates the application via Snap
func installBySnap() error {
command := "sudo snap refresh gopeed"
return executeInTerminal(command)
}
+134
View File
@@ -0,0 +1,134 @@
//go:build windows
package main
import (
"archive/zip"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
switch updateChannel {
case "windowsInstaller":
return false, installByInstaller(killSignalChan, packagePath, destDir)
default:
return true, installByPortable(killSignalChan, packagePath, destDir)
}
}
// installByInstaller extracts the installer from the zip file and runs it
func installByInstaller(killSignalChan chan<- any, packagePath, destDir string) error {
// Create a temp directory for extraction
tempDir, err := os.MkdirTemp("", "gopeed_update")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
// Extract the zip file
reader, err := zip.OpenReader(packagePath)
if err != nil {
return err
}
defer reader.Close()
// Find the installer file
var installerPath string
for _, file := range reader.File {
if file.FileInfo().IsDir() {
continue
}
// Extract file
path := filepath.Join(tempDir, file.Name)
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
}
dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
srcFile, err := file.Open()
if err != nil {
dstFile.Close()
return err
}
_, err = io.Copy(dstFile, srcFile)
srcFile.Close()
dstFile.Close()
if err != nil {
return err
}
// If this is likely an installer (.exe, .msi), save its path
ext := strings.ToLower(filepath.Ext(file.Name))
if ext == ".exe" || ext == ".msi" {
installerPath = path
}
}
if installerPath == "" {
return fmt.Errorf("no installer found in the update package")
}
// Run the installer
cmd := exec.Command(installerPath)
if err := cmd.Start(); err != nil {
return err
}
killSignalChan <- nil
return nil
}
// installByPortable extracts the portable version to the destination directory
func installByPortable(killSignalChan chan<- any, packagePath, destDir string) error {
killSignalChan <- nil
reader, err := zip.OpenReader(packagePath)
if err != nil {
return err
}
defer reader.Close()
for _, file := range reader.File {
path := filepath.Join(destDir, file.Name)
if file.FileInfo().IsDir() {
os.MkdirAll(path, file.Mode())
continue
}
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
}
dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
srcFile, err := file.Open()
if err != nil {
dstFile.Close()
return err
}
_, err = io.Copy(dstFile, srcFile)
srcFile.Close()
dstFile.Close()
if err != nil {
return err
}
}
return nil
}
+54 -21
View File
@@ -47,9 +47,15 @@ type chunk struct {
End int64 End int64
Downloaded int64 Downloaded int64
failed bool
retryTimes int retryTimes int
} }
// get remain to download bytes
func (c *chunk) remain() int64 {
return c.End - c.Begin + 1 - c.Downloaded
}
func newChunk(begin int64, end int64) *chunk { func newChunk(begin int64, end int64) *chunk {
return &chunk{ return &chunk{
Begin: begin, Begin: begin,
@@ -147,12 +153,23 @@ func (f *Fetcher) Resolve(req *base.Request) error {
_, params, _ := mime.ParseMediaType(contentDisposition) _, params, _ := mime.ParseMediaType(contentDisposition)
filename := params["filename"] filename := params["filename"]
if filename != "" { if filename != "" {
file.Name = filename // Check if the filename is MIME encoded-word
if strings.HasPrefix(filename, "=?") {
decoder := new(mime.WordDecoder)
filename = strings.Replace(filename, "UTF8", "UTF-8", 1)
file.Name, _ = decoder.Decode(filename)
} else {
file.Name = filename
}
} }
} }
// get file filePath by URL // get file filePath by URL
if file.Name == "" { if file.Name == "" {
file.Name = path.Base(httpReq.URL.Path) file.Name = path.Base(httpReq.URL.Path)
// Url decode
if file.Name != "" {
file.Name, _ = url.QueryUnescape(file.Name)
}
} }
// unknown file filePath // unknown file filePath
if file.Name == "" || file.Name == "/" || file.Name == "." { if file.Name == "" || file.Name == "/" || file.Name == "." {
@@ -254,10 +271,6 @@ func (f *Fetcher) Wait() (err error) {
return <-f.doneCh return <-f.doneCh
} }
type fetchResult struct {
err error
}
func (f *Fetcher) fetch() { func (f *Fetcher) fetch() {
var ctx context.Context var ctx context.Context
ctx, f.cancel = context.WithCancel(context.Background()) ctx, f.cancel = context.WithCancel(context.Background())
@@ -301,22 +314,35 @@ func (f *Fetcher) fetch() {
func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) { func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
chunk := f.chunks[index] chunk := f.chunks[index]
chunk.failed = false
chunk.retryTimes = 0 chunk.retryTimes = 0
var ( var (
client = f.buildClient() client = f.buildClient()
buf = make([]byte, 8192) buf = make([]byte, 8192)
maxRetries = 3
) )
// retry until all remain chunks failed // retry until all remain chunks failed
for { for {
// if chunk is completed, return // if chunk is completed, return
if f.meta.Res.Range && chunk.Downloaded >= chunk.End-chunk.Begin+1 { if f.meta.Res.Range && chunk.remain() <= 0 {
return nil return nil
} }
// if all chunks failed, return
if chunk.retryTimes >= maxRetries { if chunk.failed {
return allFailed := true
for _, c := range f.chunks {
if !c.failed {
allFailed = false
break
}
}
if allFailed {
if chunk.retryTimes >= 3 {
return
} else {
chunk.retryTimes++
}
}
} }
var ( var (
@@ -343,15 +369,30 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
err = NewRequestError(resp.StatusCode, resp.Status) err = NewRequestError(resp.StatusCode, resp.Status)
return err return err
} }
chunk.failed = false
reader := NewTimeoutReader(resp.Body, readTimeout) reader := NewTimeoutReader(resp.Body, readTimeout)
for { for {
n, err := reader.Read(buf) n, err := reader.Read(buf)
if n > 0 { if n > 0 {
finished := false
if f.meta.Res.Range {
remain := chunk.remain()
// If downloaded bytes exceed the remain bytes, only write remain bytes
if remain < int64(n) {
n = int(remain)
finished = true
}
}
_, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded) _, err := f.file.WriteAt(buf[:n], chunk.Begin+chunk.Downloaded)
if err != nil { if err != nil {
return err return err
} }
chunk.Downloaded += int64(n) chunk.Downloaded += int64(n)
if finished {
return nil
}
} }
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
@@ -367,18 +408,10 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
return return
} }
// retry request after 1 second // retry request after 1 second
chunk.retryTimes = chunk.retryTimes + 1 chunk.failed = true
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
// if chunk is completed, reset other not complete chunks retry times
if err == nil {
for _, c := range f.chunks {
if c != chunk && c.retryTimes > 0 {
c.retryTimes = 0
}
}
}
break break
} }
return return
+28
View File
@@ -37,6 +37,26 @@ func TestFetcher_Resolve(t *testing.T) {
}, },
}, },
}, t) }, t)
testResolve(test.StartTestCustomServer, "encoded-word", &base.Resource{
Size: test.BuildSize,
Range: false,
Files: []*base.FileInfo{
{
Name: "测试.zip",
Size: test.BuildSize,
},
},
}, t)
testResolve(test.StartTestCustomServer, "%E6%B5%8B%E8%AF%95.zip", &base.Resource{
Size: 0,
Range: false,
Files: []*base.FileInfo{
{
Name: "测试.zip",
Size: 0,
},
},
}, t)
testResolve(test.StartTestCustomServer, test.BuildName, &base.Resource{ testResolve(test.StartTestCustomServer, test.BuildName, &base.Resource{
Size: 0, Size: 0,
Range: false, Range: false,
@@ -156,6 +176,14 @@ func TestFetcher_DownloadResponseBodyReadTimeout(t *testing.T) {
downloadError(listener, 4, t) downloadError(listener, 4, t)
} }
func TestFetcher_DownloadOnBugFileServer(t *testing.T) {
listener := test.StartTestRangeBugServer()
defer listener.Close()
downloadNormal(listener, 1, t)
downloadNormal(listener, 4, t)
}
func TestFetcher_DownloadResume(t *testing.T) { func TestFetcher_DownloadResume(t *testing.T) {
listener := test.StartTestFileServer() listener := test.StartTestFileServer()
defer listener.Close() defer listener.Close()
+106 -57
View File
@@ -83,6 +83,25 @@ func StartTestCustomServer() net.Listener {
defer file.Close() defer file.Close()
io.Copy(writer, file) io.Copy(writer, file)
}) })
mux.HandleFunc("/encoded-word", func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Disposition", "attachment; filename=\"=?UTF8?B?5rWL6K+VLnppcA==?=\"")
writer.Header().Set("Content-Type", "application/octet-stream")
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
file, err := os.Open(BuildFile)
if err != nil {
panic(err)
}
defer file.Close()
io.Copy(writer, file)
})
mux.HandleFunc("/%E6%B5%8B%E8%AF%95.zip", func(writer http.ResponseWriter, request *http.Request) {
file, err := os.Open(BuildFile)
if err != nil {
panic(err)
}
defer file.Close()
io.Copy(writer, file)
})
return mux return mux
}) })
} }
@@ -161,68 +180,98 @@ func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
writer.WriteHeader(403) writer.WriteHeader(403)
return return
} }
rangeFileHandle(
r := request.Header.Get("Range") writer,
if r == "" { request,
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize)) nil,
writer.WriteHeader(200) func(file *os.File, n int64) {
(writer.(http.Flusher)).Flush() slowCopyN(sl, writer, file, n, delay)
},
file, err := os.Open(BuildFile) )
if err != nil {
panic(err)
}
defer file.Close()
slowCopy(sl, writer, file, delay)
} else {
// split range
s := strings.Split(r, "=")
if len(s) != 2 {
writer.WriteHeader(400)
return
}
s = strings.Split(s[1], "-")
if len(s) != 2 {
writer.WriteHeader(400)
return
}
start, err := strconv.ParseInt(s[0], 10, 64)
if err != nil {
writer.WriteHeader(400)
return
}
end, err := strconv.ParseInt(s[1], 10, 64)
if err != nil {
writer.WriteHeader(400)
return
}
if start < 0 || end < 0 || start > end {
writer.WriteHeader(400)
return
}
if end >= BuildSize {
end = BuildSize - 1
}
writer.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
writer.Header().Set("Accept-Ranges", "bytes")
writer.WriteHeader(206)
(writer.(http.Flusher)).Flush()
file, err := os.Open(BuildFile)
if err != nil {
writer.WriteHeader(500)
return
}
defer file.Close()
file.Seek(start, 0)
slowCopyN(sl, writer, file, end-start+1, delay)
}
}) })
return mux return mux
}) })
} }
// StartTestRangeBugServer simulate bug server:
// Don't follow Range request rules, always return more data than range, e.g. Range: bytes=0-100, return 150 bytes
func StartTestRangeBugServer() net.Listener {
return startTestServer(func(sl *shutdownListener) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/"+BuildName, func(writer http.ResponseWriter, request *http.Request) {
rangeFileHandle(
writer,
request,
func(end int64) int64 {
var bugEnd = end
if end != 0 {
bugEnd = end + 50
if bugEnd >= BuildSize {
bugEnd = end
}
}
return bugEnd
},
func(file *os.File, n int64) {
io.CopyN(writer, file, n)
},
)
})
return mux
})
}
func rangeFileHandle(writer http.ResponseWriter, request *http.Request, modifyEnd func(end int64) int64, iocpN func(file *os.File, n int64)) {
r := request.Header.Get("Range")
// split range
s := strings.Split(r, "=")
if len(s) != 2 {
writer.WriteHeader(400)
return
}
s = strings.Split(s[1], "-")
if len(s) != 2 {
writer.WriteHeader(400)
return
}
start, err := strconv.ParseInt(s[0], 10, 64)
if err != nil {
writer.WriteHeader(400)
return
}
end, err := strconv.ParseInt(s[1], 10, 64)
if err != nil {
writer.WriteHeader(400)
return
}
if start < 0 || end < 0 || start > end {
writer.WriteHeader(400)
return
}
if end >= BuildSize {
end = BuildSize - 1
}
if modifyEnd != nil {
end = modifyEnd(end)
}
writer.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, BuildSize))
writer.Header().Set("Accept-Ranges", "bytes")
writer.WriteHeader(206)
(writer.(http.Flusher)).Flush()
file, err := os.Open(BuildFile)
if err != nil {
writer.WriteHeader(500)
return
}
defer file.Close()
file.Seek(start, 0)
iocpN(file, end-start+1)
}
// slowCopyN copies n bytes from src to dst, speed limit is bytes per second // slowCopyN copies n bytes from src to dst, speed limit is bytes per second
func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (written int64, err error) { func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay int64) (written int64, err error) {
buf := make([]byte, 32*1024) buf := make([]byte, 32*1024)
+2 -2
View File
@@ -208,7 +208,7 @@ func (d *Downloader) Setup() error {
if task.Status == base.DownloadStatusRunning { if task.Status == base.DownloadStatusRunning {
downloadDataChanged = current != task.Progress.Downloaded downloadDataChanged = current != task.Progress.Downloaded
task.Progress.Used = task.timer.Used() task.Progress.Used = task.timer.Used()
task.Progress.Speed = task.calcSpeed(task.speedArr, current-task.Progress.Downloaded, tick) task.Progress.Speed = task.updateSpeed(current-task.Progress.Downloaded, tick)
task.Progress.Downloaded = current task.Progress.Downloaded = current
} }
@@ -217,7 +217,7 @@ func (d *Downloader) Setup() error {
uploader := task.fetcher.(fetcher.Uploader) uploader := task.fetcher.(fetcher.Uploader)
currentUploaded := uploader.UploadedBytes() currentUploaded := uploader.UploadedBytes()
uploadDataChanged = currentUploaded != task.Progress.Uploaded uploadDataChanged = currentUploaded != task.Progress.Uploaded
task.Progress.UploadSpeed = task.calcSpeed(task.uploadSpeedArr, currentUploaded-task.Progress.Uploaded, tick) task.Progress.UploadSpeed = task.updateUploadSpeed(currentUploaded-task.Progress.Uploaded, tick)
task.Progress.Uploaded = currentUploaded task.Progress.Uploaded = currentUploaded
} }
d.emit(EventKeyProgress, task) d.emit(EventKeyProgress, task)
+17 -6
View File
@@ -100,16 +100,27 @@ func (t *Task) clone() *Task {
return util.DeepClone(t) return util.DeepClone(t)
} }
func (t *Task) calcSpeed(speedArr []int64, downloaded int64, usedTime float64) int64 { func (t *Task) updateSpeed(downloaded int64, usedTime float64) int64 {
speedArr = append(speedArr, downloaded) return calcSpeed(&t.speedArr, downloaded, usedTime)
if len(speedArr) > 6 { }
speedArr = speedArr[1:]
func (t *Task) updateUploadSpeed(downloaded int64, usedTime float64) int64 {
return calcSpeed(&t.uploadSpeedArr, downloaded, usedTime)
}
func calcSpeed(speedArr *[]int64, downloaded int64, usedTime float64) int64 {
*speedArr = append(*speedArr, downloaded)
// Record last 5 seconds of download speed to calculate the average speed
if len(*speedArr) > int(5.0/usedTime) {
*speedArr = (*speedArr)[1:]
} }
var total int64 var total int64
for _, v := range speedArr { for _, v := range *speedArr {
total += v total += v
} }
return int64(float64(total) / float64(len(speedArr)) / usedTime)
return int64(float64(total) / float64(len(*speedArr)) / usedTime)
} }
type TaskFilter struct { type TaskFilter struct {
+34
View File
@@ -0,0 +1,34 @@
package rest
import (
"compress/gzip"
"io"
"net/http"
"strings"
)
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (g gzipResponseWriter) Write(b []byte) (int, error) {
return g.Writer.Write(b)
}
func gzipMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
next.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
w.Header().Add("Vary", "Accept-Encoding")
gz := gzip.NewWriter(w)
defer gz.Close()
next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
})
}
+81 -5
View File
@@ -10,12 +10,14 @@ import (
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors" "github.com/pkg/errors"
"io/fs"
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"time"
) )
var ( var (
@@ -126,16 +128,28 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
if startCfg.WebEnable { if startCfg.WebEnable {
r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem))) r.PathPrefix("/fs/tasks").Handler(http.FileServer(new(taskFileSystem)))
r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem))) r.PathPrefix("/fs/extensions").Handler(http.FileServer(new(extensionFileSystem)))
r.PathPrefix("/").Handler(http.FileServer(http.FS(startCfg.WebFS))) r.PathPrefix("/").Handler(gzipMiddleware(http.FileServer(newEmbedCacheFileSystem(http.FS(startCfg.WebFS)))))
} }
if startCfg.ApiToken != "" || (startCfg.WebEnable && startCfg.WebBasicAuth != nil) { enableApiToken := startCfg.ApiToken != ""
enableBasicAuth := startCfg.WebEnable && startCfg.WebBasicAuth != nil
if enableApiToken || enableBasicAuth {
r.Use(func(h http.Handler) http.Handler { r.Use(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if startCfg.ApiToken != "" && r.Header.Get("X-Api-Token") == startCfg.ApiToken { if enableApiToken {
h.ServeHTTP(w, r) apiTokenHeader := r.Header["X-Api-Token"]
return // If api token header is set, only check api token ignore basic auth
if len(apiTokenHeader) > 0 {
if apiTokenHeader[0] == startCfg.ApiToken {
h.ServeHTTP(w, r)
return
}
WriteStatusJson(w, http.StatusUnauthorized, model.NewErrorResult("unauthorized", model.CodeUnauthorized))
return
}
} }
if startCfg.WebEnable && startCfg.WebBasicAuth != nil { if startCfg.WebEnable && startCfg.WebBasicAuth != nil {
if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() { if r.Header.Get("Authorization") == startCfg.WebBasicAuth.Authorization() {
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
@@ -222,6 +236,68 @@ func (e *extensionFileSystem) Open(name string) (http.File, error) {
return os.Open(filepath.Join(extensionPath, path)) return os.Open(filepath.Join(extensionPath, path))
} }
type embedCacheFileSystem struct {
fs http.FileSystem
lastModTime time.Time
}
func newEmbedCacheFileSystem(fs http.FileSystem) *embedCacheFileSystem {
efs := &embedCacheFileSystem{
fs: fs,
lastModTime: time.Now(),
}
exe, err := os.Executable()
if err != nil {
return efs
}
fi, err := os.Stat(exe)
if err != nil {
return efs
}
efs.lastModTime = fi.ModTime()
return efs
}
func (e *embedCacheFileSystem) Open(name string) (http.File, error) {
file, err := e.fs.Open(name)
if err != nil {
return nil, err
}
return &embedFile{
File: file,
lastModTime: e.lastModTime,
}, nil
}
type embedFile struct {
http.File
lastModTime time.Time
}
type embedFileInfo struct {
fs.FileInfo
lastModTime time.Time
}
func (e *embedFileInfo) ModTime() time.Time {
return e.lastModTime
}
func (e *embedFile) Stat() (fs.FileInfo, error) {
fi, err := e.File.Stat()
if err != nil {
return nil, err
}
return &embedFileInfo{
FileInfo: fi,
lastModTime: e.lastModTime,
}, nil
}
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool { func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
if err := json.NewDecoder(r.Body).Decode(v); err != nil { if err := json.NewDecoder(r.Body).Decode(v); err != nil {
WriteJson(w, model.NewErrorResult(err.Error())) WriteJson(w, model.NewErrorResult(err.Error()))
+96 -1
View File
@@ -14,6 +14,7 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath"
"reflect" "reflect"
"sync" "sync"
"testing" "testing"
@@ -478,6 +479,66 @@ func TestFsExtensionFail(t *testing.T) {
}) })
} }
func TestWebFsEnhance(t *testing.T) {
indexHtml := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>index</h1>
</body>
</html>
`
webDistPath := "dist"
os.MkdirAll("dist", os.ModePerm)
if err := os.WriteFile(filepath.Join(webDistPath, "index.html"), []byte(indexHtml), os.ModePerm); err != nil {
panic(err)
}
defer os.RemoveAll(webDistPath)
doTest0(func(cfg *model.StartConfig) {
cfg.WebFS = os.DirFS(webDistPath)
}, func() {
// First request no cache
code, header, _ := doHttpRequest1(http.MethodGet, "/index.html", map[string]string{
"Accept-Encoding": "gzip",
}, nil)
if code != http.StatusOK {
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusOK)
}
// Check header last-modified
if _, ok := header["Last-Modified"]; !ok {
t.Errorf("TestWebFsEnhance() missing key = %v", "Last-Modified")
}
// Check gzip compress
if _, ok := header["Content-Encoding"]; !ok || header["Content-Encoding"] != "gzip" {
t.Errorf("TestWebFsEnhance() no gzip compress")
}
// Request with If-Modified-Since
ifModifiedSince := header["Last-Modified"]
code, _, _ = doHttpRequest1(http.MethodGet, "/index.html", map[string]string{
"If-Modified-Since": ifModifiedSince,
}, nil)
if code != http.StatusNotModified {
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusNotModified)
}
// Request with un gzip
code, header, _ = doHttpRequest1(http.MethodGet, "/index.html?t=123", nil, nil)
if code != http.StatusOK {
t.Errorf("TestWebFsEnhance() got = %v, want %v", code, http.StatusOK)
}
// Check no gzip compress
if _, ok := header["Content-Encoding"]; ok && header["Content-Encoding"] == "gzip" {
t.Errorf("TestWebFsEnhance() has gzip compress")
}
})
}
func TestDoProxy(t *testing.T) { func TestDoProxy(t *testing.T) {
doTest(func() { doTest(func() {
code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{ code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
@@ -564,15 +625,38 @@ func TestAuthorization(t *testing.T) {
if status != http.StatusOK { if status != http.StatusOK {
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK) t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
} }
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
"Authorization": cfg.WebBasicAuth.Authorization(),
"X-Api-Token": cfg.ApiToken,
}, nil)
if status != http.StatusOK {
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
}
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
"Authorization": cfg.WebBasicAuth.Authorization(),
"X-Api-Token": "",
}, nil)
if status != http.StatusUnauthorized {
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
}
} }
func doTest(handler func()) { func doTest(handler func()) {
doTest0(nil, handler)
}
func doTest0(onStart func(cfg *model.StartConfig), handler func()) {
testFunc := func(storage model.Storage) { testFunc := func(storage model.Storage) {
var cfg = &model.StartConfig{} var cfg = &model.StartConfig{}
cfg.Init() cfg.Init()
cfg.Storage = storage cfg.Storage = storage
cfg.StorageDir = ".test_storage" cfg.StorageDir = ".test_storage"
cfg.WebEnable = true cfg.WebEnable = true
if onStart != nil {
onStart(cfg)
}
fileListener := doStart(cfg) fileListener := doStart(cfg)
defer func() { defer func() {
if err := fileListener.Close(); err != nil { if err := fileListener.Close(); err != nil {
@@ -603,6 +687,11 @@ func doStart(cfg *model.StartConfig) net.Listener {
} }
func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) { func doHttpRequest0(method string, path string, headers map[string]string, body any) (int, []byte) {
r1, _, r3 := doHttpRequest1(method, path, headers, body)
return r1, r3
}
func doHttpRequest1(method string, path string, headers map[string]string, body any) (int, map[string]string, []byte) {
var reader io.Reader var reader io.Reader
if body != nil { if body != nil {
buf, _ := json.Marshal(body) buf, _ := json.Marshal(body)
@@ -623,12 +712,18 @@ func doHttpRequest0(method string, path string, headers map[string]string, body
panic(err) panic(err)
} }
defer response.Body.Close() defer response.Body.Close()
respHeader := make(map[string]string)
for k, vv := range response.Header {
respHeader[k] = vv[0]
}
respBody, err := io.ReadAll(response.Body) respBody, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return response.StatusCode, respBody return response.StatusCode, respHeader, respBody
} }
func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) { func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) {
+4 -2
View File
@@ -69,5 +69,7 @@ windows/flutter/generated_plugins.cmake
database.hive database.hive
database.lock database.lock
assets/host/host assets/exec/host
assets/host/host.exe assets/exec/host.exe
assets/exec/updater
assets/exec/updater.exe
+1 -1
View File
@@ -48,7 +48,7 @@ android {
// 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 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
@@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
@@ -91,7 +93,8 @@
<!-- Add android:stopWithTask option only when necessary. --> <!-- Add android:stopWithTask option only when necessary. -->
<service <service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService" android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:stopWithTask="true" /> android:foregroundServiceType="dataSync"
android:exported="false" />
<!-- 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 -->
File renamed without changes.
@@ -24,9 +24,11 @@ import '../../../../database/database.dart';
import '../../../../database/entity.dart'; import '../../../../database/entity.dart';
import '../../../../i18n/message.dart'; import '../../../../i18n/message.dart';
import '../../../../main.dart'; import '../../../../main.dart';
import '../../../../util/github_mirror.dart';
import '../../../../util/locale_manager.dart'; import '../../../../util/locale_manager.dart';
import '../../../../util/log_util.dart'; import '../../../../util/log_util.dart';
import '../../../../util/package_info.dart'; import '../../../../util/package_info.dart';
import '../../../../util/updater.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import '../../create/dto/create_router_params.dart'; import '../../create/dto/create_router_params.dart';
@@ -35,47 +37,22 @@ import '../../redirect/views/redirect_view.dart';
const unixSocketPath = 'gopeed.sock'; const unixSocketPath = 'gopeed.sock';
const allTrackerSubscribeUrls = [ const allTrackerSubscribeUrls = [
'https://github.com/ngosang/trackerslist/raw/master/trackers_all.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_http.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_http.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_https.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_https.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_ip.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_ip.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_udp.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_udp.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_ws.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_ws.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_best.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt',
'https://github.com/ngosang/trackerslist/raw/master/trackers_best_ip.txt', 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best_ip.txt',
'https://github.com/XIU2/TrackersListCollection/raw/master/all.txt', 'https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt',
'https://github.com/XIU2/TrackersListCollection/raw/master/best.txt', 'https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/best.txt',
'https://github.com/XIU2/TrackersListCollection/raw/master/http.txt', 'https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/http.txt',
]; ];
const allTrackerCdns = [ final allTrackerSubscribeUrlCdns = {
// jsdelivr: https://fastly.jsdelivr.net/gh/ngosang/trackerslist/trackers_all.txt for (var v in allTrackerSubscribeUrls)
["https://fastly.jsdelivr.net/gh", r".*github.com(/.*)/raw/master(/.*)"], v: githubMirrorUrls(v, MirrorType.githubSource)
// ghproxy: https://ghproxy.com/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt };
[
"https://ghproxy.com/https://raw.githubusercontent.com",
r".*github.com(/.*)/raw(/.*)"
],
// mirror.ghproxy.com: https://mirror.ghproxy.com/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt
[
"https://mirror.ghproxy.com/https://raw.githubusercontent.com",
r".*github.com(/.*)/raw(/.*)"
],
];
final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
key: (v) => v as String,
value: (v) {
final ret = [v as String];
for (final cdn in allTrackerCdns) {
final reg = RegExp(cdn[1]);
final match = reg.firstMatch(v.toString());
var matchStr = "";
for (var i = 1; i <= match!.groupCount; i++) {
matchStr += match.group(i)!;
}
ret.add("${cdn[0]}$matchStr");
}
return ret;
});
class AppController extends GetxController with WindowListener, TrayListener { class AppController extends GetxController with WindowListener, TrayListener {
static StartConfig? _defaultStartConfig; static StartConfig? _defaultStartConfig;
@@ -109,6 +86,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
_initLaunchAtStartup().onError((error, stackTrace) => _initLaunchAtStartup().onError((error, stackTrace) =>
logger.w("initLaunchAtStartup error", error, stackTrace)); logger.w("initLaunchAtStartup error", error, stackTrace));
_initCheckUpdate().onError((error, stackTrace) =>
logger.w("initCheckUpdate error", error, stackTrace));
} }
@override @override
@@ -337,8 +317,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
if (uri.path == "/create") { if (uri.path == "/create") {
final params = uri.queryParameters["params"]; final params = uri.queryParameters["params"];
if (params?.isNotEmpty == true) { if (params?.isNotEmpty == true) {
final safeParams = params!.replaceAll(" ", "+");
final paramsJson = final paramsJson =
String.fromCharCodes(base64Decode(base64.normalize(params!))); String.fromCharCodes(base64Decode(base64.normalize(safeParams)));
Get.rootDelegate.offAndToNamed(Routes.REDIRECT, Get.rootDelegate.offAndToNamed(Routes.REDIRECT,
arguments: RedirectArgs(Routes.CREATE, arguments: RedirectArgs(Routes.CREATE,
arguments: arguments:
@@ -358,7 +339,8 @@ class AppController extends GetxController with WindowListener, TrayListener {
uri.scheme == "https") { uri.scheme == "https") {
path = uri.toString(); path = uri.toString();
} else if (uri.scheme == "file") { } else if (uri.scheme == "file") {
path = Util.isWindows() ? uri.path.substring(1) : uri.path; path =
Util.isWindows() ? Uri.decodeFull(uri.path.substring(1)) : uri.path;
} else { } else {
path = (await toFile(uri.toString())).path; path = (await toFile(uri.toString())).path;
} }
@@ -523,6 +505,13 @@ class AppController extends GetxController with WindowListener, TrayListener {
autoStartup.value = await launchAtStartup.isEnabled(); autoStartup.value = await launchAtStartup.isEnabled();
} }
Future<void> _initCheckUpdate() async {
final versionInfo = await checkUpdate();
if (versionInfo != null) {
await showUpdateDialog(Get.context!, versionInfo);
}
}
Future<void> saveConfig() async { Future<void> saveConfig() async {
Database.instance.saveStartConfig(StartConfigEntity( Database.instance.saveStartConfig(StartConfigEntity(
network: startConfig.value.network, network: startConfig.value.network,
@@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:window_manager/window_manager.dart'; // Import the required packages
import '../../../../i18n/message.dart'; import '../../../../i18n/message.dart';
import '../../../../theme/theme.dart'; import '../../../../theme/theme.dart';
import '../../../../util/locale_manager.dart'; import '../../../../util/locale_manager.dart';
import '../../../../util/util.dart'; // Import the required packages
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import '../controllers/app_controller.dart'; import '../controllers/app_controller.dart';
@@ -32,6 +34,19 @@ class AppView extends GetView<AppController> {
], ],
supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(), supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(),
getPages: AppPages.routes, getPages: AppPages.routes,
// Add listening to theme changes, set the title bar color according to the current system theme.
builder: (context, child) {
// if platform is desktop
if (Util.isDesktop()){
// actual brightness of the UI
Brightness brightness = Theme.of(context).brightness;
// Set the title bar to use the actual brightness of the UI
windowManager.setBrightness(brightness);
}
return child!;
},
), ),
); );
} }
@@ -82,16 +82,7 @@ class CreateView extends GetView<CreateController> {
_urlController.text = url; _urlController.text = url;
_urlController.selection = TextSelection.fromPosition( _urlController.selection = TextSelection.fromPosition(
TextPosition(offset: _urlController.text.length)); TextPosition(offset: _urlController.text.length));
final uppercaseUrl = url.toUpperCase(); final protocol = parseProtocol(url);
Protocol? protocol;
if (uppercaseUrl.startsWith("HTTP:") ||
uppercaseUrl.startsWith("HTTPS:")) {
protocol = Protocol.http;
}
if (uppercaseUrl.startsWith("MAGNET:") ||
uppercaseUrl.endsWith(".TORRENT")) {
protocol = Protocol.bt;
}
if (protocol != null) { if (protocol != null) {
final extraHandlers = { final extraHandlers = {
Protocol.http: () { Protocol.http: () {
@@ -672,6 +663,20 @@ class CreateView extends GetView<CreateController> {
); );
} }
// parse protocol from url
parseProtocol(String url) {
final uppercaseUrl = url.toUpperCase();
Protocol? protocol;
if (uppercaseUrl.startsWith("HTTP:") || uppercaseUrl.startsWith("HTTPS:")) {
protocol = Protocol.http;
}
if (uppercaseUrl.startsWith("MAGNET:") ||
uppercaseUrl.endsWith(".TORRENT")) {
protocol = Protocol.bt;
}
return protocol;
}
// recognize magnet uri, if length == 40, auto add magnet prefix // recognize magnet uri, if length == 40, auto add magnet prefix
recognizeMagnetUri(String text) { recognizeMagnetUri(String text) {
if (text.length != 40) { if (text.length != 40) {
@@ -764,24 +769,23 @@ class CreateView extends GetView<CreateController> {
Object? parseReqExtra(String url) { Object? parseReqExtra(String url) {
Object? reqExtra; Object? reqExtra;
if (controller.showAdvanced.value) { final protocol = parseProtocol(url);
switch (controller.advancedTabController.index) { switch (protocol) {
case 0: case Protocol.http:
final header = Map<String, String>.fromEntries(_httpHeaderControllers final header = Map<String, String>.fromEntries(_httpHeaderControllers
.map((e) => MapEntry(e.name.text, e.value.text))); .map((e) => MapEntry(e.name.text, e.value.text)));
header.removeWhere( header.removeWhere(
(key, value) => key.trim().isEmpty || value.trim().isEmpty); (key, value) => key.trim().isEmpty || value.trim().isEmpty);
if (header.isNotEmpty) { if (header.isNotEmpty) {
reqExtra = ReqExtraHttp()..header = header; reqExtra = ReqExtraHttp()..header = header;
} }
break; break;
case 1: case Protocol.bt:
if (_btTrackerController.text.trim().isNotEmpty) { if (_btTrackerController.text.trim().isNotEmpty) {
reqExtra = ReqExtraBt() reqExtra = ReqExtraBt()
..trackers = Util.textToLines(_btTrackerController.text); ..trackers = Util.textToLines(_btTrackerController.text);
} }
break; break;
}
} }
return reqExtra; return reqExtra;
} }
@@ -1,11 +1,10 @@
import 'dart:convert';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gopeed/api/api.dart';
import '../../../../util/updater.dart';
class SettingController extends GetxController { class SettingController extends GetxController {
final tapStatues = <String, bool>{}.obs; final tapStatues = <String, bool>{}.obs;
final latestVersion = "".obs; final latestVersion = Rxn<VersionInfo>();
@override @override
void onInit() { void onInit() {
@@ -26,23 +25,6 @@ class SettingController extends GetxController {
// fetch latest version // fetch latest version
void fetchLatestVersion() async { void fetchLatestVersion() async {
String? releaseDataStr; latestVersion.value = await checkUpdate();
try {
releaseDataStr = (await proxyRequest(
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
.data;
} catch (e) {
releaseDataStr =
(await proxyRequest("https://gopeed.com/api/release")).data;
}
if (releaseDataStr == null) {
return;
}
final releaseData = jsonDecode(releaseDataStr);
final tagName = releaseData["tag_name"];
if (tagName == null) {
return;
}
latestVersion.value = releaseData["tag_name"].substring(1);
} }
} }
@@ -18,6 +18,7 @@ import '../../../../util/log_util.dart';
import '../../../../util/message.dart'; import '../../../../util/message.dart';
import '../../../../util/package_info.dart'; import '../../../../util/package_info.dart';
import '../../../../util/scheme_register/scheme_register.dart'; import '../../../../util/scheme_register/scheme_register.dart';
import '../../../../util/updater.dart';
import '../../../../util/util.dart'; import '../../../../util/util.dart';
import '../../../views/check_list_view.dart'; import '../../../views/check_list_view.dart';
import '../../../views/directory_selector.dart'; import '../../../views/directory_selector.dart';
@@ -514,29 +515,7 @@ class SettingView extends GetView<SettingController> {
} }
buildVersion() { buildVersion() {
bool isNewVersion(String current, String latest) { var hasNewVersion = controller.latestVersion.value != null;
if (latest == "") {
return false;
}
final v1Parts = current.split('.');
final v2Parts = latest.split('.');
for (var i = 0; i < 3; i++) {
final v1Part = int.parse(v1Parts[i]);
final v2Part = int.parse(v2Parts[i]);
if (v1Part < v2Part) {
return true;
} else if (v1Part > v2Part) {
return false;
}
}
return false;
}
var hasNewVersion =
isNewVersion(packageInfo.version, controller.latestVersion.value);
return ListTile( return ListTile(
title: hasNewVersion title: hasNewVersion
? badges.Badge( ? badges.Badge(
@@ -546,27 +525,7 @@ class SettingView extends GetView<SettingController> {
subtitle: Text(packageInfo.version), subtitle: Text(packageInfo.version),
onTap: () { onTap: () {
if (hasNewVersion) { if (hasNewVersion) {
showDialog( showUpdateDialog(context, controller.latestVersion.value!);
context: context,
builder: (context) => AlertDialog(
content: Text('newVersionTitle'.trParams(
{'version': controller.latestVersion.value})),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: Text('newVersionLater'.tr),
),
TextButton(
onPressed: () {
launchUrl(Uri.parse('https://gopeed.com'),
mode: LaunchMode.externalApplication);
},
child: Text('newVersionUpdate'.tr),
),
],
));
} }
}, },
); );
@@ -762,7 +721,7 @@ class SettingView extends GetView<SettingController> {
width: 80, width: 80,
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
value: startCfg.value.network, value: startCfg.value.network,
onChanged: Util.isDesktop() onChanged: Util.isDesktop() || Util.isAndroid()
? (value) async { ? (value) async {
startCfg.update((val) { startCfg.update((val) {
val!.network = value!; val!.network = value!;
@@ -786,7 +745,8 @@ class SettingView extends GetView<SettingController> {
), ),
) )
]; ];
if (Util.isDesktop() && startCfg.value.network == 'tcp') { if ((Util.isDesktop() || Util.isAndroid()) &&
startCfg.value.network == 'tcp') {
final arr = startCfg.value.address.split(':'); final arr = startCfg.value.address.split(':');
var ip = '127.0.0.1'; var ip = '127.0.0.1';
var port = '0'; var port = '0';
+122
View File
@@ -0,0 +1,122 @@
const huHU = {
'hu_HU': {
'label': 'Magyar',
'error': 'Hiba',
'tip': 'Tipp',
'confirm': 'Megerősít',
'cancel': 'Mégsem',
'on': 'Bekapcsolva',
'off': 'Kikapcsolva',
'selectAll': 'Összes kiválasztása',
'select': 'Kiválaszt',
'task': 'Feladatok',
'downloading': 'letöltés alatt',
'downloaded': 'letöltve',
'setting': 'Beállítások',
'donate': 'Támogatás',
'exit': 'Kilépés',
'create': 'Feladat létrehozása',
'directDownload': 'Közvetlen letöltés',
'advancedOptions': 'Haladó beállítások',
'followSettings': 'Beállítások követése',
'downloadLink': 'Letöltési hivatkozás',
'downloadLinkValid': 'Kérjük, adja meg a letöltési hivatkozást',
'downloadLinkHit': 'Kérjük, adja meg a letöltési hivatkozást, soronként egyet@append',
'downloadLinkHitDesktop': ', vagy húzza ide a torrent fájlt közvetlenül',
'download': 'Letöltés',
'noFileSelected': 'Kérjük, válasszon ki legalább egy fájlt a folytatáshoz.',
'noStoragePermission': 'Tárolási engedély szükséges',
'selectFile': 'Fájl kiválasztása',
'rename': 'Átnevezés',
'basic': 'Alap',
'advanced': 'Haladó',
'general': 'Általános',
'downloadDir': 'Letöltési könyvtár',
'downloadDirValid': 'Kérjük, válassza ki a letöltési könyvtárat',
'connections': 'Kapcsolatok',
'useServerCtime': 'Használja a szerveridőt a fájl létrehozásához',
'maxRunning': 'Maximális futó feladatok',
'defaultDirectDownload': 'Alapértelmezettként ellenőrizze a közvetlen letöltést',
'items': '@count elem',
'subscribeTracker': 'Nyomkövető előfizetése',
'subscribeFail':
'Az előfizetés nem sikerült, kérjük, ellenőrizze a hálózatot, vagy próbálja újra később',
'update': 'Frissítés',
'updateDaily': 'Napi frissítés',
'lastUpdate': 'Utolsó frissítés: @time',
'addTracker': 'Nyomkövető hozzáadása',
'addTrackerHit': 'Kérjük, adja meg a nyomkövető szerver URL-jét, soronként egyet',
'ui': 'Felhasználói felület',
'theme': 'Téma',
'themeSystem': 'Rendszer',
'themeLight': 'Világos',
'themeDark': 'Sötét',
'locale': 'Nyelv',
'about': 'Névjegy',
'homepage': 'Főoldal',
'version': 'Verzió',
'protocol': 'Protokoll',
'port': 'Port',
'apiToken': 'API token',
'notSet': 'NS',
'set': 'BEÁLLÍT',
'portInUse': 'A [@port] port használatban van, kérjük, változtassa meg a portot',
'effectAfterRestart': 'Kihatás indítás után',
'developer': 'Fejlesztő',
'logDirectory': 'Napló könyvtár',
'show': 'Megjelenítés',
'continue': 'Folyatás',
'pause': 'Szünet',
'startAll': 'Összes indítása',
'pauseAll': 'Összes szüneteltetése',
'deleteTask': '@count feladat törlése',
'deleteTaskTip': 'Letöltött fájlok megőrzése',
'delete': 'Törlés',
'newVersionTitle': 'Új verzió felfedezése @version',
'newVersionUpdate': 'Frissítés most',
'newVersionLater': 'Később',
'extensions': 'Bővítmények',
'extensionInstallUrl': 'Telepítési URL',
'extensionInstallSuccess': 'Sikeresen telepítve',
'extensionUpdateSuccess': 'Sikeresen frissítve',
'extensionDelete': 'Bővítmény törlése',
'extensionAlreadyLatest': 'Ez már a legfrissebb verzió',
'extensionFind': 'Bővítmények keresése',
'extensionDevelop': 'Bővítmények fejlesztése',
'history': 'Előzmények',
'clearHistory': 'Előzmények törlése',
'noHistoryFound': 'Nincs előzménye',
'serviceTitle': 'Letöltési szolgáltatás',
'serviceText': 'Futtatás',
'network': 'Hálózat',
'proxy': 'Proxy',
'noProxy': 'Nincs proxy',
'systemProxy': 'Rendszer proxy',
'customProxy': 'Egyéni proxy',
'server': 'Szerver',
'username': 'Felhasználónév',
'password': 'Jelszó',
'thanks': 'Köszönet',
'thanksDesc':
'Köszönet minden hozzájárulónak, aki segített a Gopeed közösség építésében és fejlesztésében!',
'browserExtension': 'Böngésző bővítmény',
'launchAtStartup': 'Indítás indításkor',
'seedConfig': 'Seed beállítás',
'seedKeep': 'Tartsa a seedet amíg manuálisan le nem állítják',
'seedRatio': 'Seed arány',
'seedTime': 'Seed idő (perc)',
'setAsDefaultBtClient': 'Alapértelmezett BT klienseként beállít',
'taskDetail': 'Feladat részlete',
'taskName': 'Feladat neve',
'taskUrl': 'Feladat URL',
'downloadPath': 'Letöltési útvonal',
'skipVerifyCert': 'Tanúsítvány ellenőrzés átugrása',
'name': 'Név',
'size': 'Méret',
'unknown': 'Ismeretlen',
'fileSelectedCount': 'Fájlok: ',
'fileSelectedSize': 'Méret: ',
'httpHeaderName': 'Fejléc neve',
'httpHeaderValue': 'Fejléc értéke',
},
};
+77 -65
View File
@@ -7,43 +7,44 @@ const trTR = {
'cancel': 'İptal', 'cancel': 'İptal',
'on': 'Açık', 'on': 'Açık',
'off': 'Kapalı', 'off': 'Kapalı',
'selectAll': 'Hepsini seç', 'selectAll': 'Tümünü Seç',
'select': 'Seç',
'task': 'Görevler', 'task': 'Görevler',
'downloading': 'indiriliyor', 'downloading': 'İndiriliyor',
'downloaded': 'indirildi', 'downloaded': 'İndirildi',
'setting': 'Ayarlar', 'setting': 'Ayarlar',
'donate': 'Bağış', 'donate': 'Bağış Yap',
'exit': 'Çık', 'exit': 'Çıkış',
'create': 'Görev oluştur', 'create': 'Görev Oluştur',
'directDownload': 'Doğrudan indir', 'directDownload': 'Doğrudan İndirme',
'advancedOptions': 'Gelişmiş ayarlar', 'advancedOptions': 'Gelişmiş Seçenekler',
'followSettings': 'Ayarları takip et', 'followSettings': 'Ayarları Takip Et',
'downloadLink': 'İndirme bağlantısı', 'downloadLink': 'İndirme Bağlantısı',
'downloadLinkValid': 'Lütfen indirme bağlantısını girin', 'downloadLinkValid': 'Lütfen indirme bağlantısını girin',
'downloadLinkHit': 'Lütfen indirme bağlantısını girin, HTTP/HTTPS/MAGNET desteklenir@append', 'downloadLinkHit': 'Lütfen her satıra bir indirme bağlantısı girin@append',
'downloadLinkHitDesktop': ', veya torrent dosyasını doğrudan buraya sürükleyin', 'downloadLinkHitDesktop': ', veya doğrudan torrent dosyasını buraya sürükleyin',
'download': 'İndir', 'download': 'İndir',
'noFileSelected': 'Devam etmek için lütfen en az bir dosya seçin.', 'noFileSelected': 'Devam etmek için en az bir dosya seçin.',
'noStoragePermission': 'Depolama izni gerekli', 'noStoragePermission': 'Depolama izni gerekli',
'selectFile': 'Dosya seç', 'selectFile': 'Dosya Seç',
'rename': 'Yeniden adlandır', 'rename': 'Yeniden Adlandır',
'basic': 'Temel', 'basic': 'Temel',
'advanced': 'Gelişmiş', 'advanced': 'Gelişmiş',
'general': 'Genel', 'general': 'Genel',
'downloadDir': 'İndirme dizini', 'downloadDir': 'İndirme Dizini',
'downloadDirValid': 'Lütfen indirme dizinini seçin', 'downloadDirValid': 'Lütfen bir indirme dizini seçin',
'connections': 'Bağlantılar', 'connections': 'Bağlantılar',
'useServerCtime': 'Dosya oluşturma için sunucu saatini kullan', 'useServerCtime': 'Dosya oluşturma için sunucu zamanını kullan',
'maxRunning': 'Maksimum çalışan görev sayısı', 'maxRunning': 'Maksimum Çalışan Görev',
'defaultDirectDownload': 'Doğrudan indir seçili olsun', 'defaultDirectDownload': 'Varsayılan olarak doğrudan indirmeyi seç',
'items': '@count tane', 'items': '@count öğe',
'subscribeTracker': 'İzleyiciye abone ol', 'subscribeTracker': 'Tracker Aboneliği',
'subscribeFail': 'Abone olunamadı, lütfen ağı kontrol edin veya daha sonra tekrar deneyin', 'subscribeFail': 'Abonelik başarısız oldu, lütfen ağı kontrol edin veya daha sonra tekrar deneyin',
'update': 'Güncelle', 'update': 'Güncelle',
'updateDaily': 'Günlük olarak güncelle', 'updateDaily': 'Günlük güncelle',
'lastUpdate': 'Son güncellenme tarihi: @time', 'lastUpdate': 'Son güncelleme: @time',
'addTracker': 'İzleyici ekle', 'addTracker': 'Tracker Ekle',
'addTrackerHit': 'Lütfen her satıra bir tane olmak üzere izleyici sunucusu bağlantısını girin', 'addTrackerHit': 'Lütfen tracker sunucu URL’sini girin, her satıra bir tane',
'ui': 'Arayüz', 'ui': 'Arayüz',
'theme': 'Tema', 'theme': 'Tema',
'themeSystem': 'Sistem', 'themeSystem': 'Sistem',
@@ -51,59 +52,70 @@ const trTR = {
'themeDark': 'Koyu', 'themeDark': 'Koyu',
'locale': 'Dil', 'locale': 'Dil',
'about': 'Hakkında', 'about': 'Hakkında',
'homepage': 'Anasayfa', 'homepage': 'Ana Sayfa',
'version': 'Sürüm', 'version': 'Sürüm',
'protocol': 'Protokol', 'protocol': 'Protokol',
'port': 'Bağlantı noktası', 'port': 'Bağlantı Noktası',
'apiToken': 'API anahtarı', 'apiToken': 'API Anahtarı',
'notSet': 'AYARLI DEĞİL', 'notSet': 'AYARLANMADI',
'set': 'AYARLI', 'set': 'AYARLA',
'portInUse': 'Bağlantı noktası [@port] kullanımda, lütfen bağlantı noktasını değiştirin', 'portInUse': 'Bağlantı noktası [@port] kullanımda, lütfen değiştirin',
'effectAfterRestart': 'Yeniden başlatma sonrası etki edecektir', 'effectAfterRestart': 'Yeniden başlatmadan sonra etkili olacak',
'developer': 'Geliştirici', 'developer': 'Geliştirici',
'logDirectory': 'Kayıt dizini', 'logDirectory': 'Günlük Dizini',
'show': 'Göster', 'show': 'Göster',
'startAll': 'Hepsini başlat', 'continue': 'Devam Et',
'pauseAll': 'Hepsini durdur', 'pause': 'Duraklat',
'startAll': 'Tümünü Başlat',
'pauseAll': 'Tümünü Duraklat',
'deleteTask': '@count görevi sil', 'deleteTask': '@count görevi sil',
'deleteTaskTip': 'İndirilen dosyaları sakla', 'deleteTaskTip': 'İndirilen dosyaları sakla',
'delete': 'Sil', 'delete': 'Sil',
'newVersionTitle': 'Yeni sürümü keşfet @version', 'newVersionTitle': 'Yeni sürüm bulundu @version',
'newVersionUpdate': 'Şimdi güncelle', 'newVersionUpdate': 'Şimdi Güncelle',
'newVersionLater': 'Sonra', 'newVersionLater': 'Daha Sonra',
'extensions': 'Uzantılar', 'extensions': 'Eklentiler',
'extensionInstallUrl': 'İndirme bağlantısı', 'extensionInstallUrl': 'Kurulum URL’si',
'extensionInstallSuccess': 'Başarıyla indirildi', 'extensionInstallSuccess': 'Başarıyla yüklendi',
'extensionUpdateSuccess': 'Başarıyla güncellendi', 'extensionUpdateSuccess': 'Başarıyla güncellendi',
'extensionDelete': 'Uzantıyı sil', 'extensionDelete': 'Eklentiyi Sil',
'extensionAlreadyLatest': 'Bu zaten en son sürüm', 'extensionAlreadyLatest': 'Zaten en son sürüm',
'extensionFind': 'Uzantı ara', 'extensionFind': 'Eklentileri Bul',
'extensionDevelop': 'Uzantı geliştir', 'extensionDevelop': 'Eklenti Geliştir',
'history': 'Geçmiş', 'history': 'Geçmiş',
'clearHistory': 'Geçmişi temizle', 'clearHistory': 'Geçmişi Temizle',
'noHistoryFound': 'Geçmiş bulunamadı', 'noHistoryFound': 'Geçmiş Bulunamadı',
'serviceTitle': 'Hizmet indir', 'serviceTitle': 'İndirme Servisi',
'serviceText': 'Çalışıyor', 'serviceText': 'Çalışıyor',
'network': 'Ağ', 'network': 'Ağ',
'proxy': 'Vekil', 'proxy': 'Vekil Sunucu',
'noProxy': 'Vekil yok', 'noProxy': 'Vekil Sunucu Yok',
'systemProxy': 'Sistem vekili', 'systemProxy': 'Sistem Vekil Sunucusu',
'customProxy': 'Özel vekil', 'customProxy': 'Özel Vekil Sunucu',
'server': 'Sunucu', 'server': 'Sunucu',
'username': 'Kullanıcı adı', 'username': 'Kullanıcı Adı',
'password': 'Şifre', 'password': 'Şifre',
'thanks': 'Teşekkürler', 'thanks': 'Teşekkürler',
'thanksDesc': 'Gopeed topluluğunun oluşmasına ve gelişmesine yardımcı olan tüm katılımcılara teşekkürler!', 'thanksDesc':
'browserExtension': 'Tarayıcı uzantısı', 'Gopeed topluluğunun inşasında ve geliştirilmesinde katkıda bulunan herkese teşekkürler!',
'launchAtStartup': 'Başlangıçta başlat', 'browserExtension': 'Tarayıcı Eklentisi',
'seedConfig': 'Seed ayarları', 'launchAtStartup': 'Başlangıçta Çalıştır',
'seedKeep': 'Manuel durdurulana kadar seed etmeye devam et', 'seedConfig': 'Seed Ayarları',
'seedKeep': 'Manuel olarak durdurulana kadar seed etmeye devam et',
'seedRatio': 'Seed oranı', 'seedRatio': 'Seed oranı',
'seedTime': 'Seed süresi (dakika)', 'seedTime': 'Seed süresi (dakika)',
'taskDetail': 'Görev detayı', 'setAsDefaultBtClient': 'Varsayılan BT istemcisi olarak ayarla',
'taskName': 'Görev adı', 'taskDetail': 'Görev Detayı',
'taskUrl': 'Görev URL', 'taskName': 'Görev Adı',
'downloadPath': 'İndirme yolu', 'taskUrl': 'Görev URL’si',
'skipVerifyCert': 'Sertifika doğrulamasını atla', 'downloadPath': 'İndirme Yolu',
'skipVerifyCert': 'Sertifika Doğrulamayı Atla',
'name': 'Ad',
'size': 'Boyut',
'unknown': 'Bilinmeyen',
'fileSelectedCount': 'Dosya: ',
'fileSelectedSize': 'Boyut: ',
'httpHeaderName': 'Başlık Adı',
'httpHeaderValue': 'Başlık Değeri',
}, },
}; };
+123
View File
@@ -0,0 +1,123 @@
const ukUA = {
'uk_UA': {
'label': 'Мова',
'error': 'Помилка',
'tip': 'Підказка',
'confirm': 'Підтвердити',
'cancel': 'Скасувати',
'on': 'Увімк.',
'off': 'Вимк.',
'selectAll': 'Вибрати все',
'select': 'Вибрати',
'task': 'Завдання',
'downloading': 'завантажується',
'downloaded': 'завантажено',
'setting': 'Налаштування',
'donate': 'Підтримати',
'exit': 'Вихід',
'create': 'Створити завдання',
'directDownload': 'Пряме завантаження',
'advancedOptions': 'Додаткові параметри',
'followSettings': 'За замовчуванням',
'downloadLink': 'Посилання на завантаження',
'downloadLinkValid': 'Будь ласка, введіть посилання на завантаження',
'downloadLinkHit':
'Будь ласка, введіть посилання на завантаження, одне на рядок@Append',
'downloadLinkHitDesktop': ', або перетягніть сюди файл торенту',
'download': 'Завантажити',
'noFileSelected': 'Будь ласка, виберіть хоча б один файл для продовження.',
'noStoragePermission': 'Потрібен дозвіл на зберігання',
'selectFile': 'Вибрати файл',
'rename': 'Перейменувати',
'basic': 'Основні',
'advanced': 'Розширені',
'general': 'Загальні',
'downloadDir': 'Каталог завантажень',
'downloadDirValid': 'Будь ласка, виберіть каталог завантажень',
'connections': 'З’єднання',
'useServerCtime': 'Використовувати час сервера для створення файлу',
'maxRunning': 'Макс. завдань у черзі',
'defaultDirectDownload': 'Пряме завантаження за замовчуванням',
'items': '@count елементів',
'subscribeTracker': 'Підписатися на трекер',
'subscribeFail':
'Не вдалося підписатися, перевірте мережу або спробуйте пізніше',
'update': 'Оновити',
'updateDaily': 'Оновлювати щодня',
'lastUpdate': 'Останнє оновлення: @time',
'addTracker': 'Додати трекер',
'addTrackerHit': 'Будь ласка, введіть URL-адресу трекера, одну на рядок',
'ui': 'Інтерфейс',
'theme': 'Тема',
'themeSystem': 'Системна',
'themeLight': 'Світла',
'themeDark': 'Темна',
'locale': 'Мова',
'about': 'Про програму',
'homepage': 'Домашня сторінка',
'version': 'Версія',
'protocol': 'Протокол',
'port': 'Порт',
'apiToken': 'API Токен',
'notSet': 'Не задано',
'set': 'Задати',
'portInUse': 'Порт [@PORT] вже використовується, будь ласка, змініть порт',
'effectAfterRestart': 'Ефект після перезапуску',
'developer': 'Розробник',
'logDirectory': 'Каталог журналів',
'show': 'Показати',
'continue': 'Продовжити',
'pause': 'Пауза',
'startAll': 'Запустити все',
'pauseAll': 'Зупинити все',
'deleteTask': 'Видалити @count завдань',
'deleteTaskTip': 'Зберегти завантажені файли',
'delete': 'Видалити',
'newVersionTitle': 'Доступна нова версія @Version',
'newVersionUpdate': 'Оновити зараз',
'newVersionLater': 'Пізніше',
'extensions': 'Розширення',
'extensionInstallUrl': 'URL для встановлення',
'extensionInstallSuccess': 'Встановлено успішно',
'extensionUpdateSuccess': 'Оновлено успішно',
'extensionDelete': 'Видалити розширення',
'extensionAlreadyLatest': 'Вже остання версія',
'extensionFind': 'Знайти розширення',
'extensionDevelop': 'Розробка розширень',
'history': 'Історія',
'clearHistory': 'Очистити історію',
'noHistoryFound': 'Історію не знайдено',
'serviceTitle': 'Служба завантаження',
'serviceText': 'Працює',
'network': 'Мережа',
'proxy': 'Проксі',
'noProxy': 'Без проксі',
'systemProxy': 'Системний проксі',
'customProxy': 'Користувацький проксі',
'server': 'Сервер',
'username': 'Ім’я користувача',
'password': 'Пароль',
'thanks': 'Подяки',
'thanksDesc':
'Дякуємо всім учасникам, які допомогли створити та розвивати спільноту Gopeed!',
'browserExtension': 'Розширення для браузера',
'launchAtStartup': 'Запускати під час завантаження системи',
'seedConfig': 'Налаштування роздачі',
'seedKeep': 'Роздавати доки не зупиню вручну',
'seedRatio': 'Коефіцієнт роздачі',
'seedTime': 'Час роздачі (хвилини)',
'setAsDefaultBtClient': 'Встановити як клієнт BT за замовчуванням',
'taskDetail': 'Деталі завдання',
'taskName': 'Назва завдання',
'taskUrl': 'URL завдання',
'downloadPath': 'Шлях завантаження',
'skipVerifyCert': 'Пропустити перевірку сертифіката',
'name': 'Назва',
'size': 'Розмір',
'unknown': 'Невідомо',
'fileSelectedCount': 'Файлів: ',
'fileSelectedSize': 'Розмір: ',
'httpHeaderName': 'Назва заголовка HTTP',
'httpHeaderValue': 'Значення заголовка HTTP',
},
};
+4
View File
@@ -14,6 +14,8 @@ import 'langs/vi_vn.dart';
import 'langs/zh_cn.dart'; import 'langs/zh_cn.dart';
import 'langs/zh_tw.dart'; import 'langs/zh_tw.dart';
import 'langs/es_es.dart'; import 'langs/es_es.dart';
import 'langs/uk_ua.dart';
import 'langs/hu_hu.dart';
final messages = _Messages(); final messages = _Messages();
@@ -35,5 +37,7 @@ class _Messages extends Translations {
...idID, ...idID,
...frFR, ...frFR,
...esES, ...esES,
...ukUA,
...huHU,
}; };
} }
+31 -18
View File
@@ -1,6 +1,7 @@
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.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:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@@ -15,6 +16,7 @@ import 'util/locale_manager.dart';
import 'util/log_util.dart'; import 'util/log_util.dart';
import 'util/package_info.dart'; import 'util/package_info.dart';
import 'util/scheme_register/scheme_register.dart'; import 'util/scheme_register/scheme_register.dart';
import 'util/updater.dart';
import 'util/util.dart'; import 'util/util.dart';
class Args { class Args {
@@ -40,6 +42,9 @@ void main(List<String> arguments) async {
Future<void> init(Args args) async { Future<void> init(Args args) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
if (Util.isMobile()) {
FlutterForegroundTask.initCommunicationPort();
}
await Util.initStorageDir(); await Util.initStorageDir();
await Database.instance.init(); await Database.instance.init();
if (Util.isDesktop()) { if (Util.isDesktop()) {
@@ -89,26 +94,34 @@ Future<void> init(Args args) async {
} }
() async { () async {
try { if (Util.isDesktop()) {
registerUrlScheme("gopeed");
if (controller.downloaderConfig.value.extra.defaultBtClient) {
registerDefaultTorrentClient();
}
} catch (e) {
logger.e("register scheme fail", e);
}
try {
await installHost();
} catch (e) {
logger.e("browser extension host binary install fail", e);
}
for (final browser in Browser.values) {
try { try {
await installManifest(browser); registerUrlScheme("gopeed");
if (controller.downloaderConfig.value.extra.defaultBtClient) {
registerDefaultTorrentClient();
}
} catch (e) { } catch (e) {
logger.e( logger.e("register scheme fail", e);
"browser [${browser.name}] extension host integration fail", e); }
try {
await installHost();
} catch (e) {
logger.e("browser extension host binary install fail", e);
}
for (final browser in Browser.values) {
try {
await installManifest(browser);
} catch (e) {
logger.e(
"browser [${browser.name}] extension host integration fail", e);
}
}
try {
await installUpdater();
} catch (e) {
logger.e("updater install fail", e);
} }
} }
}(); }();
+15
View File
@@ -0,0 +1,15 @@
import 'arch_stub.dart'
if (dart.library.io) 'entry/arch_native.dart'
if (dart.library.html) 'entry/arch_web.dart';
// Copy from pkg/sky_engine/lib/ffi/abi.dart
enum Architecture {
arm,
arm64,
ia32,
x64,
riscv32,
riscv64,
}
Architecture getArch() => doGetArch();
+3
View File
@@ -0,0 +1,3 @@
import 'arch.dart';
Architecture doGetArch() => throw UnimplementedError();
@@ -0,0 +1,13 @@
// ignore: avoid_web_libraries_in_flutter
import 'dart:ffi';
import '../arch.dart';
Architecture doGetArch() {
final currentAbi = Abi.current().toString();
final archName = currentAbi.split("_")[1];
final arch = Architecture.values.firstWhere(
(element) => element.name == archName,
orElse: () => Architecture.x64);
return arch;
}
@@ -0,0 +1,5 @@
import '../arch.dart';
Architecture doGetArch() {
return Architecture.x64;
}
@@ -1,11 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:win32_registry/win32_registry.dart'; import 'package:win32_registry/win32_registry.dart';
import '../../util.dart';
import '../../win32.dart'; import '../../win32.dart';
import '../browser_extension_host.dart'; import '../browser_extension_host.dart';
@@ -27,33 +26,9 @@ const _firefoxNativeHostsKey = r'Software\Mozilla\NativeMessagingHosts';
/// Install host binary for browser extension /// Install host binary for browser extension
Future<void> doInstallHost() async { Future<void> doInstallHost() async {
final hostPath = final hostPath = Util.homePathJoin(_hostExecName);
path.join((await getApplicationSupportDirectory()).path, _hostExecName); await Util.installAsset('assets/exec/$_hostExecName', hostPath,
executable: true);
Future<List<int>> getHostAssetData() async {
final hostAsset = await rootBundle.load('assets/host/$_hostExecName');
return hostAsset.buffer
.asUint8List(hostAsset.offsetInBytes, hostAsset.lengthInBytes);
}
// Check if host binary is not installed
if (!await File(hostPath).exists()) {
final hostData = await getHostAssetData();
final file = File(hostPath);
await file.writeAsBytes(hostData);
// Add execute permission
if (!Platform.isWindows) {
await Process.run('chmod', ['+x', hostPath]);
}
return;
}
// Check if host binary is outdated
final hostAssetData = await getHostAssetData();
final hostFileData = await File(hostPath).readAsBytes();
if (hostAssetData.length != hostFileData.length) {
await File(hostPath).writeAsBytes(hostAssetData);
return;
}
} }
/// Check if specified browser is installed /// Check if specified browser is installed
@@ -102,7 +77,7 @@ Future<bool> doCheckManifestInstalled(Browser browser) async {
} }
final existingContent = await File(manifestPath).readAsString(); final existingContent = await File(manifestPath).readAsString();
final expectedContent = await _getManifestContent(); final expectedContent = await _getManifestContent(browser);
return existingContent == expectedContent; return existingContent == expectedContent;
} }
@@ -112,7 +87,7 @@ Future<void> doInstallManifest(Browser browser) async {
if (await checkManifestInstalled(browser)) return; if (await checkManifestInstalled(browser)) return;
final manifestPath = _getManifestPath(browser)!; final manifestPath = _getManifestPath(browser)!;
final manifestContent = await _getManifestContent(); final manifestContent = await _getManifestContent(browser);
final manifestDir = path.dirname(manifestPath); final manifestDir = path.dirname(manifestPath);
await Directory(manifestDir).create(recursive: true); await Directory(manifestDir).create(recursive: true);
await File(manifestPath).writeAsString(manifestContent); await File(manifestPath).writeAsString(manifestContent);
@@ -235,10 +210,10 @@ List<String> _getUnixExecutablePaths(Browser browser) {
} }
String? _getManifestPath(Browser browser) { String? _getManifestPath(Browser browser) {
final manifestName =
browser == Browser.firefox ? '$_hostName.moz.json' : '$_hostName.json';
if (Platform.isWindows) { if (Platform.isWindows) {
final execPath = Platform.resolvedExecutable; return Util.homePathJoin(manifestName);
final execDir = path.dirname(execPath);
return path.join(execDir, '$_hostName.json');
} }
final home = final home =
@@ -249,25 +224,25 @@ String? _getManifestPath(Browser browser) {
switch (browser) { switch (browser) {
case Browser.chrome: case Browser.chrome:
return path.join(home, 'Library', 'Application Support', 'Google', return path.join(home, 'Library', 'Application Support', 'Google',
'Chrome', 'NativeMessagingHosts', '$_hostName.json'); 'Chrome', 'NativeMessagingHosts', manifestName);
case Browser.edge: case Browser.edge:
return path.join(home, 'Library', 'Application Support', return path.join(home, 'Library', 'Application Support',
'Microsoft Edge', 'NativeMessagingHosts', '$_hostName.json'); 'Microsoft Edge', 'NativeMessagingHosts', manifestName);
case Browser.firefox: case Browser.firefox:
return path.join(home, 'Library', 'Application Support', 'Mozilla', return path.join(home, 'Library', 'Application Support', 'Mozilla',
'NativeMessagingHosts', '$_hostName.json'); 'NativeMessagingHosts', manifestName);
} }
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
switch (browser) { switch (browser) {
case Browser.chrome: case Browser.chrome:
return path.join(home, '.config', 'google-chrome', return path.join(home, '.config', 'google-chrome',
'NativeMessagingHosts', '$_hostName.json'); 'NativeMessagingHosts', manifestName);
case Browser.edge: case Browser.edge:
return path.join(home, '.config', 'microsoft-edge', return path.join(home, '.config', 'microsoft-edge',
'NativeMessagingHosts', '$_hostName.json'); 'NativeMessagingHosts', manifestName);
case Browser.firefox: case Browser.firefox:
return path.join( return path.join(
home, '.mozilla', 'native-messaging-hosts', '$_hostName.json'); home, '.mozilla', 'native-messaging-hosts', manifestName);
} }
} }
return null; return null;
@@ -283,20 +258,20 @@ Future<bool> _checkWindowsRegistry(String keyPath) async {
} }
} }
Future<String> _getManifestContent() async { Future<String> _getManifestContent(Browser browser) async {
final hostPath = final hostPath = Util.homePathJoin(_hostExecName);
path.join((await getApplicationSupportDirectory()).path, _hostExecName);
final manifest = { final manifest = {
'name': _hostName, 'name': _hostName,
'description': 'Gopeed browser extension host', 'description': 'Gopeed browser extension host',
'path': hostPath, 'path': hostPath,
'type': 'stdio', 'type': 'stdio',
'allowed_origins': [ if (browser != Browser.firefox)
'chrome-extension://$_chromeExtensionId/', 'allowed_origins': [
'chrome-extension://$_edgeExtensionId/', 'chrome-extension://$_chromeExtensionId/',
..._debugExtensionIds.map((id) => 'chrome-extension://$id/'), 'chrome-extension://$_edgeExtensionId/',
], ..._debugExtensionIds.map((id) => 'chrome-extension://$id/'),
'allowed_extensions': [_firefoxExtensionId], ],
if (browser == Browser.firefox) 'allowed_extensions': [_firefoxExtensionId],
}; };
return const JsonEncoder.withIndent(' ').convert(manifest); return const JsonEncoder.withIndent(' ').convert(manifest);
} }
+79
View File
@@ -0,0 +1,79 @@
import 'package:dio/dio.dart';
// List of mirrors for GitHub
const _sourceMirror = [
// ghfast.top: https://ghfast.top/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt
["https://ghfast.top/", r"(.*)"],
// github.moeyy.xyz: https://github.moeyy.xyz/https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt
["https://github.moeyy.xyz/", r"(.*)"],
// jsdelivr: https://fastly.jsdelivr.net/gh/ngosang/trackerslist/trackers_all.txt
[
"https://fastly.jsdelivr.net/gh",
r".*raw.githubusercontent.com(/.*)(/.*)/(?:master|main)(/.*)"
],
];
// List of mirrors for GitHub release
const _assertMirror = [
// ghfast.top: https://ghfast.top/https://github.com/GopeedLab/gopeed/releases/download/v1.6.10/Gopeed-v1.6.10-windows-amd64.zip
["https://ghfast.top/", r"(.*)"],
// github.moeyy.xyz: https://github.moeyy.xyz/https://github.com/GopeedLab/gopeed/releases/download/v1.6.10/Gopeed-v1.6.10-windows-amd64.zip
["https://github.moeyy.xyz/", r"(.*)"],
];
enum MirrorType {
githubSource,
githubRelease,
}
/// Auto detect the best mirror for the given [rawUrl] and [type]
Future<String> githubAutoMirror(String rawUrl, MirrorType type) async {
final mirrorUrls = githubMirrorUrls(rawUrl, type);
// Ping all mirrors and get the fastest one
final pingResult = await Future.wait(mirrorUrls.map((e) async {
final client = Dio()
..options.sendTimeout = const Duration(seconds: 3)
..options.connectTimeout = const Duration(seconds: 3);
var time = DateTime.now().millisecondsSinceEpoch;
try {
final response = await client.head(e);
if (response.statusCode == 200) {
time = DateTime.now().millisecondsSinceEpoch - time;
return (e, time);
}
} catch (e) {
// ignore
} finally {
client.close();
}
return (e, -1);
}));
var list = pingResult.where((e) => e.$2 != -1).toList()
..sort((a, b) => a.$2.compareTo(b.$2));
if (list.isNotEmpty) {
return list.first.$1;
}
return rawUrl;
}
List<String> githubMirrorUrls(String rawUrl, MirrorType type) {
final mirrors = switch (type) {
MirrorType.githubSource => _sourceMirror,
MirrorType.githubRelease => _assertMirror,
};
final ret = <String>[];
for (final cdn in mirrors) {
final reg = RegExp(cdn[1]);
final match = reg.firstMatch(rawUrl.toString());
var matchStr = "";
for (var i = 1; i <= match!.groupCount; i++) {
matchStr += match.group(i)!;
}
ret.add("${cdn[0]}$matchStr");
}
return ret;
}
+432
View File
@@ -0,0 +1,432 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../api/api.dart';
import '../app/views/outlined_button_loading.dart';
import 'arch/arch.dart';
import 'github_mirror.dart';
import 'log_util.dart';
import 'message.dart';
import 'package_info.dart';
import 'util.dart';
enum Channel {
windowsInstaller,
windowsPortable,
macosDmg,
linuxFlathub,
linuxSnap,
linuxDeb,
linuxAppImage,
androidApk,
iosIpa,
docker,
}
const _channelEnv = String.fromEnvironment("UPDATE_CHANNEL");
final _channel =
Channel.values.where((e) => e.name == _channelEnv).firstOrNull ??
() {
if (Util.isWindows()) {
return Channel.windowsPortable;
} else if (Util.isMacos()) {
return Channel.macosDmg;
} else if (Util.isAndroid()) {
return Channel.androidApk;
} else if (Util.isIOS()) {
return Channel.iosIpa;
} else {
return null;
}
}();
final _updaterBin = "updater${Util.isWindows() ? ".exe" : ""}";
Future<void> installUpdater() async {
await Util.installAsset(
'assets/exec/$_updaterBin', Util.homePathJoin(_updaterBin),
executable: true);
}
class VersionInfo {
final String version;
final String changeLog;
VersionInfo(this.version, this.changeLog);
}
Future<VersionInfo?> checkUpdate() async {
String? releaseDataStr;
try {
releaseDataStr = (await proxyRequest(
"https://api.github.com/repos/GopeedLab/gopeed/releases/latest"))
.data;
} catch (e) {
releaseDataStr =
(await proxyRequest("https://gopeed.com/api/release")).data;
}
if (releaseDataStr == null) {
return null;
}
final releaseData = jsonDecode(releaseDataStr);
final tagName = releaseData["tag_name"];
if (tagName == null) {
return null;
}
final latestVersion = releaseData["tag_name"].substring(1);
// compare version x.y.z to x.y.z
final currentVersion = packageInfo.version;
var isNewVersion = false;
if (latestVersion != currentVersion) {
final currentVersionList = currentVersion.split(".");
final latestVersionList = latestVersion.split(".");
for (var i = 0; i < currentVersionList.length; i++) {
if (int.parse(latestVersionList[i]) > int.parse(currentVersionList[i])) {
isNewVersion = true;
break;
}
}
}
if (!isNewVersion) {
return null;
}
return VersionInfo(latestVersion, releaseData["body"]);
}
Future<void> showUpdateDialog(
BuildContext context, VersionInfo versionInfo) async {
final fullChangeLog = versionInfo.changeLog;
final isZh = Get.locale?.languageCode == "zh";
final changeLogRegex = isZh
? RegExp(r"(#\s+更新日志.*)", multiLine: true, dotAll: true)
: RegExp(r"(# Release notes.*)#\s+更新日志", multiLine: true, dotAll: true);
final changeLog = changeLogRegex.firstMatch(fullChangeLog)?.group(1) ?? "";
await showDialog(
context: Get.context!,
barrierDismissible: false,
builder: (context) {
bool isDownloading = false;
double progress = 0;
int total = 0;
final buttonController = OutlinedButtonLoadingController();
return StatefulBuilder(
builder: (context, setState) {
final screenSize = MediaQuery.of(context).size;
final dialogWidth =
screenSize.width < 500 ? screenSize.width * 0.9 : 500.0;
final dialogHeight =
screenSize.height < 600 ? screenSize.height * 0.8 : 400.0;
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: SizedBox(
width: dialogWidth,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'newVersionTitle'
.trParams({'version': versionInfo.version}),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Container(
height: dialogHeight * 0.5,
decoration: BoxDecoration(
border:
Border.all(color: Theme.of(context).dividerColor),
borderRadius: BorderRadius.circular(8),
),
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
scrollbars: true,
),
child: Builder(
builder: (context) {
final controller = ScrollController();
return Scrollbar(
controller: controller,
thumbVisibility: true,
child: SingleChildScrollView(
controller: controller,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children:
_parseMarkdown(changeLog, context),
),
),
),
);
},
),
),
),
if (isDownloading) ...[
const SizedBox(height: 16),
LinearProgressIndicator(
value: progress,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${(progress * 100).toStringAsFixed(1)}%',
style: const TextStyle(fontSize: 12),
),
Text(
total == 0
? ''
: '${Util.fmtByte((total * progress).toInt())} / ${Util.fmtByte(total)}',
style: const TextStyle(fontSize: 12),
),
],
)
],
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: isDownloading ? null : () => Get.back(),
child: Text(
'newVersionLater'.tr,
style: TextStyle(
color: isDownloading
? Theme.of(context).disabledColor
: Theme.of(context).colorScheme.error),
),
),
const SizedBox(width: 8),
OutlinedButtonLoading(
controller: buttonController,
onPressed: () async {
setState(() {
isDownloading = true;
});
buttonController.start();
try {
await _update(versionInfo.version,
(received, fileTotal) {
setState(() {
total = fileTotal;
progress = received / fileTotal;
});
});
} catch (e) {
showErrorMessage(e);
} finally {
setState(() {
isDownloading = false;
});
buttonController.stop();
}
},
child: Text('newVersionUpdate'.tr),
),
],
),
],
),
),
),
);
},
);
},
);
}
List<Widget> _parseMarkdown(String markdown, BuildContext context) {
final List<Widget> widgets = [];
final lines = markdown.split('\n');
for (final line in lines) {
if (line.trim().isEmpty) continue;
if (line.startsWith('# ')) {
// H1 header
widgets.add(Text(
line.substring(2),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
));
} else if (line.startsWith('## ')) {
// H2 header
widgets.add(Text(
line.substring(3),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
));
} else if (line.trim().startsWith('- ')) {
// List item
widgets.add(Padding(
padding: const EdgeInsets.only(left: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('• ', style: TextStyle(fontSize: 14)),
Expanded(
child: Text(
line.substring(line.indexOf('-') + 1).trim().replaceFirst(
RegExp(r'@[^\s]*\s\(#\d+\)'),
''), // Remove contributor and pr number
style: const TextStyle(fontSize: 14),
),
),
],
),
));
} else {
// Normal text
widgets.add(Text(
line,
style: const TextStyle(fontSize: 14),
));
}
// Add spacing between elements
widgets.add(const SizedBox(height: 8));
}
return widgets;
}
Future<void> _update(String version, Function(int, int) onProgress) async {
var newVersionAssetPath = "";
final newVersionAssetName = _getAssetName(version);
// Need to download the asset
if (newVersionAssetName.isNotEmpty) {
final downloadUrl =
'https://github.com/GopeedLab/gopeed/releases/download/v$version/$newVersionAssetName';
newVersionAssetPath = await _getAssetPath(version);
if (downloadUrl.isNotEmpty) {
final fastDownloadUrl =
await githubAutoMirror(downloadUrl, MirrorType.githubRelease);
final downloadClient = Dio();
await downloadClient.download(fastDownloadUrl, newVersionAssetPath,
onReceiveProgress: onProgress);
}
}
switch (_channel) {
case Channel.windowsInstaller:
case Channel.windowsPortable:
case Channel.macosDmg:
case Channel.linuxFlathub:
case Channel.linuxSnap:
case Channel.linuxDeb:
final updaterPath = Util.homePathJoin(_updaterBin);
// Check the updater binary is exists
if (!await File(updaterPath).exists()) {
await launchUrl(
Uri.parse(
'https://github.com/GopeedLab/gopeed/releases/tag/v$version'),
mode: LaunchMode.externalApplication);
break;
}
/**
*Usage of updater command:
-pid int
PID of the process to update
-channel string
Update channel
-asset string
Path to the package asset
-log string
Log file path
*/
await Process.run(updaterPath, [
"-pid",
pid.toString(),
"-channel",
_channel!.name,
"-asset",
newVersionAssetPath,
"-log",
path.join(logsDir(), "updater.log")
]);
break;
case Channel.androidApk:
await InstallPlugin.installApk(newVersionAssetPath);
break;
default:
await launchUrl(
Uri.parse(
'https://github.com/GopeedLab/gopeed/releases/tag/v$version'),
mode: LaunchMode.externalApplication);
break;
}
}
String _getAssetName(String version) {
final arch = getArch();
String commonArchName() {
return switch (arch) {
Architecture.ia32 => "386",
Architecture.x64 => "amd64",
_ => arch.name
};
}
switch (_channel) {
case Channel.windowsInstaller:
return 'Gopeed-v$version-windows-${commonArchName()}.zip';
case Channel.windowsPortable:
return 'Gopeed-v$version-windows-${commonArchName()}-portable.zip';
case Channel.macosDmg:
return 'Gopeed-v$version-macos.dmg';
case Channel.linuxDeb:
return 'Gopeed-v$version-linux-${commonArchName()}.deb';
case Channel.androidApk:
final apkArchName = switch (arch) {
Architecture.arm => "armeabi-v7a",
Architecture.arm64 => "arm64-v8a",
Architecture.x64 => "x86_64",
_ => null
};
var apkNamePrefix = "Gopeed-v$version-android";
if (apkArchName != null) {
apkNamePrefix += "-$apkArchName";
}
return "$apkNamePrefix.apk";
default:
return "";
}
}
Future<String> _getAssetPath(String version) async {
return path.join(
(await getTemporaryDirectory()).path, _getAssetName(version));
}
+43
View File
@@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@@ -153,4 +155,45 @@ class Util {
timer = Timer(Duration(milliseconds: ms), fn); timer = Timer(Duration(milliseconds: ms), fn);
}; };
} }
static String homePathJoin(String fileName) {
final execPath = Platform.resolvedExecutable;
final execDir = path.dirname(execPath);
return path.join(execDir, fileName);
}
static Future<void> installAsset(String assetPath, String targetPath,
{bool executable = false}) async {
Future<List<int>> getAssetData() async {
final asset = await rootBundle.load(assetPath);
return asset.buffer.asUint8List(asset.offsetInBytes, asset.lengthInBytes);
}
// Check if target file is not installed
if (!await File(targetPath).exists()) {
final assetData = await getAssetData();
final file = File(targetPath);
await file.writeAsBytes(assetData);
// Add execute permission when file first created
if (executable && !Platform.isWindows) {
await Process.run('chmod', ['+x', targetPath]);
}
return;
}
// Check if target file needs to be updated
final assetData = await getAssetData();
if (_md5(assetData) != await _md5File(File(targetPath))) {
await File(targetPath).writeAsBytes(assetData);
return;
}
}
static String _md5(List<int> data) {
return md5.convert(data).toString();
}
static Future<String> _md5File(File file) async {
return file.openRead().transform(md5).first.toString();
}
} }
@@ -10,6 +10,6 @@ Terminal=false
Exec=gopeed %U Exec=gopeed %U
Icon=com.gopeed.Gopeed Icon=com.gopeed.Gopeed
Type=Application Type=Application
Categories=Utility;Internet Categories=Utility;Network;
Keywords=Bittorrent;Downloader; Keywords=Bittorrent;Downloader;
MimeType=x-scheme-handler/gopeed;x-scheme-handler/magnet;application/x-bittorrent; MimeType=x-scheme-handler/gopeed;x-scheme-handler/magnet;application/x-bittorrent;
+55 -46
View File
@@ -5,15 +5,15 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "72.0.0" version: "76.0.0"
_macros: _macros:
dependency: transitive dependency: transitive
description: dart description: dart
source: sdk source: sdk
version: "0.3.2" version: "0.3.3"
after_layout: after_layout:
dependency: transitive dependency: transitive
description: description:
@@ -26,10 +26,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.7.0" version: "6.11.0"
animations: animations:
dependency: transitive dependency: transitive
description: description:
@@ -90,10 +90,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.12.0"
badges: badges:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -106,10 +106,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
build: build:
dependency: transitive dependency: transitive
description: description:
@@ -178,10 +178,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
checkable_treeview: checkable_treeview:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -210,10 +210,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
code_builder: code_builder:
dependency: transitive dependency: transitive
description: description:
@@ -226,10 +226,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.19.1"
contentsize_tabbarview: contentsize_tabbarview:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -271,7 +271,7 @@ packages:
source: hosted source: hosted
version: "0.3.4+2" version: "0.3.4+2"
crypto: crypto:
dependency: transitive dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
@@ -346,10 +346,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@@ -546,6 +546,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.0" version: "4.3.0"
install_plugin:
dependency: "direct main"
description:
path: "."
ref: cf08af829f4a4145634f8a047108f505fdbe5eaa
resolved-ref: cf08af829f4a4145634f8a047108f505fdbe5eaa
url: "https://github.com/hui-z/flutter_install_plugin.git"
source: git
version: "2.1.0"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@@ -598,18 +607,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.5" version: "10.0.8"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.5" version: "3.0.9"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
@@ -662,18 +671,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: macros name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2-main.4" version: "0.1.3-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.17"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -702,10 +711,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -750,10 +759,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.1"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1079,7 +1088,7 @@ packages:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.0"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
@@ -1100,10 +1109,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.1"
sprintf: sprintf:
dependency: transitive dependency: transitive
description: description:
@@ -1116,18 +1125,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.1" version: "1.12.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@@ -1140,10 +1149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.4.1"
styled_widget: styled_widget:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1156,18 +1165,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.2"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.2" version: "0.7.4"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@@ -1292,10 +1301,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.5" version: "14.3.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@@ -1385,5 +1394,5 @@ packages:
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.24.0"
+7 -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.6.7+1 version: 1.6.11+1
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
@@ -71,6 +71,11 @@ dependencies:
contentsize_tabbarview: ^0.0.2 contentsize_tabbarview: ^0.0.2
win32_registry: ^1.1.5 win32_registry: ^1.1.5
share_handler: ^0.0.22 share_handler: ^0.0.22
crypto: ^3.0.6
install_plugin:
git:
url: https://github.com/hui-z/flutter_install_plugin.git
ref: cf08af829f4a4145634f8a047108f505fdbe5eaa
dependency_overrides: dependency_overrides:
permission_handler_windows: permission_handler_windows:
git: git:
@@ -108,7 +113,7 @@ flutter:
- assets/tray_icon/ - assets/tray_icon/
- assets/extension/ - assets/extension/
# Browser extension native host binary, see https://github.com/GopeedLab/gopeed/tree/main/cmd/host # Browser extension native host binary, see https://github.com/GopeedLab/gopeed/tree/main/cmd/host
- assets/host/ - assets/exec/
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware
+169
View File
@@ -0,0 +1,169 @@
# Gopeed Flutter V2 UI - AI Coding Instructions
## Response Rules
Respond in the same language as user's question
## Project Overview
This is the Flutter V2 UI for **Gopeed** (Go Speed), a high-speed download manager built with Golang + Flutter. This Flutter app is a **desktop-focused UI redesign** with cross-platform support (macOS, Windows, Linux, iOS, Android, Web).
**Critical Context**: This Flutter UI communicates with a Go backend REST API (not in this workspace). The backend handles download operations, task management, and file I/O. The UI is purely presentational with future API integration planned.
## Architecture & Structure
### Module Organization (Feature-First)
```
lib/
├── main.dart # App entry, window config, router setup
├── routes/ # Centralized routing
│ ├── app_routes.dart # go_router configuration
│ └── route_names.dart # Route path constants
├── modules/ # Feature modules (screens + widgets)
│ ├── home/ # Shell layout with sidebar
│ ├── task/ # Download task management UI
│ └── settings/ # Settings screen
└── components/ # Shared UI components (gp_* prefix)
```
**Key Pattern**: Features are organized in `modules/` with `screens/` and `widgets/` subdirectories. Shared components use `gp_` prefix (e.g., `GpGradientButton`, `GpIconButton`).
### Routing Architecture
- Uses `go_router` with `ShellRoute` for persistent sidebar navigation
- `HomeScreen` wraps all routes and provides the sidebar layout
- Route paths defined in `RouteNames` class (prevents magic strings)
- Current route detection via `GoRouterState.of(context).matchedLocation`
## Critical Patterns & Conventions
### Component Design System
1. **Gradient Buttons** (`GpGradientButton`): Primary actions, teal-to-green gradient (Colors: `0xFF3ACBBE` → `0xFF2CDB90`)
2. **Outline Buttons** (`GpOutlineButton`): Secondary actions, border-only with teal accent (`0xFF39CDB9`)
3. **Icon Buttons** (`GpIconButton`): Tertiary actions with hover effects (`0xFF4883A2` on hover)
### Design Implementation
- **Glass Morphism**: `TaskItem` widget uses `BackdropFilter` with blur (24px) and layered gradients
- **Color Palette**: Dark blue theme (`0xFF131956` background, `0xFF000E4B` sidebar, teal/green accents)
- **Comments Reference Design Specs**: Many widgets have detailed comments like "Rectangle 82: 主容器" mapping to Figma/design files
### Widget Documentation Style
Components use triple-slash (`///`) documentation:
```dart
/// TaskItem - 任务项组件
///
/// 一个带有毛玻璃效果和渐变边框的任务项容器
class TaskItem extends StatelessWidget {
/// 左侧图标
final dynamic icon;
```
**Convention**: Document widget purpose, parameters, and design intent in Chinese. Keep inline comments for implementation details.
### State Management
- Currently uses `StatefulWidget` with local state (e.g., `TaskScreen._selectedTabIndex`)
- **Future Integration**: Will integrate with backend API - prepare for state management library (GetX/Riverpod likely based on Gopeed ecosystem)
### Window Management
Desktop apps use `window_manager` package:
- Initial size: 1024x768, minimum: 800x600
- Hidden title bar (`TitleBarStyle.hidden`) for custom UI
- Configured in `main.dart` before `runApp()`
## Development Workflows
### Running the App
```bash
# Desktop (macOS/Windows/Linux)
flutter run -d macos # or windows/linux
# Mobile
flutter run -d ios # requires Xcode
flutter run -d android
# Web
flutter run -d chrome
```
### Building
```bash
flutter build macos --release
flutter build windows --release
flutter build linux --release
```
### Dependencies
Key packages in `pubspec.yaml`:
- `window_manager: ^0.4.3` - Desktop window control
- `flutter_svg: ^2.0.17` - SVG asset support
- `go_router: ^14.8.1` - Declarative routing
Assets in `assets/icons/` (e.g., `gopeed_sidebar.svg`, `pause.png`)
## Backend Integration (Future)
**Not Yet Implemented**: Based on parent project structure, this UI will connect to Go REST API:
- API endpoints: `/api/task`, `/api/resolve`, `/api/config` (reference: `pkg/rest/api.go` in parent)
- Task operations: create, pause, continue, delete
- Real-time progress updates via events/WebSocket
**When Implementing API**:
1. Create `lib/api/` or `lib/services/` directory
2. Use `http` or `dio` package for REST calls
3. Models should match Go backend structs (e.g., `Task`, `Progress`)
4. Replace mock data in `TaskScreen._buildTaskList()` with API calls
## Testing & Quality
- Lints: Uses `package:flutter_lints/flutter.yaml` (standard Flutter best practices)
- No tests currently - **add tests when backend integration begins**
- Desktop focus: Prioritize macOS/Windows, mobile is secondary
## Common Tasks
### Adding a New Screen
1. Create `lib/modules/[feature]/screens/[screen_name].dart`
2. Add route to `RouteNames` constant
3. Register route in `AppRoutes.getRouter()` under `ShellRoute.routes`
### Adding Shared Component
1. Create `lib/components/gp_[component_name].dart`
2. Follow naming: `Gp` prefix + PascalCase
3. Document with `///` comments
4. Support flexible sizing/theming via parameters
### Design-to-Code Workflow
When implementing from design files:
- Preserve design layer names in comments (e.g., "Rectangle 82")
- Use exact hex colors from specs
- Document gradient stops and blur sigma values
- Stack layers match design hierarchy (bottom-to-top)
## Important Notes
- **Language**: UI text is in Chinese (e.g., "下载中", "已完成", "任务失败")
- **Branch Context**: Current branch is `feat/new_ui` - this is a redesign
- **Desktop First**: Window management, mouse hover states prioritized
- **Parent Project**: Part of larger Gopeed monorepo with Go backend (outside this workspace)
---
**When uncertain**: Check `lib/modules/task/widgets/task_item.dart` for complex widget patterns, `lib/routes/app_routes.dart` for routing examples, or `lib/main.dart` for app initialization.
+45
View File
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+45
View File
@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: android
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: ios
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: linux
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: macos
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: web
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: windows
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+3
View File
@@ -0,0 +1,3 @@
{
"dart.mcpServer": true
}
+16
View File
@@ -0,0 +1,16 @@
# gopeed
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
+28
View File
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+14
View File
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.gopeed.gopeed"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.gopeed.gopeed"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="gopeed"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package com.gopeed.gopeed
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+21
View File
@@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
+25
View File
@@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_1_4784"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath><clipPath id="master_svg1_1_4785"><rect x="1" y="1" width="14" height="13.98634147644043" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_1_4784)"><g clip-path="url(#master_svg1_1_4785)"><g></g><g></g><g><path d="M14.407077875,3.7902927L11.616782875,3.7902927L11.616782875,2.3951464C11.616782875,1.62781584,10.995943075,1,10.221636775,1L5.757169675,1C4.989839275,1,4.362023375,1.62781584,4.362023375,2.3951464L4.362023375,3.7902927L1.5717306149999999,3.7902927C1.4182645649999999,3.7902927,1.2787498849999999,3.8530741,1.174113885,3.9507344C1.076453455,4.048394699999999,1.013671875,4.187909400000001,1.013671875,4.3483515C1.013671875,4.655283,1.264798195,4.90641,1.571730435,4.90641L2.4646240749999997,4.90641L2.938973775,13.556316C2.938973775,14.330623,3.531911175,14.951464,4.271338975,14.951464L11.714442875,14.951464C12.446894875,14.951464,13.039831875,14.330623,13.039831875,13.556316L13.514181875,4.90641L14.414048875,4.90641C14.567516875,4.90641,14.707032875,4.8436277,14.811668875,4.7459674C14.909328875,4.6413317,14.972110875,4.5018167,14.972110875,4.3483505000000005C14.965135875,4.0414186,14.714007875,3.790292,14.407077875,3.7902927ZM5.478140375,2.3951464C5.478140375,2.2416803,5.603702975,2.1161171000000003,5.757169675,2.1161171000000003L10.221636775,2.1161171000000003C10.375103975,2.1161171000000003,10.500666575,2.2416803,10.500666575,2.3951464L10.500666575,3.7902927L5.478140375,3.7902927L5.478140375,2.3951464ZM11.923715875,13.50051L11.923715875,13.563292C11.923715875,13.730709,11.812103875,13.84232,11.714442875,13.84232L4.264363075,13.84232C4.159727075,13.84232,4.048114975,13.730709,4.048114975,13.563292L4.048114975,13.50051L3.587716775,4.90641L3.587716775,4.8994339L12.391089875,4.8994339L12.391089875,4.90641L11.923715875,13.50051Z" fill="#39CDB9" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M6.587355315,11.819402199999999C6.280422925,11.819402199999999,6.029296875,11.5682764,6.029296875,11.261343499999999L6.029296875,7.3549335C6.029296875,7.04800108,6.280422925,6.796875,6.587355315,6.796875C6.894287705,6.796875,7.145413775,7.04800162,7.145413775,7.3549335L7.145413775,11.261343499999999C7.145413775,11.568275,6.901263175,11.819402199999999,6.587355315,11.819402199999999ZM9.377648075,11.819402199999999C9.070715675,11.819402199999999,8.819589575,11.5682764,8.819589575,11.261343499999999L8.819589575,7.3549335C8.819589575,7.04800108,9.070715675,6.796875,9.377648075,6.796875C9.684580375,6.796875,9.935706875000001,7.04800162,9.935706875000001,7.3549335L9.935706875000001,11.261343499999999C9.935706875000001,11.568275,9.691555975,11.819402199999999,9.377648075,11.819402199999999Z" fill="#39CDB9" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="66" height="66" viewBox="0 0 66 66"><defs><clipPath id="master_svg0_1_8641"><rect x="9" y="8" width="48" height="48" rx="0"/></clipPath><linearGradient x1="0.36395263671875" y1="1" x2="0.63507080078125" y2="0.3428141176700592" id="master_svg1_1_2441"><stop offset="0%" stop-color="#3ACBBE" stop-opacity="1"/><stop offset="100%" stop-color="#2CDB90" stop-opacity="1"/></linearGradient></defs><g><g><rect x="0" y="0" width="66" height="66" rx="8" fill="#FFFFFF" fill-opacity="0"/></g><g><g clip-path="url(#master_svg0_1_8641)"><g><g><ellipse cx="33.000285148620605" cy="32" rx="24" ry="24" fill="url(#master_svg1_1_2441)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g><g><g style="opacity:0.9990000128746033;"><path d="M53.6015625,43.78803525390625C53.4974625,44.01173525390625,53.3803625,44.23043525390625,53.2499625,44.44423525390625C52.9830625,44.885535253906255,52.6939625,45.307335253906245,52.3827625,45.70993525390625C51.9353625,45.35303525390625,51.4353625,45.07963525390625,50.8827625,44.88963525390625C50.1694625,44.700135253906254,49.4585625,44.50483525390625,48.7499625,44.30363525390625C49.0318625,41.60323525390625,48.4380625,39.11883525390625,46.9687625,36.85052525390625C45.0159625,33.93152525390625,42.336262500000004,32.01747525390625,38.9296625,31.10833525390625C35.2645625,30.47861525390625,31.9833625,31.32236525390625,29.0859625,33.63958525390625C28.3107625,34.27408525390625,27.6154625,34.98503525390625,26.999962500000002,35.772395253906254C26.1911625,36.91722525390625,25.3551625,38.04222525390625,24.4921625,39.14743525390625C23.5375625,38.52874525390625,22.615762500000002,37.86466525390625,21.7265625,37.155205253906246C20.1772525,36.23210525390625,18.6929025,36.32585525390625,17.2734425,37.436455253906246C16.4707025,38.12795525390625,15.9785125,38.99513525390625,15.7968725,40.03803525390625C15.7485025,40.47003525390625,15.7953725,40.891935253906254,15.9375025,41.30363525390625C16.2344025,41.99923525390625,16.5078325,42.70233525390625,16.7578125,43.41303525390625C15.474282500000001,43.843735253906246,14.3648925,44.54683525390625,13.4296825,45.52243525390625C13.0149845,44.981835253906254,12.6556075,44.40373525390625,12.3515625,43.78803525390625C12.8900715,43.33453525390625,13.4760125,42.94393525390625,14.1093725,42.61613525390625C13.9256725,42.06883525390625,13.7850425,41.50633525390625,13.6875025,40.92863525390625C13.6562525,40.53803525390625,13.6562525,40.14733525390625,13.6875025,39.75673525390625C13.9897525,37.48811525390625,15.1850625,35.88653525390625,17.2734425,34.95208525390625C19.8165025,34.045005253906254,22.0586725,34.52158525390625,23.999962500000002,36.38177525390625C25.8556625,32.87884525390625,28.6760625,30.52731525390625,32.4609625,29.32708425390625C36.6625625,28.16083425390625,40.6313625,28.69206905390625,44.3671625,30.92083525390625C48.4145625,33.57456525390625,50.5239625,37.332345253906254,50.6953625,42.19423525390625C51.1473625,42.33423525390625,51.5848625,42.513935253906254,52.0077625,42.73333525390625C52.5658625,43.050935253906246,53.0971625,43.40253525390625,53.6015625,43.78803525390625Z" fill-rule="evenodd" fill="#FEFEFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g><g><g style="opacity:0.9990000128746033;"><path d="M36.0234625,55.5546375C33.9766825,55.8040375,31.929792499999998,55.8040375,29.8828125,55.5546375C28.0377225,53.4753375,26.1939825,51.3972375,24.3515625,49.3203175C25.7890825,49.2890475,27.2265425,49.2578275,28.6640625,49.2265575C28.6640625,45.9921875,28.6640625,42.7578175,28.6640625,39.5234375C31.5390425,39.5234375,34.4140625,39.5234375,37.2890625,39.5234375C37.2890625,42.7734175,37.2890625,46.0234575,37.2890625,49.2734375C38.7110625,49.2656075,40.1328625,49.2734375,41.5546625,49.2968775C39.7050625,51.3799375,37.8613625,53.4658375,36.0234625,55.5546375Z" fill-rule="evenodd" fill="#FEFEFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g><g><g><path d="M28.6640675,49.2266788453125C27.2265475,49.2579440953125,25.7890775,49.2891635953125,24.3515625,49.3204288953125C24.3261094,49.3176164953125,24.31049967,49.3020066953125,24.3046875,49.2735538953125C25.7574875,49.2266788453125,27.210607500000002,49.2110691053125,28.6640675,49.2266788453125Z" fill-rule="evenodd" fill="#ADE2B5" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><defs><clipPath id="master_svg0_1_4880"><rect x="2" y="2" width="16" height="16" rx="0"/></clipPath><clipPath id="master_svg1_1_4881"><rect x="3" y="3" width="14" height="13.98634147644043" rx="0"/></clipPath></defs><g><g></g><g clip-path="url(#master_svg0_1_4880)"><g clip-path="url(#master_svg1_1_4881)"><g><path d="M5.587890625,6.90625L14.391263925,6.90625L14.391263925,6.9132257244L5.587890625,6.9132257244L5.587890625,6.90625Z" fill="#000000" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M6.3623846875000005,6.90678420625L13.6171445875,6.91375980625L13.6171445875,6.90678420625L6.3623846875000005,6.90678420625ZM4.4091796875,5.79066713065L6.3623846875000005,5.79764285525L6.3623846875000005,5.79066713065L4.4091796875,5.79066713065ZM13.6171445875,5.79066713065L15.5703506875,5.79764285525L15.5703506875,5.79066713065L13.6171445875,5.79066713065ZM7.4785015875,5.79066713065L12.5010289875,5.79066713065L12.5010289875,5.78369140625L7.4785015875,5.79066713065Z" fill="#000000" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M16.407077875,5.7902927L13.616782875,5.7902927L13.616782875,4.3951464C13.616782875,3.6278158400000002,12.995943075,3,12.221636775,3L7.757169675,3C6.989839275,3,6.362023375,3.6278158400000002,6.362023375,4.3951464L6.362023375,5.7902927L3.571730615,5.7902927C3.418264565,5.7902927,3.278749885,5.853074100000001,3.174113885,5.9507344C3.0764534550000002,6.048394699999999,3.013671875,6.187909400000001,3.013671875,6.3483515C3.013671875,6.655283,3.264798195,6.90641,3.571730435,6.90641L4.464624075,6.90641L4.938973775,15.556316C4.938973775,16.330623,5.531911174999999,16.951464,6.271338975,16.951464L13.714442875,16.951464C14.446894875,16.951464,15.039831875,16.330623,15.039831875,15.556316L15.514181875,6.90641L16.414048875,6.90641C16.567516875000003,6.90641,16.707032875,6.8436277,16.811668875000002,6.7459674C16.909328875,6.6413317,16.972110875,6.5018167,16.972110875,6.3483505000000005C16.965135875,6.0414186,16.714007875,5.790292,16.407077875,5.7902927ZM7.478140375,4.3951464C7.478140375,4.2416803000000005,7.603702975,4.1161171,7.757169675,4.1161171L12.221636775,4.1161171C12.375103975,4.1161171,12.500666575,4.2416803000000005,12.500666575,4.3951464L12.500666575,5.7902927L7.478140375,5.7902927L7.478140375,4.3951464ZM13.923715875,15.50051L13.923715875,15.563292C13.923715875,15.730709,13.812103875,15.84232,13.714442875,15.84232L6.264363075,15.84232C6.159727075,15.84232,6.048114975,15.730709,6.048114975,15.563292L6.048114975,15.50051L5.5877167750000005,6.90641L5.5877167750000005,6.8994339L14.391089875,6.8994339L14.391089875,6.90641L13.923715875,15.50051Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M8.587355315,13.819402199999999C8.280422925,13.819402199999999,8.029296875,13.5682764,8.029296875,13.261343499999999L8.029296875,9.3549335C8.029296875,9.04800108,8.280422925,8.796875,8.587355315,8.796875C8.894287705,8.796875,9.145413775,9.04800162,9.145413775,9.3549335L9.145413775,13.261343499999999C9.145413775,13.568275,8.901263175,13.819402199999999,8.587355315,13.819402199999999ZM11.377648075,13.819402199999999C11.070715675,13.819402199999999,10.819589575,13.5682764,10.819589575,13.261343499999999L10.819589575,9.3549335C10.819589575,9.04800108,11.070715675,8.796875,11.377648075,8.796875C11.684580375,8.796875,11.935706875000001,9.04800162,11.935706875000001,9.3549335L11.935706875000001,13.261343499999999C11.935706875000001,13.568275,11.691555975,13.819402199999999,11.377648075,13.819402199999999Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g><g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><g><g></g><g><path d="M16.84375,10C16.84375,6.1340067000000005,13.744721,3,9.921875,3C6.0990286000000005,3,3,6.1340067000000005,3,10C3,13.865993,6.0990286000000005,17,9.921875,17C13.744721,17,16.84375,13.865993,16.84375,10ZM3.95474136,10C3.95474136,6.6672468,6.6263176999999995,3.9655172199999997,9.921875,3.9655172199999997C13.217431,3.9655172199999997,15.889008,6.6672468,15.889008,10C15.889008,13.332752,13.217431,16.034482,9.921875,16.034482C6.6263176999999995,16.034482,3.95474136,13.332752,3.95474136,10ZM9.921875,7.3448277C10.317341299999999,7.3448277,10.6379309,7.0206199,10.6379309,6.6206894C10.6379309,6.2207594,10.317341299999999,5.8965516000000004,9.921875,5.8965516000000004C9.5264077,5.8965516000000004,9.2058191,6.2207594,9.2058191,6.6206894C9.2058191,7.0206199,9.5264077,7.3448277,9.921875,7.3448277ZM9.4445038,13.862068C9.4445038,14.128689,9.6582303,14.344828,9.921875,14.344828C10.1855187,14.344828,10.3992457,14.128689,10.3992457,13.862068L10.3992457,8.551724400000001C10.3992457,8.2851038,10.1855187,8.0689659,9.921875,8.0689659C9.6582303,8.0689659,9.4445038,8.2851038,9.4445038,8.551724400000001L9.4445038,13.862068Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M7.2079058,16.495876000000003Q8.5033803,17.050001,9.921875499999999,17.050001Q11.3403711,17.050001,12.6358452,16.495876000000003Q13.887032,15.960696,14.851933,14.984904Q15.816778,14.009171,16.345923,12.744009Q16.893751,11.4341812,16.893751,10.000000499999999Q16.893751,8.565819699999999,16.345923,7.2559915Q15.816778,5.9908307,14.851933,5.0150959Q13.887032,4.0393038,12.6358452,3.50412351Q11.3403721,2.949999996,9.921875499999999,2.949999996Q8.503378900000001,2.949999996,7.2079053,3.50412363Q5.9567192,4.0393038,4.9918171000000005,5.0150962Q4.0269737,5.9908297,3.49782753,7.2559919Q2.949999966,8.5658207,2.949999996,10.000000499999999Q2.949999996,11.4341822,3.4978272,12.74401Q4.0269725,14.009171,4.991817,14.984904Q5.9567177000000004,15.960696,7.2079058,16.495876000000003ZM12.5965185,16.403935Q11.3198843,16.950001,9.921875499999999,16.950001Q8.5238671,16.950001,7.2472324,16.403935Q6.0140541,15.876458,5.0629234,14.914592Q4.1117355,13.952669,3.59008318,12.7054234Q3.050000004,11.4141111,3.050000004,10.000000499999999Q3.049999937,8.585890299999999,3.59008348,7.2945771Q4.1117356,6.0473327999999995,5.0629234,5.0854089Q6.0140541,4.1235434,7.2472324,3.5960658800000003Q8.5238681,3.050000004,9.921875499999999,3.050000004Q11.3198833,3.050000004,12.5965185,3.59606576Q13.829698,4.1235434,14.780828,5.0854087Q15.732015,6.0473313,16.253667,7.2945766Q16.793751,8.5858908,16.793751,10.000000499999999Q16.793751,11.4141083,16.253667,12.7054224Q15.732014,13.952671,14.780828,14.914592Q13.829698,15.876457,12.5965185,16.403935ZM7.5795341,15.606234Q8.697622800000001,16.084482,9.921875499999999,16.084482Q11.1461277,16.084482,12.2642155,15.606234Q13.344068,15.14434,14.176828,14.302179Q15.009533,13.460075,15.466209,12.368187Q15.939009,11.2377491,15.939009,10.000000499999999Q15.939009,8.762252799999999,15.466209,7.6318145Q15.009533,6.5399244,14.176827,5.6978199Q13.344066,4.8556588000000005,12.2642155,4.3937656Q11.1461296,3.91551727,9.921875499999999,3.9155177500000002Q8.6976199,3.91551763,7.5795336,4.3937658Q6.4996829,4.8556592,5.6669211,5.6978199Q4.8342174,6.539922,4.3775403,7.6318145Q3.9047414099999997,8.762253300000001,3.9047404500000003,10.000000499999999Q3.90474069,11.2377472,4.37754,12.368187Q4.8342161,13.460076,5.6669209,14.302179Q6.4996817,15.14434,7.5795341,15.606234ZM12.2248888,15.514292Q11.1256399,15.984483,9.921875499999999,15.984483Q8.7181096,15.984483,7.6188612,15.514292Q6.5570183,15.060101,5.738027300000001,14.231867Q4.9189789,13.403574,4.4697959,12.3296003Q4.0047406,11.2176771,4.0047405000000005,10.000000499999999Q4.0047414,8.7823229,4.4697963000000005,7.6703997Q4.9189793,6.5964255000000005,5.738027600000001,5.7681328999999995Q6.5570183,4.9398985,7.6188607,4.4857081999999995Q8.7181096,4.0155177,9.921875499999999,4.0155177Q11.1256399,4.0155172,12.2248888,4.4857078999999995Q13.286732,4.9398986,14.105721,5.7681328999999995Q14.924769,6.5964255000000005,15.373953,7.6703997Q15.839008,8.7823238,15.839008,10.000000499999999Q15.839008,11.2176762,15.373953,12.3296003Q14.924768,13.403576,14.105722,14.231867Q13.286733,15.060101,12.2248888,15.514292ZM9.379993899999999,6.0734905999999995Q9.1558194,6.3001955,9.1558194,6.6206896Q9.1558194,6.9411843,9.379993899999999,7.1678891Q9.6044002,7.3948278,9.921875499999999,7.3948278Q10.2393503,7.3948278,10.463756100000001,7.1678886Q10.6879311,6.9411831,10.6879311,6.6206887000000005Q10.6879311,6.3001955,10.463756100000001,6.0734904Q10.2393503,5.8465517,9.921875499999999,5.8465517Q9.6044002,5.8465517,9.379993899999999,6.0734905999999995ZM9.451099899999999,7.0975766Q9.255819299999999,6.9000916,9.255819299999999,6.6206896Q9.255819299999999,6.341287599999999,9.451099Line truncated

After

Width:  |  Height:  |  Size: 6.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><defs><clipPath id="master_svg0_1_4866"><rect x="0" y="0" width="20" height="20" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_1_4866)"><g><path d="M6.458075525,9.999704725Q6.458075525,10.071365625,6.451051425,10.142681225Q6.444027425,10.213996925,6.430046325,10.284280625000001Q6.416065925,10.354564525,6.395263225,10.423139525Q6.3744607250000005,10.491714725,6.3470363249999995,10.557920424999999Q6.319612525,10.624126425,6.285830725,10.687325725Q6.252049425,10.750525025,6.212235424999999,10.810108625Q6.172421925,10.869692525,6.126959825,10.925087225Q6.081497425,10.980481625,6.030824225,11.031153425Q5.980151225,11.081825225,5.924755125,11.127286425Q5.869359025,11.172747625,5.8097737249999994,11.212559925Q5.750188625,11.252372725,5.686987925,11.286153525Q5.623786925,11.319934325,5.557579325,11.347357525Q5.491371825,11.374781125,5.422794925,11.395583125Q5.354218225,11.416385125,5.283932425,11.430365525Q5.213646925,11.444345925,5.142329325,11.451370025Q5.071011825,11.458393825,4.999349125,11.458393825Q4.927686525,11.458393825,4.856369025,11.451370025Q4.785051425,11.444345925,4.714765625,11.430365325Q4.644480125,11.416385125,4.5759033250000005,11.395583125Q4.507326365,11.374781125,4.441118655,11.347357225Q4.374911065,11.319934125,4.311710235,11.286153325Q4.248509405,11.252372725,4.188924075,11.212560225Q4.129338865,11.172747625,4.073942895,11.127286425Q4.018546935,11.081825225,3.967873755,11.031153425Q3.917200655,10.980481625,3.871738345,10.925087225Q3.826276065,10.869692525,3.786462415,10.810108625Q3.746648805,10.750525025,3.712867255,10.687325725Q3.679085735,10.624126425,3.651661625,10.557920424999999Q3.624237537,10.491714725,3.603434969,10.423139525Q3.582632408,10.354564625,3.568651723,10.284280825Q3.554671041,10.213997025,3.547646876,10.142681325Q3.54062271118,10.071365625,3.54062271118,9.999704725Q3.54062271118,9.928043825,3.5476468774,9.856728125Q3.554671044,9.785412325,3.568651728,9.715128425Q3.582632415,9.644844625,3.60343498,9.576269525Q3.624237545,9.507694485,3.651661645,9.441488445Q3.679085745,9.375282525,3.712867285,9.312083365Q3.746648815,9.248884205,3.786462425,9.189300415Q3.826276095,9.129716695,3.871738375,9.074322165Q3.917200685,9.018927635,3.967873785,8.968255725Q4.018546935,8.917583945,4.073942895,8.872122795Q4.129338865,8.826661645,4.188924075,8.786849035Q4.248509405,8.747036444999999,4.311710175,8.713255765Q4.374911015,8.679475115,4.441118595,8.652051705Q4.507326245,8.624628313,4.575903225,8.603826277Q4.644480025,8.583024248,4.714765625,8.569043921Q4.785051425,8.555063596,4.856369025,8.5480396105Q4.927686525,8.541015625,4.999349125,8.541015625Q5.071011825,8.541015625,5.142329325,8.5480396105Q5.213646925,8.555063596,5.283932425,8.569043921Q5.354218225,8.583024248,5.422795025,8.603826277Q5.491371825,8.624628313,5.557579325,8.652051705Q5.623786925,8.679475115,5.686987925,8.713255775Q5.750188625,8.747036444999999,5.8097737249999994,8.786849035Q5.869359025,8.826661645,5.924755125,8.872122795Q5.980151225,8.917583945,6.030824225,8.968255725Q6.081497425,9.018927635,6.126959825,9.074322165Q6.172421925,9.129716695,6.212235424999999,9.189300415Q6.252049425,9.248884205,6.285830725,9.312083365Q6.319612525,9.375282525,6.3470363249999995,9.441488445Q6.3744607250000005,9.507694485,6.395263425,9.576269525Q6.416066125,9.644844625,6.430046525,9.715128425Q6.444027425,9.785412325,6.451051425,9.856728125Q6.458075525,9.928043825,6.458075525,9.999704725Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M11.457681925,10.0003252875Q11.457681925,10.0719686875,11.450659725,10.1432669875Q11.443637325000001,10.2145652875,11.429660325,10.2848318875Q11.415683525,10.3550986875,11.394886525,10.4236569875Q11.374089525,10.4922152875,11.346672524999999,10.5584048875Q11.319256025,10.624594887499999,11.285483625,10.6877786875Q11.251711325,10.7509624875,11.211908325,10.8105315875Q11.172105825,10.870100987499999,11.126655625,10.9254819875Q11.081205825,10.9808630875,11.030545925,11.0315222875Q10.979886525,11.082181887499999,10.924505425,11.1276318875Q10.869124424999999,11.1730818875,10.809555025,11.2128846875Q10.749985925,11.2526876875,10.686802125,11.2864601875Q10.623618324999999,11.3202325875,10.557428325,11.347649087499999Q10.491238725,11.3750660875,10.422680425,11.3958630875Q10.354122125,11.4166600875,10.283855425,11.4306368875Q10.213588825,11.444613887500001,10.142290625,11.4516362875Q10.070992125,11.4586584875,9.999348725,11.4586584875Q9.927705525,11.4586584875,9.856407125,11.4516362875Q9.785108825,11.444613887500001,9.714842125,11.4306368875Q9.644575325,11.4166600875,9.576017025,11.3958630875Q9.507458685,11.3750660875,9.441268805,11.347649087499999Q9.375079035,11.3202325875,9.311895315,11.2864601875Q9.248711525000001,11.2526878875,9.189142225,11.2128848875Q9.129572985,11.1730823875,9.074191985,11.1276321875Q9.018810955,11.08218238Line truncated

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><defs><clipPath id="master_svg0_1_4888"><rect x="2" y="2" width="16" height="16" rx="0"/></clipPath></defs><g><g></g><g clip-path="url(#master_svg0_1_4888)"><g><path d="M12.8443293375,4.57733953C12.8443293375,4.13566598,13.2034464375,3.77734375,13.644324337499999,3.77734375C14.0861249375,3.77734375,14.4443359375,4.13775983,14.4443359375,4.57733953L14.4443359375,15.24397675C14.4443359375,15.68563775,14.085217937500001,16.043972750000002,13.644324337499999,16.043972750000002C13.2025399375,16.043972750000002,12.8443293375,15.68357175,12.8443293375,15.24397675L12.8443293375,4.57733953ZM6.4443359375,4.57733953C6.4443359375,4.13566598,6.8034537475,3.77734375,7.2443456675,3.77734375C7.6861306375,3.77734375,8.0443413375,4.13775983,8.0443413375,4.57733953L8.0443413375,15.24397675C8.0443413375,15.68563775,7.6852232375,16.043972750000002,7.2443456675,16.043972750000002C6.8025606575,16.043972750000002,6.4443359375,15.68357175,6.4443359375,15.24397675L6.4443359375,4.57733953Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g><g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="12" height="13.508296966552734" viewBox="0 0 12 13.508296966552734"><g><path d="M1.1323885,13.508297C0.93743527,13.508297,0.74248183,13.456448,0.56619424,13.354822C0.21154511,13.149499,0,12.782407,0,12.373834L0,1.1349809C0,0.7243346,0.21154511,0.3593156,0.56619424,0.1539924C0.92084342,-0.051330797,1.3439337,-0.051330797,1.7006568,0.1539924L11.433805,5.7723818C11.788455,5.9777055,12,6.3447976,12,6.7533703C12,7.1640162,11.788455,7.5290356,11.433805,7.7343583L1.7006568,13.354822C1.5222954,13.456448,1.3273419,13.508297,1.1323885,13.508297ZM1.3273419,1.4709644L1.3273419,12.037849L10.477705,6.7533703L1.3273419,1.4709644Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

After

Width:  |  Height:  |  Size: 818 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><defs><clipPath id="master_svg0_1_4872"><rect x="2" y="2" width="16" height="16" rx="0"/></clipPath><clipPath id="master_svg1_1_4873"><rect x="1.9990234375" y="2" width="16" height="16" rx="0"/></clipPath></defs><g><g></g><g clip-path="url(#master_svg0_1_4872)"><g clip-path="url(#master_svg1_1_4873)"><g><path d="M15.5990234375,5.400390625C15.8193364375,5.400390625,15.9990244375,5.580078125,15.9990244375,5.800390725L15.9990244375,14.200390825C15.9990244375,14.420703625,15.8193364375,14.600391625,15.5990234375,14.600391625L4.3990235375,14.600391625C4.1787109375,14.600391625,3.9990234375,14.420703625,3.9990234375,14.200390825L3.9990234375,7.2003908249999995C3.9990234375,6.980078225,4.1787109375,6.800390725,4.3990235375,6.800390725L10.4130859375,6.800390725L10.7052741375,6.508203225L11.8130865375,5.400390625L15.5990234375,5.400390625ZM15.5990234375,4.400390625L11.6896486375,4.400390625C11.5037117375,4.400390625,11.3255863375,4.473828129,11.1943359375,4.605078135L9.999023937499999,5.800390725L4.3990235375,5.800390725C3.6255859775,5.800390725,2.9990234375,6.4269533249999995,2.9990234375,7.2003908249999995L2.9990234375,14.200390825C2.9990234375,14.973828625,3.6255859775,15.600391625,4.3990235375,15.600391625L15.5990234375,15.600391625C16.3724614375,15.600391625,16.999024437499997,14.973828625,16.999024437499997,14.200390825L16.999024437499997,5.800390725C16.999024437499997,5.026953165,16.3724614375,4.400390625,15.5990234375,4.400390625Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M13.9990234375,13.583984375L5.9990234375,13.583984375C5.7224609575,13.583984375,5.4990234375,13.360546885,5.4990234375,13.083984375C5.4990234375,12.807421895,5.7224609575,12.583984375,5.9990234375,12.583984375L13.9990234375,12.583984375C14.2755861375,12.583984375,14.4990234375,12.807421895,14.4990234375,13.083984375C14.4990234375,13.360546885,14.2755861375,13.583984375,13.9990234375,13.583984375Z" fill="#FFFFFE" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g><g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_1_4843"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath><linearGradient x1="0.9016574025154114" y1="0.22865968942642212" x2="0.5879987791120295" y2="0.524962739843075" id="master_svg1_1_3423"><stop offset="5.714285746216774%" stop-color="#2EDA94" stop-opacity="1"/><stop offset="100%" stop-color="#39CDB9" stop-opacity="1"/></linearGradient></defs><g clip-path="url(#master_svg0_1_4843)"><g><path d="M6.1838849,9.1654915875L3.50055146,9.1654915875C3.1502182,9.1654915875,2.908551335,8.814825087500001,3.033218078,8.4876584875L5.6995512999999995,1.4889922174999999C5.7734466,1.2951097775,5.9593232,1.1669921875,6.1667179999999995,1.1669921875L10.6660509,1.1669921875C11.0210505,1.1669921875,11.2630501,1.5266588675000001,11.1290512,1.8554921775L9.5765514,5.6661586875L12.4992189,5.6661586875C12.9290514,5.6661586875,13.158384,6.1728252875,12.8748846,6.4958252875L5.7092187,14.6608261875C5.3608856,15.0578261875,4.7150521,14.7138261875,4.8502188,14.2033261875L6.1838849,9.1654915875Z" fill="url(#master_svg1_1_3423)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_1_5108"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_1_5108)"><g><path d="M10.3999934,2.79999578C10.3999934,2.35832223,10.7591105,2,11.199988399999999,2C11.641789,2,12,2.3604160800000002,12,2.79999578L12,13.466633C12,13.908294,11.640882000000001,14.266629,11.199988399999999,14.266629C10.758204,14.266629,10.3999934,13.906228,10.3999934,13.466633L10.3999934,2.79999578ZM4,2.79999578C4,2.35832223,4.35911781,2,4.80000973,2C5.2417947,2,5.6000054,2.3604160800000002,5.6000054,2.79999578L5.6000054,13.466633C5.6000054,13.908294,5.2408873,14.266629,4.80000973,14.266629C4.35822472,14.266629,4,13.906228,4,13.466633L4,2.79999578Z" fill="#39CDB9" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

After

Width:  |  Height:  |  Size: 944 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="12" height="13.508296966552734" viewBox="0 0 12 13.508296966552734"><g><path d="M1.1323885,13.508297C0.93743527,13.508297,0.74248183,13.456448,0.56619424,13.354822C0.21154511,13.149499,0,12.782407,0,12.373834L0,1.1349809C0,0.7243346,0.21154511,0.3593156,0.56619424,0.1539924C0.92084342,-0.051330797,1.3439337,-0.051330797,1.7006568,0.1539924L11.433805,5.7723818C11.788455,5.9777055,12,6.3447976,12,6.7533703C12,7.1640162,11.788455,7.5290356,11.433805,7.7343583L1.7006568,13.354822C1.5222954,13.456448,1.3273419,13.508297,1.1323885,13.508297ZM1.3273419,1.4709644L1.3273419,12.037849L10.477705,6.7533703L1.3273419,1.4709644Z" fill="#39CDB9" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

After

Width:  |  Height:  |  Size: 818 B

+34
View File
@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+43
View File
@@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "[email protected]",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Loaded 100 of 203 files, more files were not shown because too many files have changed in this diff. Show more