67 Commits

Author SHA1 Message Date
558832d188 Update version code 2025-08-10 18:10:59 +08:00
2be88895ed Use image_gallery_saver_plus 2025-08-10 18:09:04 +08:00
1a110e711d update gradle 2025-08-10 17:09:42 +08:00
1ad8329797 Update READMD.md 2025-08-10 15:47:30 +08:00
1cf4da66ad Add auto complete. Close #24 2025-08-10 15:33:48 +08:00
5dad6910fc Add novel following page to initial page setting. Close #17 2025-08-03 18:07:03 +08:00
72eb4a51ea Add navigation action for Settings page in main page 2025-06-28 10:33:13 +08:00
3d49bebc44 Add font fallback for Linux and Windows platforms 2025-06-28 10:27:18 +08:00
e86f8b7f51 Update dependencies. 2025-06-28 10:16:42 +08:00
nyne
20130ef89e Update README.md 2025-06-04 10:09:23 +08:00
铺盖崽
b76fbf040b add linux arm build (#16)
* add linux arm build

* Update build.py

* Update main.yml

* Update main.yml

* Update debian.yaml
2025-02-14 09:18:21 +08:00
018fb11ca5 Update version code 2025-01-30 15:43:09 +08:00
cbc69b4707 Improve UI 2025-01-30 15:03:12 +08:00
1e53e374e4 flutter 3.27.3 2025-01-30 14:59:52 +08:00
0756ebe4e5 fix #12 2025-01-30 14:58:30 +08:00
47ebe9deec Add following novels page.
Close #13
2025-01-30 14:52:00 +08:00
37f84efe05 Add private novel bookmarks.
Close #14
2025-01-30 14:43:40 +08:00
6530f2c57d Improve animation 2025-01-29 21:22:46 +08:00
a3e758831b Improve animation 2025-01-29 20:52:37 +08:00
3e5ae0a39a update telegram 2025-01-29 20:21:21 +08:00
974f739900 fix #9 2024-12-19 16:47:19 +08:00
deb866da63 fix #10 2024-12-19 16:39:32 +08:00
eea77b297a update build script 2024-12-14 22:34:48 +08:00
264c2b0e20 update build script 2024-12-14 22:13:23 +08:00
513e716608 update windows build script 2024-12-14 21:38:34 +08:00
8364b56178 fix android 2024-12-14 21:26:51 +08:00
0d0122be3a Update version code 2024-12-14 18:26:32 +08:00
4f1fd8530a Update build script 2024-12-14 18:24:05 +08:00
82b8eac989 fix #6 2024-12-14 18:08:54 +08:00
95815131fe fix #4 2024-12-14 17:52:12 +08:00
f8439a3cb2 Update README.md 2024-12-14 17:27:16 +08:00
f7a6eb1ac9 fix #8 2024-12-14 17:20:23 +08:00
a10e8c05d5 update font 2024-12-14 17:15:56 +08:00
bd15053c2f Migrate to flutter 3.27.0 2024-12-14 17:08:55 +08:00
nyne
86c6f13282 fix workflow 2024-10-02 21:40:09 +08:00
nyne
b69d2a0950 update workflow 2024-10-02 21:30:45 +08:00
nyne
c897891b2a add archlinux build 2024-10-02 21:15:55 +08:00
nyne
3c73439588 update version code 2024-10-02 21:12:10 +08:00
nyne
2d16502154 add translator for novel 2024-10-02 21:10:22 +08:00
nyne
294498d8a7 update README.md 2024-10-02 16:47:23 +08:00
nyne
20dfbf5125 improve code 2024-10-02 16:45:25 +08:00
nyne
63aa4ee8b0 improve ui 2024-10-02 16:29:45 +08:00
nyne
c8d4b3db88 fix input 2024-10-02 16:26:15 +08:00
wgh19
b1e7adb1c5 verify response data 2024-06-29 19:15:58 +08:00
wgh19
0143a67fa0 add search button for mobile platform 2024-06-18 19:56:52 +08:00
wgh19
eee1141970 update readme.md 2024-06-14 13:09:37 +08:00
wgh19
50a69f77b6 improve ui 2024-06-14 12:50:27 +08:00
wgh19
790ed54d5b Emphasize artworks from following artists 2024-06-14 12:38:50 +08:00
wgh19
21fe14f88b improve ui; update version code 2024-06-14 12:22:37 +08:00
wgh19
a03ad12837 check update 2024-06-13 23:01:01 +08:00
wgh19
426257716f fix save_to_gallery 2024-06-13 22:48:52 +08:00
wgh19
67e01ea69f catch error 2024-06-13 22:24:21 +08:00
wgh19
6cf9ce9c96 improve ui 2024-06-13 20:54:31 +08:00
wgh19
593899af8c disable acrylic effect for windows 10 2024-06-13 12:40:34 +08:00
wgh19
908c26d764 update readme.md 2024-06-12 22:34:53 +08:00
wgh19
eef1af3ad1 update build script 2024-06-12 22:09:29 +08:00
nyne
b97c7cde25 add linux workflow 2024-06-12 21:58:26 +08:00
nyne
6118fc30f8 update version code; add linux build script 2024-06-12 21:51:20 +08:00
nyne
4476ad7f90 linux support 2024-06-12 21:30:23 +08:00
wgh19
7c8fabf52c add shortcuts 2024-06-12 19:17:32 +08:00
wgh19
70da478044 improve login 2024-06-12 17:03:45 +08:00
wgh19
b14c2682a7 save to gallery 2024-06-12 16:45:39 +08:00
wgh19
54b64fb19b improve ui 2024-06-12 16:05:50 +08:00
wgh19
d247455c19 local history 2024-06-12 15:43:06 +08:00
wgh19
759d6959b5 improve login and logout 2024-06-11 17:27:48 +08:00
wgh19
488be5fb1a add retry 2024-06-05 22:54:42 +08:00
wgh19
62b50c466e update readme 2024-06-01 22:47:28 +08:00
71 changed files with 3260 additions and 919 deletions

View File

@@ -1,6 +0,0 @@
#!/bin/bash
mkdir -p build/ios/iphoneos/Payload
mv build/ios/iphoneos/Runner.app build/ios/iphoneos/Payload
cd build/ios/iphoneos/
zip -r app-ios.ipa Payload

View File

@@ -1,20 +0,0 @@
name: Build IOS SIMULATOR
run-name: Build IOS SIMULATOR
on:
workflow_dispatch: {}
jobs:
Build_IOS_SIMULATOR:
runs-on: macos-13
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
architecture: x64
- run: sudo xcode-select --switch /Applications/Xcode_14.3.1.app
- run: flutter pub get
- run: flutter build ios --simulator --no-codesign
- uses: actions/upload-artifact@v3
with:
name: build_files
path: build/ios/iphonesimulator

View File

@@ -1,40 +1,188 @@
name: Build IOS name: Build ALL
run-name: Build IOS run-name: Build ALL
on: on:
workflow_dispatch: {} workflow_dispatch: {}
release:
types: [published]
jobs: jobs:
Build_IOS:
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
architecture: x64
- run: sudo xcode-select --switch /Applications/Xcode_14.3.1.app
- run: flutter pub get
- run: flutter build ios --release --no-codesign
- run: bash .github/generate_ipa.sh
- uses: actions/upload-artifact@v3
with:
name: app-ios.ipa
path: build/ios/iphoneos/app-ios.ipa
Build_MacOS: Build_MacOS:
runs-on: macos-13 runs-on: macos-15
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: 'stable' channel: 'stable'
architecture: x64 architecture: x64
- run: sudo xcode-select --switch /Applications/Xcode_14.3.1.app flutter-version-file: pubspec.yaml
- run: flutter pub get - run: sudo xcode-select --switch /Applications/Xcode_16.0.app
- run: flutter build macos --release - run: flutter pub get
- run: | - run: flutter build macos --release
cd build/macos/Build/Products/Release - run: |
zip -r macos-build.zip pixes.app cd build/macos/Build/Products/Release
- uses: actions/upload-artifact@v4 chmod +x pixes.app/Contents/MacOS/pixes
with: zip -r macos.zip pixes.app
name: macos-build.zip - uses: actions/upload-artifact@v4
path: build/macos/Build/Products/Release/macos-build.zip with:
name: macos.zip
path: build/macos/Build/Products/Release/macos.zip
Build_IOS:
runs-on: macos-15
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version-file: pubspec.yaml
architecture: x64
- run: sudo xcode-select --switch /Applications/Xcode_16.0.app
- run: flutter pub get
- run: flutter build ios --release --no-codesign
- run: |
mkdir -p /Users/runner/work/pixes/pixes/build/ios/iphoneos/Payload
mv /Users/runner/work/pixes/pixes/build/ios/iphoneos/Runner.app /Users/runner/work/pixes/pixes/build/ios/iphoneos/Payload
cd /Users/runner/work/pixes/pixes/build/ios/iphoneos/
zip -r pixes-ios.ipa Payload
- uses: actions/upload-artifact@v4
with:
name: app-ios.ipa
path: /Users/runner/work/pixes/pixes/build/ios/iphoneos/pixes-ios.ipa
Build_Android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version-file: pubspec.yaml
architecture: x64
- name: Decode and install certificate
env:
STORE_FILE: ${{ secrets.ANDROID_KEYSTORE }}
PROPERTY_FILE: ${{ secrets.ANDROID_KEY_PROPERTIES }}
run: |
echo "$STORE_FILE" | base64 --decode > android/keystore.jks
echo "$PROPERTY_FILE" > android/key.properties
- uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: '17'
- run: flutter pub get
- run: flutter build apk --release
- uses: actions/upload-artifact@v4
with:
name: apks
path: build/app/outputs/apk/release
Build_Windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: install dependencies
run: |
choco install yq -y
pip install httpx
- uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version-file: pubspec.yaml
architecture: x64
- name: build
run: |
flutter pub get
python windows/build.py
- uses: actions/upload-artifact@v4
with:
name: windows_build
path: build/windows/pixes-*
Build_Linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version-file: pubspec.yaml
architecture: x64
- run: |
sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1
dart pub global activate flutter_to_debian
- run: python3 debian/build.py x64
- run: dart run flutter_to_arch
- run: |
sudo rm -rf build/linux/arch/app.tar.gz
sudo rm -rf build/linux/arch/pkg
sudo rm -rf build/linux/arch/src
sudo rm -rf build/linux/arch/PKGBUILD
- uses: actions/upload-artifact@v4
with:
name: deb_build
path: build/linux/x64/release/debian
- uses: actions/upload-artifact@v4
with:
name: arch_build
path: build/linux/arch/
Build_Linux_ARM64:
runs-on: ubuntu-22.04-arm
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: 'master'
flutter-version-file: pubspec.yaml
- run: |
flutter pub get
sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1
dart pub global activate flutter_to_debian
- run: python3 debian/build.py arm64
- uses: actions/upload-artifact@v4
with:
name: deb_arm64_build
path: build/linux/x64/release/debian # This is a bug related to flutter_to_debian, but it's not a big deal.
Release:
runs-on: ubuntu-latest
needs: [Build_MacOS, Build_IOS, Build_Android, Build_Windows, Build_Linux, Build_Linux_ARM64]
if: github.event_name == 'release' # 仅在 push 事件时执行
steps:
- uses: actions/download-artifact@v4
with:
name: macos.zip
path: outputs
- uses: actions/download-artifact@v4
with:
name: app-ios.ipa
path: outputs
- uses: actions/download-artifact@v4
with:
name: apks
path: outputs
- uses: actions/download-artifact@v4
with:
name: windows_build
path: outputs
- uses: actions/download-artifact@v4
with:
name: deb_build
path: outputs
- uses: actions/download-artifact@v4
with:
name: arch_build
path: outputs
- uses: actions/download-artifact@v4
with:
name: deb_arm64_build
path: outputs
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
outputs/*.ipa
outputs/*.dmg
outputs/*.apk
outputs/*.zip
outputs/*.exe
outputs/*.deb
outputs/*.zst
env:
GITHUB_TOKEN: ${{ secrets.ACTION_GITHUB_TOKEN }}

View File

@@ -1,9 +1,44 @@
# pixes # pixes
非官方 Pixiv app, 支持 Windows, Android, iOS, macOS [![flutter](https://img.shields.io/badge/flutter-3.32.5-blue)](https://flutter.dev/)
[![License](https://img.shields.io/github/license/wgh136/pixes)](https://github.com/wgh136/pixes/blob/master/LICENSE)
[![Download](https://img.shields.io/github/v/release/wgh136/pixes)](https://github.com/wgh136/pixes)
[![stars](https://img.shields.io/github/stars/wgh136/pixes)](https://github.com/wgh136/pixes/stargazers)
主要功能均已实现 Unofficial Pixiv app, support Windows, Android, iOS, macOS, linux
## 屏幕截图 All main features are implemented.
<img src="screenshots/1.png" style="width: 400px"> ## Download
Download from [Release](https://github.com/wgh136/pixes/releases)
## Build from source
### Install Flutter
View [Flutter Document](https://flutter.dev/docs/get-started/install)
### Build Android
Put your keystore file (`key.jks`, `key.properties`) in `android/`
Run `flutter build apk`
### Build iOS/Windows/macOS
Run `flutter build ios/windows/macos`
### Build Linux
Use`python3 debian/build.py {ARCH}` to build deb package. Replace {ARCH} with `x64` or `arm64`.
For other linux distributions, you can use `flutter build linux` to build.
You must register the `pixiv` scheme in the `.desktop` file, otherwise the login will not work.
## Screenshots
<img src="screenshots/1.png" style="width: 400px">
<img src="screenshots/2.png" style="width: 400px">
<img src="screenshots/3.png" style="width: 400px">
<img src="screenshots/4.png" style="width: 400px">

View File

@@ -4,6 +4,8 @@ plugins {
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
} }
ext.abiCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86_64": 3]
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@@ -33,26 +35,29 @@ android {
compileSdk flutter.compileSdkVersion compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
splits{
abi {
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86_64'
enable true
universalApk true
}
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions{
kotlinOptions { jvmTarget = JavaVersion.VERSION_17
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.github.wgh136.pixes" applicationId "com.github.wgh136.pixes"
// 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-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion
targetSdkVersion 34 targetSdk = flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
@@ -74,9 +79,25 @@ android {
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. ndk {
// Signing with the debug keys for now, so `flutter run --release` works. abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
signingConfig signingConfigs.debug }
signingConfig signingConfigs.release
applicationVariants.all { variant ->
variant.outputs.all { output ->
def abi = output.getFilter(com.android.build.OutputFile.ABI)
if (abi != null) {
outputFileName = "pixes-${variant.versionName}-${abi}.apk"
def abiVersionCode = project.ext.abiCodes.get(abi)
if (abiVersionCode != null) {
versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
} else {
outputFileName = "pixes-${variant.versionName}.apk"
versionCodeOverride = variant.versionCode * 10
}
}
}
} }
} }
} }

View File

@@ -5,6 +5,7 @@
<application <application
android:label="pixes" android:label="pixes"
android:name="${applicationName}" android:name="${applicationName}"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
@@ -48,6 +49,7 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false"/>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and https://developer.android.com/training/package-visibility?hl=en and

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip

View File

@@ -19,8 +19,8 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false id "com.android.application" version "8.9.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false
} }
include ":app" include ":app"

Binary file not shown.

View File

@@ -10,7 +10,7 @@
"History": "历史", "History": "历史",
"Ranking": "排行", "Ranking": "排行",
"Settings": "设置", "Settings": "设置",
"Artworks": "插画", "Artworks": "作品",
"Mangas": "漫画", "Mangas": "漫画",
"Users": "用户", "Users": "用户",
"Search artwork": "搜索作品", "Search artwork": "搜索作品",
@@ -112,7 +112,6 @@
"Multiple path separators will be automatically replaced with a single": "多个路径分隔符将被自动替换为单个", "Multiple path separators will be automatically replaced with a single": "多个路径分隔符将被自动替换为单个",
"Login": "登录", "Login": "登录",
"You need to complete the login operation in the browser window that will open.": "您需要在打开的浏览器窗口中完成登录操作", "You need to complete the login operation in the browser window that will open.": "您需要在打开的浏览器窗口中完成登录操作",
"I have read and agree to the Terms of Use": "我已阅读并同意使用条款",
"Waiting..." : "等待中...", "Waiting..." : "等待中...",
"Waiting for authentication. Please finished in the browser." : "等待验证. 请在浏览器中完成.", "Waiting for authentication. Please finished in the browser." : "等待验证. 请在浏览器中完成.",
"Back" : "返回", "Back" : "返回",
@@ -162,7 +161,31 @@
"Current quantity": "当前数量", "Current quantity": "当前数量",
"Display the original image on the details page": "在详情页显示原图", "Display the original image on the details page": "在详情页显示原图",
"Open link": "打开链接", "Open link": "打开链接",
"Read": "阅读" "Read": "阅读",
"Error": "错误",
"Failed to register URL scheme.": "注册URL协议失败",
"Retry": "重试",
"Network": "网络",
"Save to gallery": "保存到相册",
"Choose a way to login": "选择登录方式",
"Use Webview: you cannot sign in with Google.": "使用Webview: 无法使用Google登录",
"Use an external browser: You can sign in using Google. However, some browsers may not be compatible with the application": "使用外部浏览器: 可以使用Google登录. 但是, 一些浏览器可能与应用程序不兼容",
"External browser": "外部浏览器",
"Show comments": "显示评论",
"Show original image": "显示原图",
"Illustrations": "插画",
"New version available": "新版本可用",
"A new version of Pixes is available. Do you want to update now?" : "Pixes有新版本可用. 您要立即更新吗?",
"Update": "更新",
"Check for updates": "检查更新",
"Check for updates on startup": "启动时检查更新",
"I understand pixes is a free unofficial application.": "我了解Pixes是一个免费的非官方应用程序",
"Related Artworks": "相关作品",
"Emphasize artworks from following artists": "强调关注画师的作品",
"The border of the artworks will be darker": "作品的边框将被加深",
"Initial Page": "初始页面",
"Close the pane to apply the settings": "关闭面板以应用设置",
"No results found": "未找到结果"
}, },
"zh_TW": { "zh_TW": {
"Search": "搜索", "Search": "搜索",
@@ -277,7 +300,6 @@
"Multiple path separators will be automatically replaced with a single": "多個路徑分隔符號將自動替換為單一", "Multiple path separators will be automatically replaced with a single": "多個路徑分隔符號將自動替換為單一",
"Login": "登錄", "Login": "登錄",
"You need to complete the login operation in the browser window that will open.": "您需要在打開的瀏覽器窗口中完成登錄操作", "You need to complete the login operation in the browser window that will open.": "您需要在打開的瀏覽器窗口中完成登錄操作",
"I have read and agree to the Terms of Use": "我已閱讀並同意使用條款",
"Waiting..." : "等待中...", "Waiting..." : "等待中...",
"Waiting for authentication. Please finished in the browser." : "等待驗證. 請在瀏覽器中完成.", "Waiting for authentication. Please finished in the browser." : "等待驗證. 請在瀏覽器中完成.",
"Back" : "返回", "Back" : "返回",
@@ -327,6 +349,30 @@
"Current quantity": "當前數量", "Current quantity": "當前數量",
"Display the original image on the details page": "在詳情頁顯示原圖", "Display the original image on the details page": "在詳情頁顯示原圖",
"Open link": "打開鏈接", "Open link": "打開鏈接",
"Read": "閱讀" "Read": "閱讀",
"Error": "錯誤",
"Failed to register URL scheme.": "註冊URL協議失敗",
"Retry": "重試",
"Network": "網絡",
"Save to gallery": "保存到相冊",
"Choose a way to login": "選擇登錄方式",
"Use Webview: you cannot sign in with Google.": "使用Webview: 無法使用Google登錄",
"Use an external browser: You can sign in using Google. However, some browsers may not be compatible with the application": "使用外部瀏覽器: 可以使用Google登錄. 但是, 一些瀏覽器可能與應用程序不兼容",
"External browser": "外部瀏覽器",
"Show comments": "顯示評論",
"Show original image": "顯示原圖",
"Illustrations": "插畫",
"New version available": "新版本可用",
"A new version of Pixes is available. Do you want to update now?" : "Pixes有新版本可用. 您要立即更新嗎?",
"Update": "更新",
"Check for updates": "檢查更新",
"Check for updates on startup": "啟動時檢查更新",
"I understand pixes is a free unofficial application.": "我了解Pixes是一個免費的非官方應用程序",
"Related Artworks": "相關作品",
"Emphasize artworks from following artists": "強調關注畫師的作品",
"The border of the artworks will be darker": "作品的邊框將被加深",
"Initial Page": "初始頁面",
"Close the pane to apply the settings": "關閉面板以應用設置",
"No results found": "未找到結果"
} }
} }

35
debian/build.py vendored Normal file
View File

@@ -0,0 +1,35 @@
import subprocess
import sys
arch = sys.argv[1]
debianContent = ''
desktopContent = ''
version = ''
with open('debian/debian.yaml', 'r') as f:
debianContent = f.read()
with open('debian/gui/pixes.desktop', 'r') as f:
desktopContent = f.read()
with open('pubspec.yaml', 'r') as f:
version = str.split(str.split(f.read(), 'version: ')[1], '+')[0]
with open('debian/debian.yaml', 'w') as f:
content = debianContent.replace('{{Version}}', version)
if arch == 'x64':
content = content.replace('{{Arch}}', 'x64')
content = content.replace('{{Architecture}}', 'amd64')
elif arch == 'arm64':
content = content.replace('{{Arch}}', 'arm64')
content = content.replace('{{Architecture}}', 'arm64')
f.write(content)
with open('debian/gui/pixes.desktop', 'w') as f:
f.write(desktopContent.replace('{{Version}}', version))
subprocess.run(["flutter", "build", "linux"])
subprocess.run(["$HOME/.pub-cache/bin/flutter_to_debian"], shell=True)
with open('debian/debian.yaml', 'w') as f:
f.write(debianContent)
with open('debian/gui/pixes.desktop', 'w') as f:
f.write(desktopContent)

18
debian/debian.yaml vendored Normal file
View File

@@ -0,0 +1,18 @@
flutter_app:
command: pixes
arch: {{Arch}}
parent: /usr/local/lib
nonInteractive: true
execFieldCodes: u
control:
Package: pixes
Version: {{Version}}
Architecture: {{Architecture}}
Priority: optional
Depends: libwebkit2gtk-4.1-0, libgtk-3-0
Maintainer: nyne
Description: Unofficial pixiv application
#options:
# exec_out_dir: debian/packages

10
debian/gui/pixes.desktop vendored Normal file
View File

@@ -0,0 +1,10 @@
[Desktop Entry]
Name=Pixes
GenericName=Pixes
Comment=Unofficial pixiv application
Terminal=false
Type=Application
Categories=Utility
Keywords=Flutter;share;images;
MimeType=x-scheme-handler/pixiv;
Icon=pixes

BIN
debian/gui/pixes.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -56,5 +56,9 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</dict> </dict>
</array> </array>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>photo</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>photo</string>
</dict> </dict>
</plist> </plist>

View File

@@ -32,8 +32,13 @@ class _Appdata {
LogicalKeyboardKey.enter.keyId, LogicalKeyboardKey.enter.keyId,
LogicalKeyboardKey.keyD.keyId, LogicalKeyboardKey.keyD.keyId,
LogicalKeyboardKey.keyF.keyId, LogicalKeyboardKey.keyF.keyId,
LogicalKeyboardKey.keyC.keyId,
LogicalKeyboardKey.keyG.keyId,
], ],
"showOriginalImage": false, "showOriginalImage": false,
"checkUpdate": true,
"emphasizeArtworksFromFollowingArtists": true,
"initialPage": 4,
}; };
bool lock = false; bool lock = false;
@@ -63,14 +68,25 @@ class _Appdata {
Future<void> readData() async { Future<void> readData() async {
final file = File("${App.dataPath}/account.json"); final file = File("${App.dataPath}/account.json");
if (file.existsSync()) { if (file.existsSync()) {
account = Account.fromJson(jsonDecode(await file.readAsString())); var json = jsonDecode(await file.readAsString());
if (json != null) {
account = Account.fromJson(json);
}
} }
final settingsFile = File("${App.dataPath}/settings.json"); final settingsFile = File("${App.dataPath}/settings.json");
if (settingsFile.existsSync()) { if (settingsFile.existsSync()) {
var json = jsonDecode(await settingsFile.readAsString()); var json = jsonDecode(await settingsFile.readAsString());
for (var key in json.keys) { for (var key in json.keys) {
if(json[key] != null) { if (json[key] != null) {
settings[key] = json[key]; if (json[key] is List && settings[key] is List) {
for (int i = 0;
i < json[key].length && i < settings[key].length;
i++) {
settings[key][i] = json[key][i];
}
} else {
settings[key] = json[key];
}
} }
} }
} }

100
lib/components/button.dart Normal file
View File

@@ -0,0 +1,100 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/foundation/app.dart';
abstract class BaseButton extends StatelessWidget {
const BaseButton({this.enabled = true, this.isLoading = false, super.key});
final bool enabled;
final bool isLoading;
Widget buildNormal(BuildContext context);
Widget buildLoading(BuildContext context);
Widget buildDisabled(BuildContext context);
@override
Widget build(BuildContext context) {
if (isLoading) {
return buildLoading(context);
} else if (enabled) {
return buildNormal(context);
} else {
return buildDisabled(context);
}
}
}
class FluentButton extends BaseButton {
const FluentButton({
required this.onPressed,
required this.child,
this.width,
super.enabled,
super.isLoading,
super.key,
});
final void Function() onPressed;
final Widget child;
final double? width;
static const _kFluentButtonPadding = 12.0;
@override
Widget buildNormal(BuildContext context) {
Widget child = this.child;
if (width != null) {
child = child.fixWidth(width! - _kFluentButtonPadding * 2);
}
return FilledButton(
onPressed: onPressed,
child: child,
);
}
@override
Widget buildLoading(BuildContext context) {
Widget child = Center(
widthFactor: 1,
heightFactor: 1,
child: const ProgressRing(
strokeWidth: 1.6,
).fixWidth(14).fixHeight(14),
);
if (width != null) {
child = child.fixWidth(width! - _kFluentButtonPadding * 2);
}
return Container(
padding: const EdgeInsets.symmetric(
horizontal: _kFluentButtonPadding, vertical: 6.5),
decoration: BoxDecoration(
color: FluentTheme.of(context).inactiveBackgroundColor,
borderRadius: BorderRadius.circular(4)),
child: child,
);
}
@override
Widget buildDisabled(BuildContext context) {
Widget child = Center(
widthFactor: 1,
heightFactor: 1,
child: this.child,
);
if (width != null) {
child = child.fixWidth(width! - _kFluentButtonPadding * 2);
}
return Container(
padding: const EdgeInsets.symmetric(
horizontal: _kFluentButtonPadding, vertical: 6.5),
decoration: BoxDecoration(
color: FluentTheme.of(context).inactiveBackgroundColor,
borderRadius: BorderRadius.circular(4)),
child: child,
);
}
}

View File

@@ -1,8 +1,11 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/components/animated_image.dart'; import 'package:pixes/components/animated_image.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/history.dart';
import 'package:pixes/foundation/image_provider.dart'; import 'package:pixes/foundation/image_provider.dart';
import 'package:pixes/network/download.dart'; import 'package:pixes/network/download.dart';
import 'package:pixes/pages/related_page.dart';
import 'package:pixes/utils/translation.dart'; import 'package:pixes/utils/translation.dart';
import '../network/network.dart'; import '../network/network.dart';
@@ -60,33 +63,53 @@ class _IllustWidgetState extends State<IllustWidget> {
child: Stack( child: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: Container(
width: width,
height: height,
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 8.0),
child: Container( child: Container(
width: width, width: double.infinity,
height: height, height: double.infinity,
padding: padding: EdgeInsets.zero,
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), decoration: BoxDecoration(
child: Card(
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
child: GestureDetector(
onTap: widget.onTap ??
() {
context.to(() => IllustPage(widget.illust));
},
onSecondaryTapUp: showMenu,
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0), borderRadius: BorderRadius.circular(4.0),
child: AnimatedImage( color: FluentTheme.of(context).cardColor,
image: CachedImageProvider( border: () {
widget.illust.images.first.medium), var emphasis = widget.illust.author.isFollowed &&
fit: BoxFit.cover, appdata.settings[
width: width - 16.0, 'emphasizeArtworksFromFollowingArtists'];
height: height - 16.0, var color = emphasis
? ColorScheme.of(context).primary
: ColorScheme.of(context)
.outlineVariant
.toOpacity(0.64);
var width = emphasis ? 1.6 : 1.0;
return Border.all(color: color, width: width);
}(),
),
margin: EdgeInsets.zero,
child: GestureDetector(
onTap: widget.onTap ??
() {
context.to(() => IllustPage(widget.illust));
},
onSecondaryTapUp: showMenu,
onLongPress: showMenu,
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: AnimatedImage(
image: CachedImageProvider(
widget.illust.images.first.medium),
fit: BoxFit.cover,
width: width - 16.0,
height: height - 16.0,
),
), ),
), ),
), ),
), ),
)), ),
if (widget.illust.images.length > 1) if (widget.illust.images.length > 1)
Positioned( Positioned(
top: 12, top: 12,
@@ -97,7 +120,7 @@ class _IllustWidgetState extends State<IllustWidget> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: FluentTheme.of(context) color: FluentTheme.of(context)
.micaBackgroundColor .micaBackgroundColor
.withOpacity(0.72), .toOpacity(0.72),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, color: ColorScheme.of(context).outlineVariant,
@@ -120,7 +143,7 @@ class _IllustWidgetState extends State<IllustWidget> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorScheme.of(context) color: ColorScheme.of(context)
.errorContainer .errorContainer
.withOpacity(0.8), .toOpacity(0.8),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, color: ColorScheme.of(context).outlineVariant,
@@ -143,7 +166,7 @@ class _IllustWidgetState extends State<IllustWidget> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorScheme.of(context) color: ColorScheme.of(context)
.primaryContainer .primaryContainer
.withOpacity(0.8), .toOpacity(0.8),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, color: ColorScheme.of(context).outlineVariant,
@@ -210,13 +233,13 @@ class _IllustWidgetState extends State<IllustWidget> {
}); });
} }
void showMenu(TapUpDetails details) { void showMenu([TapUpDetails? details]) {
// This calculates the position of the flyout according to the parent navigator // This calculates the position of the flyout according to the parent navigator
final targetContext = contextAttachKey.currentContext; final targetContext = contextAttachKey.currentContext;
if (targetContext == null) return; if (targetContext == null) return;
final box = targetContext.findRenderObject() as RenderBox; final box = targetContext.findRenderObject() as RenderBox;
final position = box.localToGlobal( Offset? position = box.localToGlobal(
details.localPosition, details?.localPosition ?? box.size.center(Offset.zero),
ancestor: Navigator.of(context).context.findRenderObject(), ancestor: Navigator.of(context).context.findRenderObject(),
); );
@@ -242,6 +265,12 @@ class _IllustWidgetState extends State<IllustWidget> {
context.showToast(message: "Added"); context.showToast(message: "Added");
DownloadManager().addDownloadingTask(widget.illust); DownloadManager().addDownloadingTask(widget.illust);
}), }),
MenuFlyoutItem(
text: Text("Related Artworks".tl),
onPressed: () {
context.to(
() => RelatedIllustsPage(widget.illust.id.toString()));
}),
], ],
); );
}, },
@@ -307,3 +336,161 @@ class _IllustWidgetState extends State<IllustWidget> {
); );
} }
} }
class IllustHistoryWidget extends StatelessWidget {
const IllustHistoryWidget(this.illust, {super.key});
final IllustHistory illust;
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constrains) {
final width = constrains.maxWidth;
final height = illust.height * width / illust.width;
return SizedBox(
width: width,
height: height,
child: Stack(
children: [
Positioned.fill(
child: Container(
width: width,
height: height,
padding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
child: Card(
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
child: GestureDetector(
onTap: () {
context.to(() => IllustPageWithId(illust.id.toString()));
},
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: AnimatedImage(
image: CachedImageProvider(illust.imgPath),
fit: BoxFit.cover,
width: width - 16.0,
height: height - 16.0,
),
),
),
),
)),
if (illust.imageCount > 1)
Positioned(
top: 12,
left: 12,
child: Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: FluentTheme.of(context)
.micaBackgroundColor
.toOpacity(0.72),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: ColorScheme.of(context).outlineVariant,
width: 0.6),
),
child: Center(
child: Text(
"${illust.imageCount}P",
style: const TextStyle(fontSize: 12),
),
)),
),
if (illust.isAi)
Positioned(
bottom: 12,
left: 12,
child: Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ColorScheme.of(context)
.errorContainer
.toOpacity(0.8),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: ColorScheme.of(context).outlineVariant,
width: 0.6),
),
child: const Center(
child: Text(
"AI",
style: TextStyle(fontSize: 12),
),
)),
),
if (illust.isGif)
Positioned(
bottom: 12,
left: 12,
child: Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ColorScheme.of(context)
.primaryContainer
.toOpacity(0.8),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: ColorScheme.of(context).outlineVariant,
width: 0.6),
),
child: const Center(
child: Text(
"GIF",
style: TextStyle(fontSize: 12),
),
)),
),
if (illust.isR18)
Positioned(
bottom: 12,
right: 12,
child: Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ColorScheme.of(context).errorContainer,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: ColorScheme.of(context).outlineVariant,
width: 0.6),
),
child: const Center(
child: Text(
"R18",
style: TextStyle(fontSize: 12),
),
)),
),
if (illust.isR18G)
Positioned(
bottom: 12,
right: 12,
child: Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ColorScheme.of(context).errorContainer,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: ColorScheme.of(context).outlineVariant,
width: 0.6),
),
child: const Center(
child: Text(
"R18G",
style: TextStyle(fontSize: 12),
),
)),
),
],
),
);
});
}
}

View File

@@ -44,8 +44,7 @@ class KeyEventListenerState extends State<KeyEventListener> {
if (event.logicalKey == LogicalKeyboardKey.escape) { if (event.logicalKey == LogicalKeyboardKey.escape) {
if (App.rootNavigatorKey.currentState?.canPop() ?? false) { if (App.rootNavigatorKey.currentState?.canPop() ?? false) {
App.rootNavigatorKey.currentState?.pop(); App.rootNavigatorKey.currentState?.pop();
} } else if (App.mainNavigatorKey?.currentState?.canPop() ?? false) {
if (App.mainNavigatorKey?.currentState?.canPop() ?? false) {
App.mainNavigatorKey?.currentState?.pop(); App.mainNavigatorKey?.currentState?.pop();
} }
return KeyEventResult.handled; return KeyEventResult.handled;
@@ -53,7 +52,7 @@ class KeyEventListenerState extends State<KeyEventListener> {
for (var handler in _handlers) { for (var handler in _handlers) {
handler(event.logicalKey); handler(event.logicalKey);
} }
return KeyEventResult.handled; return KeyEventResult.ignored;
}, },
child: widget.child, child: widget.child,
); );

View File

@@ -132,7 +132,9 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
if(message.length > 20) { if(message.length > 20) {
message = "${message.substring(0, 20)}..."; message = "${message.substring(0, 20)}...";
} }
context.showToast(message: message); if (mounted) {
context.showToast(message: message);
}
} }
}); });
} }
@@ -150,6 +152,7 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
void firstLoad() { void firstLoad() {
loadData(_page).then((value) { loadData(_page).then((value) {
if (!mounted) return;
if(value.success) { if(value.success) {
_page++; _page++;
setState(() { setState(() {

View File

@@ -4,6 +4,7 @@ typedef MdIcons = md.Icons;
typedef MdTheme = md.Theme; typedef MdTheme = md.Theme;
typedef MdThemeData = md.ThemeData; typedef MdThemeData = md.ThemeData;
typedef MdColorScheme = md.ColorScheme; typedef MdColorScheme = md.ColorScheme;
typedef TextField = md.TextField;
class ColorScheme { class ColorScheme {
static md.ColorScheme of(md.BuildContext context) { static md.ColorScheme of(md.BuildContext context) {

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/components/md.dart'; import 'package:pixes/components/md.dart';
import 'package:pixes/foundation/app.dart';
void showToast(BuildContext context, {required String message, IconData? icon}) { void showToast(BuildContext context, {required String message, IconData? icon}) {
var newEntry = OverlayEntry( var newEntry = OverlayEntry(
@@ -30,7 +31,7 @@ class ToastOverlay extends StatelessWidget {
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: PhysicalModel( child: PhysicalModel(
color: ColorScheme.of(context).surface.withOpacity(1), color: ColorScheme.of(context).surface.toOpacity(1),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
elevation: 1, elevation: 1,
child: Container( child: Container(

View File

@@ -20,6 +20,7 @@ class AppPageRoute<T> extends PageRoute<T> with _AppRouteTransitionMixin {
super.barrierDismissible = false, super.barrierDismissible = false,
this.enableIOSGesture = true, this.enableIOSGesture = true,
this.preventRebuild = true, this.preventRebuild = true,
this.isRoot = false,
}) { }) {
assert(opaque); assert(opaque);
} }
@@ -44,6 +45,9 @@ class AppPageRoute<T> extends PageRoute<T> with _AppRouteTransitionMixin {
@override @override
final bool preventRebuild; final bool preventRebuild;
@override
final bool isRoot;
static void updateBackButton() { static void updateBackButton() {
Future.delayed(const Duration(milliseconds: 300), () { Future.delayed(const Duration(milliseconds: 300), () {
StateController.findOrNull(tag: "back_button")?.update(); StateController.findOrNull(tag: "back_button")?.update();
@@ -77,6 +81,8 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
Widget? _child; Widget? _child;
bool get isRoot;
@override @override
Widget buildPage( Widget buildPage(
BuildContext context, BuildContext context,
@@ -115,19 +121,44 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
@override @override
Widget buildTransitions(BuildContext context, Animation<double> animation, Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) { Animation<double> secondaryAnimation, Widget child) {
return DrillInPageTransition( child = ColoredBox(
animation: CurvedAnimation( color: FluentTheme.of(context).micaBackgroundColor,
parent: animation, child: child,
curve: FluentTheme.of(context).animationCurve,
),
child: enableIOSGesture && App.isIOS
? IOSBackGestureDetector(
gestureWidth: _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture(this),
child: child)
: child,
); );
if (isRoot) {
child = EntrancePageTransition(
animation: CurvedAnimation(
parent: animation,
curve: FluentTheme.of(context).animationCurve,
),
child: enableIOSGesture && App.isIOS
? IOSBackGestureDetector(
gestureWidth: _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture(this),
child: child)
: child,
);
} else {
child = DrillInPageTransition(
animation: CurvedAnimation(
parent: animation,
curve: FluentTheme
.of(context)
.animationCurve,
),
child: enableIOSGesture && App.isIOS
? IOSBackGestureDetector(
gestureWidth: _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture(this),
child: child)
: child,
);
}
return child;
} }
IOSBackGestureController _startPopGesture(PageRoute<T> route) { IOSBackGestureController _startPopGesture(PageRoute<T> route) {
@@ -330,7 +361,7 @@ class SideBarRoute<T> extends PopupRoute<T> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: FluentTheme.of(context) color: FluentTheme.of(context)
.micaBackgroundColor .micaBackgroundColor
.withOpacity(0.98), .toOpacity(0.98),
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4), topLeft: Radius.circular(4),
bottomLeft: Radius.circular(4))), bottomLeft: Radius.circular(4))),
@@ -388,3 +419,32 @@ class SideBarRoute<T> extends PopupRoute<T> {
return IOSBackGestureController(route.controller!, route.navigator!); return IOSBackGestureController(route.controller!, route.navigator!);
} }
} }
class EntrancePageTransition extends StatelessWidget {
/// Creates an entrance page transition
const EntrancePageTransition({
super.key,
required this.child,
required this.animation,
});
/// The widget to be animated
final Widget child;
/// The animation to drive this transition
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.1),
end: Offset.zero,
).animate(animation),
child: FadeTransition(
opacity: animation,
child: child,
),
);
}
}

View File

@@ -0,0 +1,315 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/foundation/app.dart';
class AutoCompleteItem {
final String title;
final String? subtitle;
final VoidCallback onTap;
const AutoCompleteItem({
required this.title,
this.subtitle,
required this.onTap,
});
}
class AutoCompleteData {
final List<AutoCompleteItem> items;
final bool isLoading;
const AutoCompleteData({
this.items = const <AutoCompleteItem>[],
this.isLoading = false,
});
}
class SearchField extends StatefulWidget {
const SearchField({
super.key,
this.autoCompleteItems = const <AutoCompleteItem>[],
this.isLoadingAutoCompleteItems = false,
this.enableAutoComplete = true,
this.textEditingController,
this.placeholder,
this.leading,
this.trailing,
this.foregroundDecoration,
this.onChanged,
this.onSubmitted,
this.padding,
this.focusNode,
this.autoCompleteNoResultsText,
});
final List<AutoCompleteItem> autoCompleteItems;
final bool isLoadingAutoCompleteItems;
final bool enableAutoComplete;
final TextEditingController? textEditingController;
final String? placeholder;
final Widget? leading;
final Widget? trailing;
final WidgetStatePropertyAll<BoxDecoration>? foregroundDecoration;
final void Function(String)? onChanged;
final void Function(String)? onSubmitted;
final EdgeInsets? padding;
final FocusNode? focusNode;
final String? autoCompleteNoResultsText;
@override
State<SearchField> createState() => _SearchFieldState();
}
class _SearchFieldState extends State<SearchField> with TickerProviderStateMixin {
late final ValueNotifier<AutoCompleteData> autoCompleteItems;
late final FocusNode focusNode;
final boxKey = GlobalKey();
OverlayEntry? _overlayEntry;
AnimationController? _animationController;
Animation<double>? _fadeAnimation;
@override
void initState() {
autoCompleteItems = ValueNotifier(AutoCompleteData(
items: widget.autoCompleteItems,
isLoading: widget.isLoadingAutoCompleteItems,
));
focusNode = widget.focusNode ?? FocusNode();
focusNode.addListener(onfocusChange);
super.initState();
}
@override
void dispose() {
_animationController?.dispose();
focusNode.removeListener(onfocusChange);
if (widget.focusNode == null) {
focusNode.dispose();
}
super.dispose();
}
@override
void didUpdateWidget(covariant SearchField oldWidget) {
if (widget.autoCompleteItems != oldWidget.autoCompleteItems ||
widget.isLoadingAutoCompleteItems !=
oldWidget.isLoadingAutoCompleteItems) {
Future.microtask(() {
autoCompleteItems.value = AutoCompleteData(
items: widget.autoCompleteItems,
isLoading: widget.isLoadingAutoCompleteItems,
);
});
}
super.didUpdateWidget(oldWidget);
}
void onfocusChange() {
if (focusNode.hasFocus && widget.enableAutoComplete) {
final box = context.findRenderObject() as RenderBox?;
if (box == null) return;
final overlay = Overlay.of(context);
final position = box.localToGlobal(
Offset.zero,
ancestor: overlay.context.findRenderObject(),
);
if (_overlayEntry != null) {
_removeOverlayWithAnimation();
}
_animationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController!,
curve: Curves.easeOut,
));
_overlayEntry = OverlayEntry(
builder: (context) {
return Positioned(
left: position.dx,
width: box.size.width,
top: position.dy + box.size.height,
child: _AnimatedOverlayWrapper(
animation: _fadeAnimation!,
child: _AutoCompleteOverlay(
data: autoCompleteItems,
noResultsText: widget.autoCompleteNoResultsText,
),
),
);
},
);
overlay.insert(_overlayEntry!);
_animationController!.forward();
} else {
_removeOverlayWithAnimation();
}
}
void _removeOverlayWithAnimation() {
if (_overlayEntry != null && _animationController != null) {
_animationController!.reverse().then((_) {
_overlayEntry?.remove();
_overlayEntry = null;
_animationController?.dispose();
_animationController = null;
_fadeAnimation = null;
});
}
}
@override
Widget build(BuildContext context) {
return TextBox(
controller: widget.textEditingController,
key: boxKey,
focusNode: focusNode,
padding: const EdgeInsets.symmetric(horizontal: 12),
placeholder: widget.placeholder,
onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted,
foregroundDecoration: widget.foregroundDecoration,
prefix: widget.leading,
suffix: widget.trailing,
);
}
}
class _AutoCompleteOverlay extends StatefulWidget {
const _AutoCompleteOverlay({required this.data, this.noResultsText});
final ValueNotifier<AutoCompleteData> data;
final String? noResultsText;
@override
State<_AutoCompleteOverlay> createState() => _AutoCompleteOverlayState();
}
class _AutoCompleteOverlayState extends State<_AutoCompleteOverlay> {
late final notifier = widget.data;
var items = <AutoCompleteItem>[];
var isLoading = false;
@override
void initState() {
items = notifier.value.items;
isLoading = notifier.value.isLoading;
notifier.addListener(onItemsChanged);
super.initState();
}
@override
void dispose() {
notifier.removeListener(onItemsChanged);
super.dispose();
}
void onItemsChanged() {
setState(() {
items = notifier.value.items;
isLoading = notifier.value.isLoading;
});
}
@override
Widget build(BuildContext context) {
var items = List<AutoCompleteItem>.from(this.items);
Widget? content;
if (isLoading) {
content = SizedBox(
height: 44,
child: Center(
child: ProgressRing(
activeColor: FluentTheme.of(context).accentColor,
strokeWidth: 2,
).fixWidth(24).fixHeight(24),
),
);
} else if (items.isEmpty) {
content = ListTile(
title: Text(widget.noResultsText ?? 'No results found'),
onPressed: () {},
);
} else {
if (items.length > 8) {
items = items.sublist(0, 8);
}
content = Column(
mainAxisSize: MainAxisSize.min,
children: items.map((item) {
return ListTile(
title: Text(item.title),
subtitle: item.subtitle != null ? Text(item.subtitle!) : null,
onPressed: item.onTap,
);
}).toList(),
);
}
return Card(
backgroundColor: FluentTheme.of(context).micaBackgroundColor,
child: AnimatedSize(
alignment: Alignment.topCenter,
duration: const Duration(milliseconds: 160),
child: content,
),
);
}
}
class _AnimatedOverlayWrapper extends StatelessWidget {
const _AnimatedOverlayWrapper({
required this.animation,
required this.child,
});
final Animation<double> animation;
final Widget child;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return FadeTransition(
opacity: animation,
child: Transform.scale(
scale: 0.9 + (0.1 * animation.value),
alignment: Alignment.topCenter,
child: child,
),
);
},
child: child,
);
}
}

View File

@@ -40,7 +40,7 @@ class SegmentedButton<T> extends StatelessWidget {
onPressed: () => onPressed(e.key), onPressed: () => onPressed(e.key),
builder: (context, states) { builder: (context, states) {
var textColor = active ? null : ColorScheme.of(context).outline; var textColor = active ? null : ColorScheme.of(context).outline;
var backgroundColor = active ? null : ButtonState.resolveWith((states) { var backgroundColor = active ? null : WidgetStateProperty.resolveWith((states) {
return ButtonThemeData.buttonColor(context, states); return ButtonThemeData.buttonColor(context, states);
}).resolve(states); }).resolve(states);

View File

@@ -12,7 +12,7 @@ export "state_controller.dart";
export "navigation.dart"; export "navigation.dart";
class _App { class _App {
final version = "1.0.6"; final version = "1.2.0";
bool get isAndroid => Platform.isAndroid; bool get isAndroid => Platform.isAndroid;
bool get isIOS => Platform.isIOS; bool get isIOS => Platform.isIOS;

102
lib/foundation/history.dart Normal file
View File

@@ -0,0 +1,102 @@
import 'package:pixes/foundation/app.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:pixes/network/models.dart';
class IllustHistory {
final int id;
final String imgPath;
final DateTime time;
final int imageCount;
final bool isR18;
final bool isR18G;
final bool isAi;
final bool isGif;
final int width;
final int height;
IllustHistory(this.id, this.imgPath, this.time, this.imageCount, this.isR18,
this.isR18G, this.isAi, this.isGif, this.width, this.height);
}
class HistoryManager {
static HistoryManager? instance;
factory HistoryManager() => instance ??= HistoryManager._create();
HistoryManager._create();
late Database _db;
init() {
_db = sqlite3.open("${App.dataPath}/history.db");
_db.execute('''
create table if not exists history (
id integer primary key not null,
imgPath text not null,
time integer not null,
imageCount integer not null,
isR18 integer not null,
isR18g integer not null,
isAi integer not null,
isGif integer not null,
width integer not null,
height integer not null
)
''');
}
void addHistory(Illust illust) {
var time = DateTime.now();
_db.execute('''
insert or replace into history (id, imgPath, time, imageCount, isR18, isR18g, isAi, isGif, width, height)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', [
illust.id,
illust.images.first.medium,
time.millisecondsSinceEpoch,
illust.pageCount,
illust.isR18 ? 1 : 0,
illust.isR18G ? 1 : 0,
illust.isAi ? 1 : 0,
illust.isUgoira ? 1 : 0,
illust.width,
illust.height
]);
if(length > 1000) {
_db.execute('''
delete from history where id in (
select id from history order by time asc limit 100
)
''');
}
}
List<IllustHistory> getHistories(int page) {
var rows = _db.select('''
select * from history order by time desc
limit 20 offset ?
''', [(page - 1) * 20]);
List<IllustHistory> res = [];
for (var row in rows) {
res.add(IllustHistory(
row['id'],
row['imgPath'],
DateTime.fromMillisecondsSinceEpoch(row['time']),
row['imageCount'],
row['isR18'] == 1,
row['isR18g'] == 1,
row['isAi'] == 1,
row['isGif'] == 1,
row['width'],
row['height']));
}
return res;
}
int get length {
var rows = _db.select('''
select count(*) from history
''');
return rows.first.values.first! as int;
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:pixes/utils/ext.dart'; import 'package:pixes/utils/ext.dart';
@@ -26,12 +28,15 @@ class Log {
static bool ignoreLimitation = false; static bool ignoreLimitation = false;
/// only for debug
static const String? logFile = null;
static void printWarning(String text) { static void printWarning(String text) {
print('\x1B[33m$text\x1B[0m'); debugPrint('\x1B[33m$text\x1B[0m');
} }
static void printError(String text) { static void printError(String text) {
print('\x1B[31m$text\x1B[0m'); debugPrint('\x1B[31m$text\x1B[0m');
} }
static void addLog(LogLevel level, String title, String content) { static void addLog(LogLevel level, String title, String content) {
@@ -39,15 +44,13 @@ class Log {
content = "${content.substring(0, maxLogLength)}..."; content = "${content.substring(0, maxLogLength)}...";
} }
if (kDebugMode) { switch (level) {
switch (level) { case LogLevel.error:
case LogLevel.error: printError(content);
printError(content); case LogLevel.warning:
case LogLevel.warning: printWarning(content);
printWarning(content); case LogLevel.info:
case LogLevel.info: debugPrint(content);
print(content);
}
} }
var newLog = LogItem(level, title, content); var newLog = LogItem(level, title, content);
@@ -57,6 +60,9 @@ class Log {
} }
_logs.add(newLog); _logs.add(newLog);
if(logFile != null) {
File(logFile!).writeAsString(newLog.toString(), mode: FileMode.append);
}
if (_logs.length > maxLogNumber) { if (_logs.length > maxLogNumber) {
var res = _logs.remove( var res = _logs.remove(
_logs.firstWhereOrNull((element) => element.level == LogLevel.info)); _logs.firstWhereOrNull((element) => element.level == LogLevel.info));

View File

@@ -64,4 +64,10 @@ extension WidgetExtension on Widget{
Widget fixHeight(double height){ Widget fixHeight(double height){
return SizedBox(height: height, child: this); return SizedBox(height: height, child: this);
} }
}
extension ColorExt on Color {
Color toOpacity(double opacity){
return withValues(alpha: opacity);
}
} }

View File

@@ -1,3 +1,4 @@
import "dart:async";
import "dart:ui"; import "dart:ui";
import "package:dynamic_color/dynamic_color.dart"; import "package:dynamic_color/dynamic_color.dart";
@@ -11,45 +12,58 @@ import "package:pixes/components/keyboard.dart";
import "package:pixes/components/md.dart"; import "package:pixes/components/md.dart";
import "package:pixes/components/message.dart"; import "package:pixes/components/message.dart";
import "package:pixes/foundation/app.dart"; import "package:pixes/foundation/app.dart";
import "package:pixes/foundation/history.dart";
import "package:pixes/foundation/log.dart"; import "package:pixes/foundation/log.dart";
import "package:pixes/network/app_dio.dart"; import "package:pixes/network/app_dio.dart";
import "package:pixes/pages/main_page.dart"; import "package:pixes/pages/main_page.dart";
import "package:pixes/utils/app_links.dart"; import "package:pixes/utils/app_links.dart";
import "package:pixes/utils/loop.dart"; import "package:pixes/utils/loop.dart";
import "package:pixes/utils/translation.dart"; import "package:pixes/utils/translation.dart";
import "package:pixes/utils/update.dart";
import "package:pixes/utils/window.dart"; import "package:pixes/utils/window.dart";
import "package:window_manager/window_manager.dart"; import "package:window_manager/window_manager.dart";
void main() async { void main() {
WidgetsFlutterBinding.ensureInitialized(); runZonedGuarded(() async {
FlutterError.onError = (details) { Future.delayed(const Duration(seconds: 3), checkUpdate);
Log.error("Unhandled", "${details.exception}\n${details.stack}"); WidgetsFlutterBinding.ensureInitialized();
}; FlutterError.onError = (details) {
setSystemProxy(); Log.error("Unhandled", "${details.exception}\n${details.stack}");
await App.init(); };
await appdata.readData(); setSystemProxy();
await Translation.init(); await App.init();
handleLinks(); await appdata.readData();
if (App.isDesktop) { await Translation.init();
await flutter_acrylic.Window.initialize(); HistoryManager().init();
if (App.isWindows) { handleLinks();
await flutter_acrylic.Window.hideWindowControls(); if (App.isDesktop) {
await flutter_acrylic.Window.initialize();
if (App.isWindows) {
await flutter_acrylic.Window.hideWindowControls();
}
await WindowManager.instance.ensureInitialized();
windowManager.waitUntilReadyToShow().then((_) async {
await windowManager.setTitleBarStyle(
TitleBarStyle.hidden,
windowButtonVisibility: false,
);
if (App.isLinux) {
// https://github.com/leanflutter/window_manager/issues/460
return;
}
await windowManager.setMinimumSize(const Size(500, 600));
var placement = await WindowPlacement.loadFromFile();
await placement.applyToWindow();
await windowManager.show();
Loop.register(WindowPlacement.loop);
});
} }
await WindowManager.instance.ensureInitialized(); Loop.start();
windowManager.waitUntilReadyToShow().then((_) async { Log.info("APP", "Application started");
await windowManager.setTitleBarStyle( runApp(const MyApp());
TitleBarStyle.hidden, }, (error, stack) {
windowButtonVisibility: false, Log.error("Unhandled Exception", "$error\n$stack");
); });
await windowManager.setMinimumSize(const Size(500, 600));
var placement = await WindowPlacement.loadFromFile();
await placement.applyToWindow();
await windowManager.show();
Loop.register(WindowPlacement.loop);
});
}
Loop.start();
runApp(const MyApp());
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@@ -58,7 +72,6 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
var windowsFont = kDebugMode ? "微软雅黑" : "font";
return StateBuilder<SimpleController>( return StateBuilder<SimpleController>(
init: SimpleController(), init: SimpleController(),
tag: "MyApp", tag: "MyApp",
@@ -91,7 +104,7 @@ class MyApp extends StatelessWidget {
title: 'pixes', title: 'pixes',
theme: FluentThemeData( theme: FluentThemeData(
brightness: brightness, brightness: brightness,
fontFamily: App.isWindows ? windowsFont : null, fontFamily: App.isWindows ? "Microsoft YaHei UI" : null,
accentColor: AccentColor.swatch({ accentColor: AccentColor.swatch({
'darkest': darken(colorScheme.primary, 30), 'darkest': darken(colorScheme.primary, 30),
'darker': darken(colorScheme.primary, 20), 'darker': darken(colorScheme.primary, 20),
@@ -121,12 +134,29 @@ class MyApp extends StatelessWidget {
throw "widget is null"; throw "widget is null";
} }
String? font;
List<String>? fallback;
if (App.isLinux || App.isWindows) {
font = 'Noto Sans CJK';
fallback = [
'Segoe UI',
'Noto Sans SC',
'Noto Sans TC',
'Noto Sans',
'Microsoft YaHei',
'PingFang SC',
'Arial',
'sans-serif'
];
}
Widget widget = MdTheme( Widget widget = MdTheme(
data: MdThemeData.from( data: MdThemeData.from(
colorScheme: colorScheme, useMaterial3: true), colorScheme: colorScheme, useMaterial3: true),
child: DefaultTextStyle.merge( child: DefaultTextStyle.merge(
style: TextStyle( style: TextStyle(
fontFamily: App.isWindows ? 'font' : null, fontFamily: font,
fontFamilyFallback: fallback,
), ),
child: OverlayWidget(child), child: OverlayWidget(child),
), ),
@@ -144,7 +174,7 @@ class MyApp extends StatelessWidget {
), ),
child: widget, child: widget,
); );
} else if (App.windowsVersion == 10) { } /* else if (App.windowsVersion == 10) {
flutter_acrylic.Window.setEffect( flutter_acrylic.Window.setEffect(
effect: flutter_acrylic.WindowEffect.acrylic, effect: flutter_acrylic.WindowEffect.acrylic,
dark: FluentTheme.of(context).brightness == dark: FluentTheme.of(context).brightness ==
@@ -157,7 +187,7 @@ class MyApp extends StatelessWidget {
), ),
child: widget, child: widget,
); );
} }*/
} }
return KeyEventListener(child: widget); return KeyEventListener(child: widget);
@@ -169,21 +199,28 @@ class MyApp extends StatelessWidget {
} }
} }
/// from https://stackoverflow.com/a/60191441 int _floatToInt8(double x) {
return (x * 255.0).round() & 0xff;
}
Color darken(Color c, [int percent = 10]) { Color darken(Color c, [int percent = 10]) {
assert(1 <= percent && percent <= 100); assert(1 <= percent && percent <= 100);
var f = 1 - percent / 100; var f = 1 - percent / 100;
return Color.fromARGB(c.alpha, (c.red * f).round(), (c.green * f).round(), return Color.fromARGB(
(c.blue * f).round()); _floatToInt8(c.a),
_floatToInt8(c.r * f),
_floatToInt8(c.g * f),
_floatToInt8(c.b * f),
);
} }
/// from https://stackoverflow.com/a/60191441
Color lighten(Color c, [int percent = 10]) { Color lighten(Color c, [int percent = 10]) {
assert(1 <= percent && percent <= 100); assert(1 <= percent && percent <= 100);
var p = percent / 100; var p = percent / 100;
return Color.fromARGB( return Color.fromARGB(
c.alpha, _floatToInt8(c.a),
c.red + ((255 - c.red) * p).round(), _floatToInt8(c.r + (1 - c.r) * p),
c.green + ((255 - c.green) * p).round(), _floatToInt8(c.g + (1 - c.g) * p),
c.blue + ((255 - c.blue) * p).round()); _floatToInt8(c.b + (1 - c.b) * p),
);
} }

View File

@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@@ -109,12 +110,53 @@ class AppDio extends DioForNative {
CancelToken? cancelToken, CancelToken? cancelToken,
Options? options, Options? options,
ProgressCallback? onSendProgress, ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress}) { ProgressCallback? onReceiveProgress}) async{
if (!isInitialized) { if (!isInitialized) {
isInitialized = true; isInitialized = true;
interceptors.add(MyLogInterceptor()); interceptors.add(MyLogInterceptor());
} }
return super.request(path, if(T == Map<String, dynamic>) {
var res = await super.request<String>(path,
data: data,
queryParameters: queryParameters,
cancelToken: cancelToken,
options: options,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress);
if(res.data == null) {
return Response(
data: null,
requestOptions: res.requestOptions,
statusCode: res.statusCode,
statusMessage: res.statusMessage,
isRedirect: res.isRedirect,
redirects: res.redirects,
extra: res.extra,
headers: res.headers
);
}
try {
var json = jsonDecode(res.data!);
return Response(
data: json,
requestOptions: res.requestOptions,
statusCode: res.statusCode,
statusMessage: res.statusMessage,
isRedirect: res.isRedirect,
redirects: res.redirects,
extra: res.extra,
headers: res.headers
);
}
catch(e) {
var data = res.data!;
if(data.length > 50) {
data = "${data.substring(0, 50)}...";
}
throw "Failed to decode response: $e\n$data";
}
}
return super.request<T>(path,
data: data, data: data,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,

View File

@@ -324,7 +324,7 @@ class DownloadManager {
where illust_id = ?; where illust_id = ?;
''', [illust.illustId]); ''', [illust.illustId]);
for(var image in images) { for(var image in images) {
File(image["path"] as String).deleteIfExists(); File(image["path"] as String).deleteIgnoreError();
} }
_db.execute(''' _db.execute('''
delete from images delete from images

View File

@@ -152,6 +152,10 @@ class Tag {
@override @override
int get hashCode => name.hashCode; int get hashCode => name.hashCode;
static Tag fromJson(Map<String, dynamic> json) {
return Tag(json['name'] ?? "", json['translated_name']);
}
} }
class IllustImage { class IllustImage {

View File

@@ -387,9 +387,9 @@ class Network {
} }
} }
Future<Res<List<Illust>>> getUserIllusts(String uid) async { Future<Res<List<Illust>>> getUserIllusts(String uid, String? type) async {
var res = await apiGet( var res = await apiGet(
"/v1/user/illusts?filter=for_android&user_id=$uid&type=illust"); "/v1/user/illusts?filter=for_android&user_id=$uid${type != null ? "&type=$type" : ""}");
if (res.success) { if (res.success) {
return Res( return Res(
(res.data["illusts"] as List).map((e) => Illust.fromJson(e)).toList(), (res.data["illusts"] as List).map((e) => Illust.fromJson(e)).toList(),
@@ -573,4 +573,23 @@ class Network {
return Res.error(res.errorMessage); return Res.error(res.errorMessage);
} }
} }
Future<Res<bool>> sendHistory(List<int> ids) async {
var res = await apiPost("/v2/user/browsing-history/illust/add",
data: {"illust_ids": ids});
if (res.success) {
return const Res(true);
} else {
return Res.fromErrorRes(res);
}
}
Future<Res<List<Tag>>> getAutoCompleteTags(String keyword) async {
var res = await apiGet("/v2/search/autocomplete?merge_plain_keyword_results=true&word=${Uri.encodeComponent(keyword)}");
if (res.success) {
return Res((res.data["tags"] as List).map((e) => Tag.fromJson(e)).toList());
} else {
return Res.error(res.errorMessage);
}
}
} }

View File

@@ -35,15 +35,15 @@ extension NovelExt on Network {
return getNovelsWithNextUrl(url); return getNovelsWithNextUrl(url);
} }
Future<Res<List<Novel>>> getBookmarkedNovels(String uid) { Future<Res<List<Novel>>> getBookmarkedNovels(String uid, bool public) {
return getNovelsWithNextUrl( return getNovelsWithNextUrl(
"/v1/user/bookmarks/novel?user_id=$uid&restrict=public"); "/v1/user/bookmarks/novel?user_id=$uid&restrict=${public ? "public" : "private"}");
} }
Future<Res<bool>> favoriteNovel(String id) async { Future<Res<bool>> favoriteNovel(String id, bool public) async {
var res = await apiPost("/v2/novel/bookmark/add", data: { var res = await apiPost("/v2/novel/bookmark/add", data: {
"novel_id": id, "novel_id": id,
"restrict": "public", "restrict": public ? "public" : "private",
}); });
if (res.error) { if (res.error) {
return Res.fromErrorRes(res); return Res.fromErrorRes(res);
@@ -149,4 +149,17 @@ extension NovelExt on Network {
} }
return Res(Novel.fromJson(res.data["novel"])); return Res(Novel.fromJson(res.data["novel"]));
} }
Future<Res<List<Novel>>> getFollowingNovels(String restrict,
[String? nextUrl]) async {
var res = await apiGet(nextUrl ?? "/v1/novel/follow?restrict=$restrict");
if (res.success) {
return Res(
(res.data["novels"] as List).map((e) => Novel.fromJson(e)).toList(),
subData: res.data["next_url"],
);
} else {
return Res.error(res.errorMessage);
}
}
} }

View File

@@ -0,0 +1,75 @@
import 'package:pixes/network/app_dio.dart';
abstract class Translator {
static Translator? _instance;
static Translator get instance {
if (_instance == null) {
init();
}
return _instance!;
}
static void init() {
_instance = GoogleTranslator();
}
/// Translates the given [text] to the given [to] language.
Future<String> translate(String text, String to);
}
class GoogleTranslator implements Translator {
final Dio _dio = AppDio();
String get url => 'https://translate.google.com/translate_a/single';
Map<String, dynamic> buildBody(String text, String to) {
return {
'q': text,
'client': 'at',
'sl': 'auto',
'tl': to,
'dt': 't',
'ie': 'UTF-8',
'oe': 'UTF-8',
'dj': '1',
};
}
Future<String> translatePart(String part, String to) async {
final response = await _dio.post(
url,
data: buildBody(part, to),
options: Options(
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
),
);
var buffer = StringBuffer();
for(var e in response.data['sentences']) {
buffer.write(e['trans']);
}
return buffer.toString();
}
@override
Future<String> translate(String text, String to) async {
final lines = text.split('\n');
var buffer = StringBuffer();
var result = '';
for(int i=0; i<lines.length; i++) {
final line = lines[i];
if (buffer.length + line.length > 5000) {
result += await translatePart(buffer.toString(), to);
buffer.clear();
}
buffer.write(line);
buffer.write('\n');
}
if (buffer.isNotEmpty) {
result += await translatePart(buffer.toString(), to);
}
return result;
}
}

View File

@@ -143,15 +143,15 @@ class _CommentsPageState extends MultiPageLoadingState<CommentsPage, Comment> {
return Card( return Card(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
backgroundColor: backgroundColor:
FluentTheme.of(context).micaBackgroundColor.withOpacity(0.96), FluentTheme.of(context).micaBackgroundColor.toOpacity(0.96),
child: SizedBox( child: SizedBox(
height: 52, height: 52,
child: TextBox( child: TextBox(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
placeholder: "Comment".tl, placeholder: "Comment".tl,
foregroundDecoration: BoxDecoration( foregroundDecoration: WidgetStatePropertyAll(BoxDecoration(
border: Border.all(color: Colors.transparent), border: Border.all(color: Colors.transparent),
), )),
onSubmitted: (s) { onSubmitted: (s) {
showToast(context, message: "Sending".tl); showToast(context, message: "Sending".tl);
if (isCommenting) return; if (isCommenting) return;
@@ -161,10 +161,12 @@ class _CommentsPageState extends MultiPageLoadingState<CommentsPage, Comment> {
if (widget.isNovel) { if (widget.isNovel) {
Network().commentNovel(widget.id, s).then((value) { Network().commentNovel(widget.id, s).then((value) {
if (value.error) { if (value.error) {
context.showToast(message: "Network Error"); if (context.mounted) {
setState(() { context.showToast(message: "Network Error");
isCommenting = false; setState(() {
}); isCommenting = false;
});
}
} else { } else {
isCommenting = false; isCommenting = false;
nextUrl = null; nextUrl = null;
@@ -174,10 +176,12 @@ class _CommentsPageState extends MultiPageLoadingState<CommentsPage, Comment> {
} else { } else {
Network().comment(widget.id, s).then((value) { Network().comment(widget.id, s).then((value) {
if (value.error) { if (value.error) {
context.showToast(message: "Network Error"); if(context.mounted) {
setState(() { context.showToast(message: "Network Error");
isCommenting = false; setState(() {
}); isCommenting = false;
});
}
} else { } else {
isCommenting = false; isCommenting = false;
nextUrl = null; nextUrl = null;

View File

@@ -0,0 +1,83 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/components/grid.dart';
import 'package:pixes/components/loading.dart';
import 'package:pixes/components/novel.dart';
import 'package:pixes/components/segmented_button.dart';
import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/widget_utils.dart';
import 'package:pixes/network/network.dart';
import 'package:pixes/utils/translation.dart';
class FollowingNovelsPage extends StatefulWidget {
const FollowingNovelsPage({super.key});
@override
State<FollowingNovelsPage> createState() => _FollowingNovelsPageState();
}
class _FollowingNovelsPageState
extends MultiPageLoadingState<FollowingNovelsPage, Novel> {
bool public = true;
@override
Widget? buildFrame(BuildContext context, Widget child) {
return Column(
children: [
TitleBar(
title: "Following".tl,
action: SegmentedButton(
options: [
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
],
onPressed: (key) {
var newPublic = key == "public";
if (newPublic != public) {
public = newPublic;
nextUrl = null;
reset();
}
},
value: public ? "public" : "private",
),
),
Expanded(
child: child,
)
],
);
}
@override
Widget buildContent(BuildContext context, List<Novel> data) {
return Column(
children: [
Expanded(
child: GridViewWithFixedItemHeight(
itemCount: data.length,
itemHeight: 164,
minCrossAxisExtent: 400,
builder: (context, index) {
if (index == data.length - 1) {
nextPage();
}
return NovelWidget(data[index]);
},
).paddingHorizontal(8),
)
],
);
}
String? nextUrl;
@override
Future<Res<List<Novel>>> loadData(int page) async {
if (nextUrl == "end") return Res.error("No more data");
var res = nextUrl == null
? await Network().getFollowingNovels(public ? "public" : "private")
: await Network().getNovelsWithNextUrl(nextUrl!);
nextUrl = res.subData ?? "end";
return res;
}
}

View File

@@ -2,8 +2,10 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:pixes/appdata.dart'; import 'package:pixes/appdata.dart';
import 'package:pixes/components/loading.dart'; import 'package:pixes/components/loading.dart';
import 'package:pixes/components/segmented_button.dart';
import 'package:pixes/components/title_bar.dart'; import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/history.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
import 'package:pixes/utils/translation.dart'; import 'package:pixes/utils/translation.dart';
@@ -17,42 +19,109 @@ class HistoryPage extends StatefulWidget {
State<HistoryPage> createState() => _HistoryPageState(); State<HistoryPage> createState() => _HistoryPageState();
} }
class _HistoryPageState extends MultiPageLoadingState<HistoryPage, Illust> { class _HistoryPageState extends State<HistoryPage> {
int page = 0;
@override @override
Widget buildContent(BuildContext context, final List<Illust> data) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
TitleBar(title: "History".tl), TitleBar(
title: "History".tl,
action: SegmentedButton<int>(
options: [
SegmentedButtonOption(0, "Local".tl,),
SegmentedButtonOption(1, "Network".tl,),
],
value: page,
onPressed: (key) {
setState(() {
page = key;
});
},
),
),
Expanded( Expanded(
child: LayoutBuilder(builder: (context, constrains){ child: page == 0
return MasonryGridView.builder( ? const LocalHistoryPage()
padding: const EdgeInsets.symmetric(horizontal: 8) : const NetworkHistoryPage(),
+ EdgeInsets.only(bottom: context.padding.bottom), ),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),
itemCount: data.length,
itemBuilder: (context, index) {
if(index == data.length - 1){
nextPage();
}
return IllustWidget(data[index], onTap: () {
context.to(() => IllustGalleryPage(
illusts: data,
initialPage: index,
));
});
},
);
}),
)
], ],
); );
} }
}
class LocalHistoryPage extends StatefulWidget {
const LocalHistoryPage({super.key});
@override
State<LocalHistoryPage> createState() => _LocalHistoryPageState();
}
class _LocalHistoryPageState extends State<LocalHistoryPage> {
int page = 1;
var data = <IllustHistory>[];
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constrains) {
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8) +
EdgeInsets.only(bottom: context.padding.bottom),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),
itemCount: HistoryManager().length,
itemBuilder: (context, index) {
if (index == data.length) {
data.addAll(HistoryManager().getHistories(page));
page++;
}
return IllustHistoryWidget(data[index]);
},
);
});
}
}
class NetworkHistoryPage extends StatefulWidget {
const NetworkHistoryPage({super.key});
@override
State<NetworkHistoryPage> createState() => _NetworkHistoryPageState();
}
class _NetworkHistoryPageState
extends MultiPageLoadingState<NetworkHistoryPage, Illust> {
@override
Widget buildContent(BuildContext context, final List<Illust> data) {
return LayoutBuilder(builder: (context, constrains) {
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8) +
EdgeInsets.only(bottom: context.padding.bottom),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),
itemCount: data.length,
itemBuilder: (context, index) {
if (index == data.length - 1) {
nextPage();
}
return IllustWidget(data[index], onTap: () {
context.to(() => IllustGalleryPage(
illusts: data,
initialPage: index,
));
});
},
);
});
}
@override @override
Future<Res<List<Illust>>> loadData(page) { Future<Res<List<Illust>>> loadData(page) {
if(appdata.account?.user.isPremium != true) { if (appdata.account?.user.isPremium != true) {
return Future.value(Res.error("Premium Required".tl)); return Future.value(Res.error("Premium Required".tl));
} }
return Network().getHistory(page); return Network().getHistory(page);

View File

@@ -4,7 +4,6 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show Icons; import 'package:flutter/material.dart' show Icons;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:pixes/appdata.dart'; import 'package:pixes/appdata.dart';
import 'package:pixes/components/animated_image.dart'; import 'package:pixes/components/animated_image.dart';
import 'package:pixes/components/keyboard.dart'; import 'package:pixes/components/keyboard.dart';
@@ -14,11 +13,13 @@ import 'package:pixes/components/page_route.dart';
import 'package:pixes/components/title_bar.dart'; import 'package:pixes/components/title_bar.dart';
import 'package:pixes/components/user_preview.dart'; import 'package:pixes/components/user_preview.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/history.dart';
import 'package:pixes/foundation/image_provider.dart'; import 'package:pixes/foundation/image_provider.dart';
import 'package:pixes/network/download.dart'; import 'package:pixes/network/download.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
import 'package:pixes/pages/comments_page.dart'; import 'package:pixes/pages/comments_page.dart';
import 'package:pixes/pages/image_page.dart'; import 'package:pixes/pages/image_page.dart';
import 'package:pixes/pages/related_page.dart';
import 'package:pixes/pages/search_page.dart'; import 'package:pixes/pages/search_page.dart';
import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/pages/user_info_page.dart';
import 'package:pixes/utils/block.dart'; import 'package:pixes/utils/block.dart';
@@ -44,6 +45,8 @@ class IllustGalleryPage extends StatefulWidget {
final String? nextUrl; final String? nextUrl;
static var cachedHistoryIds = <int>{};
@override @override
State<IllustGalleryPage> createState() => _IllustGalleryPageState(); State<IllustGalleryPage> createState() => _IllustGalleryPageState();
} }
@@ -57,6 +60,8 @@ class _IllustGalleryPageState extends State<IllustGalleryPage> {
bool loading = false; bool loading = false;
late int page = widget.initialPage;
@override @override
void initState() { void initState() {
illusts = List.from(widget.illusts); illusts = List.from(widget.illusts);
@@ -68,6 +73,16 @@ class _IllustGalleryPageState extends State<IllustGalleryPage> {
super.initState(); super.initState();
} }
@override
void dispose() {
if (IllustGalleryPage.cachedHistoryIds.length > 5) {
Network().sendHistory(
IllustGalleryPage.cachedHistoryIds.toList().reversed.toList());
IllustGalleryPage.cachedHistoryIds.clear();
}
super.dispose();
}
void nextPage() { void nextPage() {
var length = illusts.length; var length = illusts.length;
if (controller.page == length - 1) return; if (controller.page == length - 1) return;
@@ -88,16 +103,51 @@ class _IllustGalleryPageState extends State<IllustGalleryPage> {
length++; length++;
} }
return PageView.builder( return Stack(
controller: controller, children: [
itemCount: length, Positioned.fill(
itemBuilder: (context, index) { child: PageView.builder(
if (index == illusts.length) { controller: controller,
return buildLast(); itemCount: length,
} itemBuilder: (context, index) {
return IllustPage(illusts[index], if (index == illusts.length) {
nextPage: nextPage, previousPage: previousPage); return buildLast();
}, }
return IllustPage(illusts[index],
nextPage: nextPage, previousPage: previousPage);
},
onPageChanged: (value) => setState(() {
page = value;
}),
),
),
if (page < length - 1 && length > 1 && App.isDesktop)
Positioned(
right: 0,
top: 0,
bottom: 32,
child: Center(
child: IconButton(
icon: const Icon(FluentIcons.chevron_right),
onPressed: () {
nextPage();
},
)),
),
if (page != 0 && length > 1 && App.isDesktop)
Positioned(
left: 0,
top: 0,
bottom: 32,
child: Center(
child: IconButton(
icon: const Icon(FluentIcons.chevron_left),
onPressed: () {
previousPage();
},
)),
),
],
); );
} }
@@ -169,6 +219,10 @@ class _IllustPageState extends State<IllustPage> {
widget.illust.author.isFollowed = v; widget.illust.author.isFollowed = v;
}); });
}; };
HistoryManager().addHistory(widget.illust);
if (appdata.account!.user.isPremium) {
IllustGalleryPage.cachedHistoryIds.add(widget.illust.id);
}
super.initState(); super.initState();
} }
@@ -265,6 +319,14 @@ class _IllustPageState extends State<IllustPage> {
_bottomBarController.download(); _bottomBarController.download();
case 6: case 6:
_bottomBarController.follow(); _bottomBarController.follow();
case 7:
if (ModalRoute.of(context)?.isCurrent ?? true) {
CommentsPage.show(context, widget.illust.id.toString());
} else {
context.pop();
}
case 8:
openImage(0);
} }
} }
@@ -558,7 +620,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(8)), const BorderRadius.vertical(top: Radius.circular(8)),
backgroundColor: backgroundColor:
FluentTheme.of(context).micaBackgroundColor.withOpacity(0.96), FluentTheme.of(context).micaBackgroundColor.toOpacity(0.96),
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
@@ -997,7 +1059,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
Widget buildMoreActions() { Widget buildMoreActions() {
return Wrap( return Wrap(
runSpacing: 4, runSpacing: 8,
spacing: 8, spacing: 8,
children: [ children: [
Button( Button(
@@ -1078,7 +1140,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
).fixWidth(96), ).fixWidth(96),
Button( Button(
onPressed: () { onPressed: () {
context.to(() => _RelatedIllustsPage(widget.illust.id.toString())); context.to(() => RelatedIllustsPage(widget.illust.id.toString()));
}, },
child: SizedBox( child: SizedBox(
height: 28, height: 28,
@@ -1157,7 +1219,7 @@ class __BlockingPageState extends State<_BlockingPage> {
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
borderColor: blockedTags.contains(index) borderColor: blockedTags.contains(index)
? ColorScheme.of(context).outlineVariant ? ColorScheme.of(context).outlineVariant
: ColorScheme.of(context).outlineVariant.withOpacity(0.2), : ColorScheme.of(context).outlineVariant.toOpacity(0.2),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: ListTile( child: ListTile(
title: Text(text), title: Text(text),
@@ -1295,64 +1357,3 @@ class _IllustPageWithIdState extends LoadingState<IllustPageWithId, Illust> {
return Network().getIllustByID(widget.id); return Network().getIllustByID(widget.id);
} }
} }
class _RelatedIllustsPage extends StatefulWidget {
const _RelatedIllustsPage(this.id);
final String id;
@override
State<_RelatedIllustsPage> createState() => _RelatedIllustsPageState();
}
class _RelatedIllustsPageState
extends MultiPageLoadingState<_RelatedIllustsPage, Illust> {
@override
Widget? buildFrame(BuildContext context, Widget child) {
return Column(
children: [
TitleBar(title: "Related artworks".tl),
Expanded(
child: child,
)
],
);
}
@override
Widget buildContent(BuildContext context, final List<Illust> data) {
return LayoutBuilder(builder: (context, constrains) {
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8) +
EdgeInsets.only(bottom: context.padding.bottom),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),
itemCount: data.length,
itemBuilder: (context, index) {
if (index == data.length - 1) {
nextPage();
}
return IllustWidget(data[index]);
},
);
});
}
String? nextUrl;
@override
Future<Res<List<Illust>>> loadData(page) async {
if (nextUrl == "end") {
return Res.error("No more data");
}
var res = nextUrl == null
? await Network().relatedIllusts(widget.id)
: await Network().getIllustsWithNextUrl(nextUrl!);
if (!res.error) {
nextUrl = res.subData;
nextUrl ??= "end";
}
return res;
}
}

View File

@@ -3,8 +3,10 @@ import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:image_gallery_saver_plus/image_gallery_saver_plus.dart';
import 'package:photo_view/photo_view_gallery.dart'; import 'package:photo_view/photo_view_gallery.dart';
import 'package:pixes/components/md.dart'; import 'package:pixes/components/md.dart';
import 'package:pixes/components/message.dart';
import 'package:pixes/components/page_route.dart'; import 'package:pixes/components/page_route.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/cache_manager.dart'; import 'package:pixes/foundation/cache_manager.dart';
@@ -89,6 +91,8 @@ class _ImagePageState extends State<ImagePage> with WindowListener {
void showMenu() { void showMenu() {
menuController.showFlyout( menuController.showFlyout(
barrierColor: Colors.transparent,
position: App.isMobile ? Offset(context.size!.width, 0) : null,
builder: (context) => MenuFlyout( builder: (context) => MenuFlyout(
items: [ items: [
MenuFlyoutItem( MenuFlyoutItem(
@@ -96,20 +100,41 @@ class _ImagePageState extends State<ImagePage> with WindowListener {
onPressed: () async { onPressed: () async {
var file = await getFile(); var file = await getFile();
if (file != null) { if (file != null) {
var fileName = file.path.split('/').last; var fileName = widget.urls[currentPage].split('/').last;
if (!fileName.contains('.')) { if (!fileName.contains('.')) {
fileName += getExtensionName(); fileName += getExtensionName();
} }
saveFile(file, fileName); saveFile(file, fileName);
} }
}), }),
if (App.isMobile)
MenuFlyoutItem(
text: Text("Save to gallery".tl),
onPressed: () async {
var file = await getFile();
if (file != null) {
var fileName =
widget.urls[currentPage].split('/').last;
if (!fileName.contains('.')) {
fileName += getExtensionName();
}
await ImageGallerySaverPlus.saveImage(
await file.readAsBytes(),
quality: 100,
name: fileName,
);
if (context.mounted) {
showToast(context, message: "Saved".tl);
}
}
}),
MenuFlyoutItem( MenuFlyoutItem(
text: Text("Share".tl), text: Text("Share".tl),
onPressed: () async { onPressed: () async {
var file = await getFile(); var file = await getFile();
if (file != null) { if (file != null) {
var ext = getExtensionName(); var ext = getExtensionName();
var fileName = file.path.split('/').last; var fileName = widget.urls[currentPage].split('/').last;
if (!fileName.contains('.')) { if (!fileName.contains('.')) {
fileName += ext; fileName += ext;
} }
@@ -164,6 +189,7 @@ class _ImagePageState extends State<ImagePage> with WindowListener {
var image = widget.urls[index]; var image = widget.urls[index];
return PhotoViewGalleryPageOptions( return PhotoViewGalleryPageOptions(
filterQuality: FilterQuality.medium,
imageProvider: getImageProvider(image), imageProvider: getImageProvider(image),
); );
}, },

View File

@@ -1,4 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/components/button.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
import 'package:pixes/pages/webview_page.dart'; import 'package:pixes/pages/webview_page.dart';
@@ -24,7 +25,7 @@ class _LoginPageState extends State<LoginPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if(isLogging) { if (isLogging) {
return buildLoading(context); return buildLoading(context);
} else if (!waitingForAuth) { } else if (!waitingForAuth) {
return buildLogin(context); return buildLogin(context);
@@ -56,23 +57,12 @@ class _LoginPageState extends State<LoginPage> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (checked) FluentButton(
FilledButton( onPressed: onContinue,
onPressed: onContinue, enabled: checked,
child: Text("Continue".tl), width: 96,
) child: Text("Continue".tl),
else ),
Container(
height: 28,
width: 78,
decoration: BoxDecoration(
color: FluentTheme.of(context)
.inactiveBackgroundColor,
borderRadius: BorderRadius.circular(4)),
child: Center(
child: Text("Continue".tl),
),
),
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
@@ -97,7 +87,9 @@ class _LoginPageState extends State<LoginPage> {
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
Text("I have read and agree to the Terms of Use".tl) Expanded(
child: Text("I understand pixes is a free unofficial application.".tl),
)
], ],
) )
], ],
@@ -186,6 +178,39 @@ class _LoginPageState extends State<LoginPage> {
} }
void onContinue() async { void onContinue() async {
bool? useExternal;
if (App.isMobile) {
await showDialog(
context: context,
barrierDismissible: true,
builder: (context) => ContentDialog(
title: Text("Choose a way to login".tl),
content: Text("${"Use Webview: you cannot sign in with Google.".tl}"
"\n\n"
"${"Use an external browser: You can sign in using Google. However, some browsers may not be compatible with the application".tl}"),
actions: [
Button(
child: Text("Webview".tl),
onPressed: () {
useExternal = false;
App.rootNavigatorKey.currentState!.pop();
},
),
Button(
child: Text("External browser".tl),
onPressed: () {
useExternal = true;
App.rootNavigatorKey.currentState!.pop();
},
)
]),
);
} else {
useExternal = true;
}
if (useExternal == null) {
return;
}
var url = await Network().generateWebviewUrl(); var url = await Network().generateWebviewUrl();
onLink = (uri) { onLink = (uri) {
if (uri.scheme == "pixiv") { if (uri.scheme == "pixiv") {
@@ -198,15 +223,18 @@ class _LoginPageState extends State<LoginPage> {
setState(() { setState(() {
waitingForAuth = true; waitingForAuth = true;
}); });
if(App.isMobile && mounted) { if (!useExternal! && mounted) {
context.to(() => WebviewPage(url, onNavigation: (req) { context.to(() => WebviewPage(
if(req.url.startsWith("pixiv://")) { url,
App.rootNavigatorKey.currentState!.pop(); onNavigation: (req) {
onLink?.call(Uri.parse(req.url)); if (req.url.startsWith("pixiv://")) {
return false; App.rootNavigatorKey.currentState!.pop();
} onLink?.call(Uri.parse(req.url));
return true; return false;
},)); }
return true;
},
));
} else { } else {
launchUrlString(url); launchUrlString(url);
} }
@@ -219,7 +247,7 @@ class _LoginPageState extends State<LoginPage> {
}); });
var res = await Network().loginWithCode(code); var res = await Network().loginWithCode(code);
if (res.error) { if (res.error) {
if(mounted) { if (mounted) {
context.showToast(message: res.errorMessage!); context.showToast(message: res.errorMessage!);
} }
setState(() { setState(() {

View File

@@ -34,7 +34,7 @@ class _LogsPageState extends State<LogsPage> {
children: [ children: [
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorScheme.of(context).surfaceVariant, color: ColorScheme.of(context).surfaceContainerHighest,
borderRadius: const BorderRadius.all(Radius.circular(16)), borderRadius: const BorderRadius.all(Radius.circular(16)),
), ),
child: Padding( child: Padding(

View File

@@ -2,6 +2,7 @@ import "dart:async";
import "package:fluent_ui/fluent_ui.dart"; import "package:fluent_ui/fluent_ui.dart";
import "package:flutter/foundation.dart"; import "package:flutter/foundation.dart";
import "package:flutter/services.dart";
import "package:pixes/appdata.dart"; import "package:pixes/appdata.dart";
import "package:pixes/components/md.dart"; import "package:pixes/components/md.dart";
import "package:pixes/foundation/app.dart"; import "package:pixes/foundation/app.dart";
@@ -10,6 +11,7 @@ import "package:pixes/network/network.dart";
import "package:pixes/pages/bookmarks.dart"; import "package:pixes/pages/bookmarks.dart";
import "package:pixes/pages/downloaded_page.dart"; import "package:pixes/pages/downloaded_page.dart";
import "package:pixes/pages/following_artworks.dart"; import "package:pixes/pages/following_artworks.dart";
import "package:pixes/pages/following_novels_page.dart";
import "package:pixes/pages/history.dart"; import "package:pixes/pages/history.dart";
import "package:pixes/pages/novel_bookmarks_page.dart"; import "package:pixes/pages/novel_bookmarks_page.dart";
import "package:pixes/pages/novel_ranking_page.dart"; import "package:pixes/pages/novel_ranking_page.dart";
@@ -64,26 +66,43 @@ class MainPage extends StatefulWidget {
State<MainPage> createState() => _MainPageState(); State<MainPage> createState() => _MainPageState();
} }
class _MainPageState extends State<MainPage> with WindowListener { class _MainPageState extends State<MainPage>
with WindowListener
implements PopEntry {
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
int index = 4; int index = 4;
int windowButtonKey = 0; int windowButtonKey = 0;
ModalRoute<dynamic>? _route;
@override @override
void initState() { void initState() {
StateController.put<TitleBarController>(TitleBarController()); StateController.put<TitleBarController>(TitleBarController());
windowManager.addListener(this); windowManager.addListener(this);
listenMouseSideButtonToBack(navigatorKey); listenMouseSideButtonToBack(navigatorKey);
App.mainNavigatorKey = navigatorKey; App.mainNavigatorKey = navigatorKey;
index = appdata.settings["initialPage"] ?? 4;
super.initState(); super.initState();
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
if (nextRoute != _route) {
_route?.unregisterPopEntry(this);
_route = nextRoute;
_route?.registerPopEntry(this);
}
}
@override @override
void dispose() { void dispose() {
StateController.remove<TitleBarController>(); StateController.remove<TitleBarController>();
windowManager.removeListener(this); windowManager.removeListener(this);
ModalRoute.of(context)!.unregisterPopEntry(this);
super.dispose(); super.dispose();
} }
@@ -112,115 +131,122 @@ class _MainPageState extends State<MainPage> with WindowListener {
); );
} }
return DefaultSelectionStyle.merge( return DefaultSelectionStyle.merge(
selectionColor: FluentTheme.of(context).selectionColor.withOpacity(0.4), selectionColor: FluentTheme.of(context).selectionColor.toOpacity(0.4),
child: NavigationView( child: NavigationView(
appBar: buildAppBar(context, navigatorKey), appBar: buildAppBar(context, navigatorKey),
pane: NavigationPane( pane: NavigationPane(
selected: index, selected: index,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
index = value; index = value;
}); });
navigate(value); navigate(value);
}, },
items: [ items: [
UserPane(), UserPane(),
PaneItem( PaneItem(
icon: const Icon( icon: const Icon(
MdIcons.search, MdIcons.search,
size: 20, size: 20,
),
title: Text('Search'.tl),
body: const SizedBox.shrink(),
), ),
PaneItem( title: Text('Search'.tl),
icon: const Icon( body: const SizedBox.shrink(),
MdIcons.downloading, ),
size: 20, PaneItem(
), icon: const Icon(
title: Text('Downloading'.tl), MdIcons.downloading,
body: const SizedBox.shrink(), size: 20,
), ),
PaneItem( title: Text('Downloading'.tl),
icon: const Icon( body: const SizedBox.shrink(),
MdIcons.download, ),
size: 20, PaneItem(
), icon: const Icon(
title: Text('Downloaded'.tl), MdIcons.download,
body: const SizedBox.shrink(), size: 20,
), ),
PaneItemSeparator(), title: Text('Downloaded'.tl),
PaneItemHeader( body: const SizedBox.shrink(),
header: Text('${"Artwork".tl}/${"Manga".tl}') ),
.paddingBottom(4) PaneItemSeparator(),
.paddingLeft(8)), PaneItemHeader(
PaneItem( header: Text('${"Illustrations".tl}/${"Manga".tl}')
icon: const Icon( .paddingBottom(4)
MdIcons.explore_outlined, .paddingLeft(8)),
size: 20, PaneItem(
), icon: const Icon(
title: Text('Explore'.tl), MdIcons.explore_outlined,
body: const SizedBox.shrink(), size: 20,
), ),
PaneItem( title: Text('Explore'.tl),
icon: const Icon(MdIcons.bookmark_outline, size: 20), body: const SizedBox.shrink(),
title: Text('Bookmarks'.tl), ),
body: const SizedBox.shrink(), PaneItem(
), icon: const Icon(MdIcons.bookmark_outline, size: 20),
PaneItem( title: Text('Bookmarks'.tl),
icon: const Icon(MdIcons.interests_outlined, size: 20), body: const SizedBox.shrink(),
title: Text('Following'.tl), ),
body: const SizedBox.shrink(), PaneItem(
), icon: const Icon(MdIcons.interests_outlined, size: 20),
PaneItem( title: Text('Following'.tl),
icon: const Icon(MdIcons.history, size: 20), body: const SizedBox.shrink(),
title: Text('History'.tl), ),
body: const SizedBox.shrink(), PaneItem(
), icon: const Icon(MdIcons.history, size: 20),
PaneItem( title: Text('History'.tl),
icon: const Icon(MdIcons.leaderboard_outlined, size: 20), body: const SizedBox.shrink(),
title: Text('Ranking'.tl), ),
body: const SizedBox.shrink(), PaneItem(
), icon: const Icon(MdIcons.leaderboard_outlined, size: 20),
PaneItemSeparator(), title: Text('Ranking'.tl),
PaneItemHeader( body: const SizedBox.shrink(),
header: Text("Novel".tl).paddingBottom(4).paddingLeft(8)), ),
PaneItem( PaneItemSeparator(),
icon: const Icon(MdIcons.featured_play_list_outlined, size: 20), PaneItemHeader(
title: Text('Recommendation'.tl), header: Text("Novel".tl).paddingBottom(4).paddingLeft(8)),
body: const SizedBox.shrink(), PaneItem(
), icon: const Icon(MdIcons.featured_play_list_outlined, size: 20),
PaneItem( title: Text('Recommendation'.tl),
icon: body: const SizedBox.shrink(),
const Icon(MdIcons.collections_bookmark_outlined, size: 20), ),
title: Text('Bookmarks'.tl), PaneItem(
body: const SizedBox.shrink(), icon: const Icon(MdIcons.collections_bookmark_outlined, size: 20),
), title: Text('Bookmarks'.tl),
PaneItem( body: const SizedBox.shrink(),
icon: const Icon(MdIcons.leaderboard_outlined, size: 20), ),
title: Text('Ranking'.tl), PaneItem(
body: const SizedBox.shrink(), icon: const Icon(MdIcons.interests_outlined, size: 20),
), title: Text('Following'.tl),
PaneItemSeparator(), body: const SizedBox.shrink(),
PaneItem( ),
icon: const Icon(MdIcons.settings_outlined, size: 20), PaneItem(
title: Text('Settings'.tl), icon: const Icon(MdIcons.leaderboard_outlined, size: 20),
body: const SizedBox.shrink(), title: Text('Ranking'.tl),
), body: const SizedBox.shrink(),
], ),
PaneItemSeparator(),
PaneItemAction(
icon: const Icon(MdIcons.settings_outlined, size: 20),
title: Text('Settings'.tl),
body: const SizedBox.shrink(),
onTap: () {
navigatorKey.currentContext?.to(() => const SettingsPage());
},
),
],
),
paneBodyBuilder: (pane, child) => MediaQuery.removePadding(
context: context,
removeTop: true,
child: Navigator(
key: navigatorKey,
onGenerateRoute: (settings) => AppPageRoute(
isRoot: true,
builder: (context) => pageBuilders.elementAtOrNull(index)!(),
),
), ),
paneBodyBuilder: (pane, child) => NavigatorPopHandler( ),
key: const Key("navigator"), ),
onPop: () => navigatorKey.currentState?.pop(),
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: Navigator(
key: navigatorKey,
onGenerateRoute: (settings) => AppPageRoute(
builder: (context) => const RecommendationPage()),
),
))),
); );
} }
@@ -236,8 +262,8 @@ class _MainPageState extends State<MainPage> with WindowListener {
() => const RankingPage(), () => const RankingPage(),
() => const NovelRecommendationPage(), () => const NovelRecommendationPage(),
() => const NovelBookmarksPage(), () => const NovelBookmarksPage(),
() => const FollowingNovelsPage(),
() => const NovelRankingPage(), () => const NovelRankingPage(),
() => const SettingsPage(),
]; ];
void navigate(int index) { void navigate(int index) {
@@ -246,7 +272,12 @@ class _MainPageState extends State<MainPage> with WindowListener {
child: Text("Invalid Page: $index"), child: Text("Invalid Page: $index"),
); );
navigatorKey.currentState!.pushAndRemoveUntil( navigatorKey.currentState!.pushAndRemoveUntil(
AppPageRoute(builder: (context) => page()), (route) => false); AppPageRoute(
builder: (context) => page(),
isRoot: true,
),
(route) => false,
);
} }
NavigationAppBar buildAppBar( NavigationAppBar buildAppBar(
@@ -297,7 +328,26 @@ class _MainPageState extends State<MainPage> with WindowListener {
], ],
), ),
).paddingTop(4).paddingLeft(4), ).paddingTop(4).paddingLeft(4),
if (App.isDesktop) const SizedBox(width: 128), if (App.isDesktop)
const SizedBox(width: 128)
else
Tooltip(
message: "Search".tl,
child: IconButton(
icon: const Icon(
MdIcons.search,
size: 18,
),
onPressed: () {
if (index == 1) {
return;
}
setState(() {
index = 1;
});
navigate(1);
},
)),
], ],
), ),
), ),
@@ -315,6 +365,25 @@ class _MainPageState extends State<MainPage> with WindowListener {
: null, : null,
); );
} }
final popValue = ValueNotifier(false);
@override
ValueListenable<bool> get canPopNotifier => popValue;
@override
void onPopInvokedWithResult(bool didPop, result) {
if (App.rootNavigatorKey.currentState?.canPop() ?? false) {
App.rootNavigatorKey.currentState?.pop();
} else if (App.mainNavigatorKey?.currentState?.canPop() ?? false) {
App.mainNavigatorKey?.currentState?.pop();
} else {
SystemNavigator.pop();
}
}
@override
void onPopInvoked(bool didPop) {}
} }
class _BackButton extends StatefulWidget { class _BackButton extends StatefulWidget {
@@ -365,7 +434,7 @@ class _BackButtonState extends State<_BackButton> {
return NavigationPaneTheme( return NavigationPaneTheme(
data: NavigationPaneTheme.of(context).merge(NavigationPaneThemeData( data: NavigationPaneTheme.of(context).merge(NavigationPaneThemeData(
unselectedIconColor: ButtonState.resolveWith((states) { unselectedIconColor: WidgetStateProperty.resolveWith((states) {
if (states.isDisabled) { if (states.isDisabled) {
return ButtonThemeData.buttonColor(context, states); return ButtonThemeData.buttonColor(context, states);
} }
@@ -618,16 +687,16 @@ class UserPane extends PaneItem {
final tileColor = this.tileColor ?? final tileColor = this.tileColor ??
theme.tileColor ?? theme.tileColor ??
kDefaultPaneItemColor(context, mode == PaneDisplayMode.top); kDefaultPaneItemColor(context, mode == PaneDisplayMode.top);
final newStates = states.toSet()..remove(ButtonStates.disabled); final newStates = states.toSet()..remove(WidgetState.disabled);
if (selected && selectedTileColor != null) { if (selected && selectedTileColor != null) {
return selectedTileColor!.resolve(newStates); return selectedTileColor!.resolve(newStates);
} }
return tileColor.resolve( return tileColor.resolve(
selected selected
? { ? {
states.isHovering states.isHovered
? ButtonStates.pressing ? WidgetState.pressed
: ButtonStates.hovering, : WidgetState.hovered,
} }
: newStates, : newStates,
); );
@@ -655,13 +724,16 @@ class UserPane extends PaneItem {
/// Close /// Close
class CloseIcon extends StatelessWidget { class CloseIcon extends StatelessWidget {
final Color color; final Color color;
const CloseIcon({super.key, required this.color}); const CloseIcon({super.key, required this.color});
@override @override
Widget build(BuildContext context) => _AlignedPaint(_ClosePainter(color)); Widget build(BuildContext context) => _AlignedPaint(_ClosePainter(color));
} }
class _ClosePainter extends _IconPainter { class _ClosePainter extends _IconPainter {
_ClosePainter(super.color); _ClosePainter(super.color);
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
Paint p = getPaint(color, true); Paint p = getPaint(color, true);
@@ -673,13 +745,16 @@ class _ClosePainter extends _IconPainter {
/// Maximize /// Maximize
class MaximizeIcon extends StatelessWidget { class MaximizeIcon extends StatelessWidget {
final Color color; final Color color;
const MaximizeIcon({super.key, required this.color}); const MaximizeIcon({super.key, required this.color});
@override @override
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color)); Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
} }
class _MaximizePainter extends _IconPainter { class _MaximizePainter extends _IconPainter {
_MaximizePainter(super.color); _MaximizePainter(super.color);
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
Paint p = getPaint(color); Paint p = getPaint(color);
@@ -690,16 +765,19 @@ class _MaximizePainter extends _IconPainter {
/// Restore /// Restore
class RestoreIcon extends StatelessWidget { class RestoreIcon extends StatelessWidget {
final Color color; final Color color;
const RestoreIcon({ const RestoreIcon({
super.key, super.key,
required this.color, required this.color,
}); });
@override @override
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color)); Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
} }
class _RestorePainter extends _IconPainter { class _RestorePainter extends _IconPainter {
_RestorePainter(super.color); _RestorePainter(super.color);
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
Paint p = getPaint(color); Paint p = getPaint(color);
@@ -716,13 +794,16 @@ class _RestorePainter extends _IconPainter {
/// Minimize /// Minimize
class MinimizeIcon extends StatelessWidget { class MinimizeIcon extends StatelessWidget {
final Color color; final Color color;
const MinimizeIcon({super.key, required this.color}); const MinimizeIcon({super.key, required this.color});
@override @override
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color)); Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
} }
class _MinimizePainter extends _IconPainter { class _MinimizePainter extends _IconPainter {
_MinimizePainter(super.color); _MinimizePainter(super.color);
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
Paint p = getPaint(color); Paint p = getPaint(color);
@@ -734,6 +815,7 @@ class _MinimizePainter extends _IconPainter {
/// Helpers /// Helpers
abstract class _IconPainter extends CustomPainter { abstract class _IconPainter extends CustomPainter {
_IconPainter(this.color); _IconPainter(this.color);
final Color color; final Color color;
@override @override
@@ -742,6 +824,7 @@ abstract class _IconPainter extends CustomPainter {
class _AlignedPaint extends StatelessWidget { class _AlignedPaint extends StatelessWidget {
const _AlignedPaint(this.painter); const _AlignedPaint(this.painter);
final CustomPainter painter; final CustomPainter painter;
@override @override

View File

@@ -3,6 +3,7 @@ import 'package:pixes/appdata.dart';
import 'package:pixes/components/grid.dart'; import 'package:pixes/components/grid.dart';
import 'package:pixes/components/loading.dart'; import 'package:pixes/components/loading.dart';
import 'package:pixes/components/novel.dart'; import 'package:pixes/components/novel.dart';
import 'package:pixes/components/segmented_button.dart';
import 'package:pixes/components/title_bar.dart'; import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/widget_utils.dart'; import 'package:pixes/foundation/widget_utils.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
@@ -17,11 +18,41 @@ class NovelBookmarksPage extends StatefulWidget {
class _NovelBookmarksPageState class _NovelBookmarksPageState
extends MultiPageLoadingState<NovelBookmarksPage, Novel> { extends MultiPageLoadingState<NovelBookmarksPage, Novel> {
bool public = true;
@override
Widget? buildFrame(BuildContext context, Widget child) {
return Column(
children: [
TitleBar(
title: "Bookmarks".tl,
action: SegmentedButton(
options: [
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
],
onPressed: (key) {
var newPublic = key == "public";
if (newPublic != public) {
public = newPublic;
nextUrl = null;
reset();
}
},
value: public ? "public" : "private",
),
),
Expanded(
child: child,
)
],
);
}
@override @override
Widget buildContent(BuildContext context, List<Novel> data) { Widget buildContent(BuildContext context, List<Novel> data) {
return Column( return Column(
children: [ children: [
TitleBar(title: "Bookmarks".tl),
Expanded( Expanded(
child: GridViewWithFixedItemHeight( child: GridViewWithFixedItemHeight(
itemCount: data.length, itemCount: data.length,
@@ -45,7 +76,7 @@ class _NovelBookmarksPageState
Future<Res<List<Novel>>> loadData(int page) async { Future<Res<List<Novel>>> loadData(int page) async {
if (nextUrl == "end") return Res.error("No more data"); if (nextUrl == "end") return Res.error("No more data");
var res = nextUrl == null var res = nextUrl == null
? await Network().getBookmarkedNovels(appdata.account!.user.id) ? await Network().getBookmarkedNovels(appdata.account!.user.id, public)
: await Network().getNovelsWithNextUrl(nextUrl!); : await Network().getNovelsWithNextUrl(nextUrl!);
nextUrl = res.subData ?? "end"; nextUrl = res.subData ?? "end";
return res; return res;

View File

@@ -121,18 +121,18 @@ class _NovelPageState extends State<NovelPage> {
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: Row( child: Row(
children: [ children: [
const SizedBox( const SizedBox(width: 2),
width: 2,
),
Expanded( Expanded(
child: Container( child: Container(
height: 68, height: 68,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, color: ColorScheme.of(context).outlineVariant,
width: 0.6), width: 0.6,
borderRadius: BorderRadius.circular(4)), ),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row( child: Row(
children: [ children: [
Column( Column(
@@ -148,31 +148,31 @@ class _NovelPageState extends State<NovelPage> {
) )
], ],
), ),
const SizedBox( const SizedBox(width: 8),
width: 12,
),
Text( Text(
widget.novel.totalViews.toString(), widget.novel.totalViews.toString(),
style: TextStyle( style: TextStyle(
color: ColorScheme.of(context).primary, color: ColorScheme.of(context).primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 18), fontSize: 18,
),
) )
], ],
), ),
), ),
), ),
const SizedBox( const SizedBox(width: 16),
width: 16,
),
Expanded( Expanded(
child: Container( child: Container(
height: 68, height: 68,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, width: 0.6), color: ColorScheme.of(context).outlineVariant,
borderRadius: BorderRadius.circular(4)), width: 0.6,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), ),
borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row( child: Row(
children: [ children: [
Column( Column(
@@ -188,22 +188,19 @@ class _NovelPageState extends State<NovelPage> {
) )
], ],
), ),
const SizedBox( const SizedBox(width: 8),
width: 12,
),
Text( Text(
widget.novel.totalBookmarks.toString(), widget.novel.totalBookmarks.toString(),
style: TextStyle( style: TextStyle(
color: ColorScheme.of(context).primary, color: ColorScheme.of(context).primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 18), fontSize: 18,
),
) )
], ],
), ),
)), )),
const SizedBox( const SizedBox(width: 2),
width: 2,
),
], ],
), ),
); );
@@ -214,7 +211,7 @@ class _NovelPageState extends State<NovelPage> {
constraints: const BoxConstraints(maxWidth: 560), constraints: const BoxConstraints(maxWidth: 560),
child: Card( child: Card(
margin: const EdgeInsets.only(left: 2, right: 2, bottom: 12), margin: const EdgeInsets.only(left: 2, right: 2, bottom: 12),
borderColor: ColorScheme.of(context).outlineVariant.withOpacity(0.52), borderColor: ColorScheme.of(context).outlineVariant.toOpacity(0.52),
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
@@ -241,25 +238,30 @@ class _NovelPageState extends State<NovelPage> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Text(widget.novel.author.name, children: [
Text(
widget.novel.author.name,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
)), ),
Text( maxLines: 1,
widget.novel.createDate.toString().substring(0, 10), overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: ColorScheme.of(context).outline,
), ),
), Text(
], widget.novel.createDate.toString().substring(0, 10),
style: TextStyle(
fontSize: 12,
color: ColorScheme.of(context).outline,
),
),
],
),
), ),
const Spacer(),
const Icon(MdIcons.chevron_right) const Icon(MdIcons.chevron_right)
], ],
), ),
@@ -271,15 +273,44 @@ class _NovelPageState extends State<NovelPage> {
bool isAddingFavorite = false; bool isAddingFavorite = false;
var favoriteFlyout = FlyoutController();
Widget buildActions() { Widget buildActions() {
void favorite() async { void favorite() async {
if (isAddingFavorite) return; if (isAddingFavorite) return;
bool? public;
if (!widget.novel.isBookmarked) {
await favoriteFlyout.showFlyout(
navigatorKey: App.rootNavigatorKey.currentState!,
builder: (context) {
return MenuFlyout(
items: [
MenuFlyoutItem(
text: Text("Public".tl),
onPressed: () {
public = true;
},
),
MenuFlyoutItem(
text: Text("Private".tl),
onPressed: () {
public = false;
},
),
],
);
},
);
if (public == null) {
return;
}
}
setState(() { setState(() {
isAddingFavorite = true; isAddingFavorite = true;
}); });
var res = widget.novel.isBookmarked var res = widget.novel.isBookmarked
? await Network().deleteFavoriteNovel(widget.novel.id.toString()) ? await Network().deleteFavoriteNovel(widget.novel.id.toString())
: await Network().favoriteNovel(widget.novel.id.toString()); : await Network().favoriteNovel(widget.novel.id.toString(), public!);
if (res.error) { if (res.error) {
if (mounted) { if (mounted) {
context.showToast(message: res.errorMessage ?? "Network Error"); context.showToast(message: res.errorMessage ?? "Network Error");
@@ -337,38 +368,41 @@ class _NovelPageState extends State<NovelPage> {
context.to(() => NovelReadingPage(widget.novel)); context.to(() => NovelReadingPage(widget.novel));
}), }),
const SizedBox(width: 16), const SizedBox(width: 16),
Button( FlyoutTarget(
onPressed: favorite, controller: favoriteFlyout,
child: Row( child: Button(
mainAxisAlignment: constrains.maxWidth > 420 onPressed: favorite,
? MainAxisAlignment.start child: Row(
: MainAxisAlignment.center, mainAxisAlignment: constrains.maxWidth > 420
children: [ ? MainAxisAlignment.start
if (isAddingFavorite) : MainAxisAlignment.center,
const SizedBox( children: [
width: 18, if (isAddingFavorite)
height: 18, const SizedBox(
child: ProgressRing( width: 18,
strokeWidth: 2, height: 18,
), child: ProgressRing(
) strokeWidth: 2,
else if (widget.novel.isBookmarked) ),
Icon( )
MdIcons.favorite, else if (widget.novel.isBookmarked)
size: 18, Icon(
color: ColorScheme.of(context).error, MdIcons.favorite,
) size: 18,
else color: ColorScheme.of(context).error,
const Icon(MdIcons.favorite_outline, size: 18), )
if (constrains.maxWidth > 420) else
const SizedBox(width: 12), const Icon(MdIcons.favorite_outline, size: 18),
if (constrains.maxWidth > 420) Text("Favorite".tl) if (constrains.maxWidth > 420)
], const SizedBox(width: 12),
) if (constrains.maxWidth > 420) Text("Favorite".tl)
.fixWidth(shouldFillSpace ],
? width / 4 - 4 - kFluentButtonPadding )
: 64) .fixWidth(shouldFillSpace
.fixHeight(32), ? width / 4 - 4 - kFluentButtonPadding
: 64)
.fixHeight(32),
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Button( Button(
@@ -555,7 +589,7 @@ class _NovelSeriesWidgetState
color: FluentTheme.of(context).cardColor, color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant.withOpacity(0.6), color: ColorScheme.of(context).outlineVariant.toOpacity(0.6),
width: 0.5, width: 0.5,
)), )),
sliver: SliverMainAxisGroup(slivers: [ sliver: SliverMainAxisGroup(slivers: [
@@ -646,7 +680,7 @@ class _NovelPageWithIdState extends LoadingState<NovelPageWithId, Novel> {
} }
class _RelatedNovelsPage extends StatefulWidget { class _RelatedNovelsPage extends StatefulWidget {
const _RelatedNovelsPage(this.id, {super.key}); const _RelatedNovelsPage(this.id);
final String id; final String id;

View File

@@ -7,7 +7,9 @@ import 'package:pixes/components/page_route.dart';
import 'package:pixes/components/title_bar.dart'; import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/image_provider.dart'; import 'package:pixes/foundation/image_provider.dart';
import 'package:pixes/foundation/log.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
import 'package:pixes/network/translator.dart';
import 'package:pixes/pages/image_page.dart'; import 'package:pixes/pages/image_page.dart';
import 'package:pixes/pages/main_page.dart'; import 'package:pixes/pages/main_page.dart';
import 'package:pixes/utils/ext.dart'; import 'package:pixes/utils/ext.dart';
@@ -27,15 +29,36 @@ class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
bool isShowingSettings = false; bool isShowingSettings = false;
String? translatedContent;
@override @override
void initState() { void initState() {
action = TitleBarAction(MdIcons.tune, "Settings".tl, () { action = TitleBarAction(MdIcons.tune, "Settings".tl, () {
if (!isShowingSettings) { if (!isShowingSettings) {
_NovelReadingSettings.show(context, () { _NovelReadingSettings.show(
setState(() {}); context,
}).then((value) { () {
isShowingSettings = false; setState(() {});
}); },
TranslationController(
content: data!,
isTranslated: translatedContent != null,
onTranslated: (s) {
setState(() {
translatedContent = s;
});
},
revert: () {
setState(() {
translatedContent = null;
});
},
),
).then(
(value) {
isShowingSettings = false;
},
);
isShowingSettings = true; isShowingSettings = true;
} else { } else {
Navigator.of(context).pop(); Navigator.of(context).pop();
@@ -92,7 +115,7 @@ class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
); );
yield const SizedBox(height: 12.0); yield const SizedBox(height: 12.0);
var novelContent = data!.split('\n'); var novelContent = (translatedContent ?? data!).split('\n');
for (var content in novelContent) { for (var content in novelContent) {
if (content.isEmpty) continue; if (content.isEmpty) continue;
if (content.startsWith('[uploadedimage:')) { if (content.startsWith('[uploadedimage:')) {
@@ -132,14 +155,38 @@ class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
} }
} }
class TranslationController {
final String content;
final bool isTranslated;
final void Function(String translated) onTranslated;
final void Function() revert;
const TranslationController({
required this.content,
required this.isTranslated,
required this.onTranslated,
required this.revert,
});
}
class _NovelReadingSettings extends StatefulWidget { class _NovelReadingSettings extends StatefulWidget {
const _NovelReadingSettings(this.callback); const _NovelReadingSettings(this.callback, this.controller);
final void Function() callback; final void Function() callback;
static Future show(BuildContext context, void Function() callback) { final TranslationController controller;
return Navigator.of(context)
.push(SideBarRoute(_NovelReadingSettings(callback))); static Future show(
BuildContext context,
void Function() callback,
TranslationController controller,
) {
return Navigator.of(context).push(
SideBarRoute(_NovelReadingSettings(callback, controller)),
);
} }
@override @override
@@ -256,9 +303,64 @@ class __NovelReadingSettingsState extends State<_NovelReadingSettings> {
}), }),
]), ]),
), ),
), ).paddingBottom(8),
Card(
padding: EdgeInsets.zero,
child: ListTile(
title: Text("Translate Novel".tl),
trailing: widget.controller.isTranslated
? Button(
onPressed: () {
widget.controller.revert();
context.pop();
},
child: Text("Revert".tl),
)
: Button(
onPressed: translate,
child: isTranslating
? const SizedBox(
width: 42,
height: 18,
child: Center(
child: SizedBox.square(
dimension: 18,
child: ProgressRing(
strokeWidth: 2,
),
),
),
)
: Text("Translate".tl),
),
),
).paddingHorizontal(8).paddingBottom(8),
], ],
), ),
); );
} }
bool isTranslating = false;
void translate() async {
setState(() {
isTranslating = true;
});
try {
var translated = await Translator.instance
.translate(widget.controller.content, "zh-CN");
widget.controller.onTranslated(translated);
if (mounted) {
context.pop();
}
} catch (e) {
setState(() {
isTranslating = false;
});
if (mounted) {
context.showToast(message: "Failed to translate".tl);
}
Log.error("Translate", e.toString());
}
}
} }

View File

@@ -45,7 +45,7 @@ class _RecommendationPageState extends State<RecommendationPage> {
title: "Explore".tl, title: "Explore".tl,
action: SegmentedButton<int>( action: SegmentedButton<int>(
options: [ options: [
SegmentedButtonOption(0, "Artworks".tl), SegmentedButtonOption(0, "Illustrations".tl),
SegmentedButtonOption(1, "Mangas".tl), SegmentedButtonOption(1, "Mangas".tl),
SegmentedButtonOption(2, "Users".tl), SegmentedButtonOption(2, "Users".tl),
], ],

View File

@@ -0,0 +1,69 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:pixes/components/illust_widget.dart';
import 'package:pixes/components/loading.dart';
import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/app.dart';
import 'package:pixes/network/network.dart';
import 'package:pixes/utils/translation.dart';
class RelatedIllustsPage extends StatefulWidget {
const RelatedIllustsPage(this.id, {super.key});
final String id;
@override
State<RelatedIllustsPage> createState() => _RelatedIllustsPageState();
}
class _RelatedIllustsPageState
extends MultiPageLoadingState<RelatedIllustsPage, Illust> {
@override
Widget? buildFrame(BuildContext context, Widget child) {
return Column(
children: [
TitleBar(title: "Related artworks".tl),
Expanded(
child: child,
)
],
);
}
@override
Widget buildContent(BuildContext context, final List<Illust> data) {
return LayoutBuilder(builder: (context, constrains) {
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8) +
EdgeInsets.only(bottom: context.padding.bottom),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),
itemCount: data.length,
itemBuilder: (context, index) {
if (index == data.length - 1) {
nextPage();
}
return IllustWidget(data[index]);
},
);
});
}
String? nextUrl;
@override
Future<Res<List<Illust>>> loadData(page) async {
if (nextUrl == "end") {
return Res.error("No more data");
}
var res = nextUrl == null
? await Network().relatedIllusts(widget.id)
: await Network().getIllustsWithNextUrl(nextUrl!);
if (!res.error) {
nextUrl = res.subData;
nextUrl ??= "end";
}
return res;
}
}

View File

@@ -4,6 +4,7 @@ import 'package:pixes/appdata.dart';
import 'package:pixes/components/loading.dart'; import 'package:pixes/components/loading.dart';
import 'package:pixes/components/novel.dart'; import 'package:pixes/components/novel.dart';
import 'package:pixes/components/page_route.dart'; import 'package:pixes/components/page_route.dart';
import 'package:pixes/components/search_field.dart';
import 'package:pixes/components/user_preview.dart'; import 'package:pixes/components/user_preview.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
@@ -12,6 +13,7 @@ import 'package:pixes/pages/novel_page.dart';
import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/pages/user_info_page.dart';
import 'package:pixes/utils/app_links.dart'; import 'package:pixes/utils/app_links.dart';
import 'package:pixes/utils/block.dart'; import 'package:pixes/utils/block.dart';
import 'package:pixes/utils/debounce.dart';
import 'package:pixes/utils/ext.dart'; import 'package:pixes/utils/ext.dart';
import 'package:pixes/utils/translation.dart'; import 'package:pixes/utils/translation.dart';
@@ -21,6 +23,15 @@ import '../components/illust_widget.dart';
import '../components/md.dart'; import '../components/md.dart';
import '../foundation/image_provider.dart'; import '../foundation/image_provider.dart';
const searchTypes = [
"Search artwork",
"Search novel",
"Search user",
"Artwork ID",
"Artist ID",
"Novel ID"
];
class SearchPage extends StatefulWidget { class SearchPage extends StatefulWidget {
const SearchPage({super.key}); const SearchPage({super.key});
@@ -29,20 +40,9 @@ class SearchPage extends StatefulWidget {
} }
class _SearchPageState extends State<SearchPage> { class _SearchPageState extends State<SearchPage> {
String text = "";
int searchType = 0; int searchType = 0;
static const searchTypes = [ void search(String text) {
"Search artwork",
"Search novel",
"Search user",
"Artwork ID",
"Artist ID",
"Novel ID"
];
void search() {
if (text.isURL && handleLink(Uri.parse(text))) { if (text.isURL && handleLink(Uri.parse(text))) {
return; return;
} else if ("https://$text".isURL && } else if ("https://$text".isURL &&
@@ -71,9 +71,19 @@ class _SearchPageState extends State<SearchPage> {
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
content: Column( content: Column(
children: [ children: [
buildSearchBar(), _SearchBar(
const SizedBox( searchType: searchType,
height: 8, onTypeChanged: (type) {
setState(() {
searchType = type;
});
},
onSearch: (text) {
if (text.isEmpty) {
return;
}
search(text);
},
), ),
const Expanded( const Expanded(
child: _TrendingTagsView(), child: _TrendingTagsView(),
@@ -82,100 +92,6 @@ class _SearchPageState extends State<SearchPage> {
), ),
); );
} }
final optionController = FlyoutController();
Widget buildSearchBar() {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: SizedBox(
height: 42,
width: double.infinity,
child: LayoutBuilder(
builder: (context, constrains) {
return SizedBox(
height: 42,
width: constrains.maxWidth,
child: Row(
children: [
Expanded(
child: TextBox(
placeholder:
'${searchTypes[searchType].tl} / ${"Open link".tl}',
onChanged: (s) => text = s,
onSubmitted: (s) => search(),
foregroundDecoration: BoxDecoration(
border: Border.all(
color: ColorScheme.of(context)
.outlineVariant
.withOpacity(0.6)),
borderRadius: BorderRadius.circular(4)),
suffix: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: search,
child: const Icon(
FluentIcons.search,
size: 16,
).paddingHorizontal(12),
),
),
),
),
const SizedBox(
width: 4,
),
FlyoutTarget(
controller: optionController,
child: Button(
child: const SizedBox(
height: 42,
child: Center(
child: Icon(FluentIcons.chevron_down),
),
),
onPressed: () {
optionController.showFlyout(
placementMode: FlyoutPlacementMode.bottomCenter,
builder: buildSearchOption,
barrierColor: Colors.transparent);
},
),
),
const SizedBox(
width: 4,
),
Button(
child: const SizedBox(
height: 42,
child: Center(
child: Icon(FluentIcons.settings),
),
),
onPressed: () {
Navigator.of(context).push(SideBarRoute(SearchSettings(
isNovel: searchType == 1,
)));
},
)
],
),
);
},
),
).paddingHorizontal(16),
);
}
Widget buildSearchOption(BuildContext context) {
return MenuFlyout(
items: List.generate(
searchTypes.length,
(index) => MenuFlyoutItem(
text: Text(searchTypes[index].tl),
onPressed: () => setState(() => searchType = index))),
);
}
} }
class _TrendingTagsView extends StatefulWidget { class _TrendingTagsView extends StatefulWidget {
@@ -246,7 +162,7 @@ class _TrendingTagsViewState
decoration: BoxDecoration( decoration: BoxDecoration(
color: FluentTheme.of(context) color: FluentTheme.of(context)
.micaBackgroundColor .micaBackgroundColor
.withOpacity(0.84), .toOpacity(0.84),
borderRadius: BorderRadius.circular(4)), borderRadius: BorderRadius.circular(4)),
child: Text(text) child: Text(text)
.paddingHorizontal(4) .paddingHorizontal(4)
@@ -418,6 +334,19 @@ class _SearchSettingsState extends State<SearchSettings> {
})) }))
.toList(), .toList(),
)), )),
const SizedBox(height: 4),
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(FluentIcons.info, size: 16),
const SizedBox(
width: 4,
),
Text("Close the pane to apply the settings".tl)
],
),
),
SizedBox( SizedBox(
height: context.padding.bottom, height: context.padding.bottom,
) )
@@ -455,6 +384,12 @@ class _SearchResultPageState
late final controller = TextEditingController(text: widget.keyword); late final controller = TextEditingController(text: widget.keyword);
@override
void reset() {
nextUrl = null;
super.reset();
}
void search() { void search() {
if (keyword != oldKeyword) { if (keyword != oldKeyword) {
oldKeyword = keyword; oldKeyword = keyword;
@@ -516,12 +451,16 @@ class _SearchResultPageState
placeholder: "Search artworks".tl, placeholder: "Search artworks".tl,
onChanged: (s) => keyword = s, onChanged: (s) => keyword = s,
onSubmitted: (s) => search(), onSubmitted: (s) => search(),
foregroundDecoration: BoxDecoration( foregroundDecoration: WidgetStatePropertyAll(
BoxDecoration(
border: Border.all( border: Border.all(
color: ColorScheme.of(context) color: ColorScheme.of(context)
.outlineVariant .outlineVariant
.withOpacity(0.6)), .toOpacity(0.6),
borderRadius: BorderRadius.circular(4)), ),
borderRadius: BorderRadius.circular(4),
),
),
suffix: MouseRegion( suffix: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
@@ -708,12 +647,14 @@ class _SearchNovelResultPageState
placeholder: "Search artworks".tl, placeholder: "Search artworks".tl,
onChanged: (s) => keyword = s, onChanged: (s) => keyword = s,
onSubmitted: (s) => search(), onSubmitted: (s) => search(),
foregroundDecoration: BoxDecoration( foregroundDecoration: WidgetStatePropertyAll(
border: Border.all( BoxDecoration(
color: ColorScheme.of(context) border: Border.all(
.outlineVariant color: ColorScheme.of(context)
.withOpacity(0.6)), .outlineVariant
borderRadius: BorderRadius.circular(4)), .toOpacity(0.6)),
borderRadius: BorderRadius.circular(4)),
),
suffix: MouseRegion( suffix: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
@@ -776,3 +717,184 @@ class _SearchNovelResultPageState
return res; return res;
} }
} }
class _SearchBar extends StatefulWidget {
const _SearchBar({
required this.searchType,
required this.onTypeChanged,
required this.onSearch,
});
final int searchType;
final void Function(int) onTypeChanged;
final void Function(String) onSearch;
@override
State<_SearchBar> createState() => _SearchBarState();
}
class _SearchBarState extends State<_SearchBar> {
final optionController = FlyoutController();
final textController = TextEditingController();
var autoCompleteItems = <AutoCompleteItem>[];
var debouncer = Debounce(delay: const Duration(milliseconds: 300));
var autoCompleteKey = 0;
var isLoadingAutoCompleteItems = false;
Widget buildSearchOption(BuildContext context) {
return MenuFlyout(
items: List.generate(
searchTypes.length,
(index) => MenuFlyoutItem(
text: Text(searchTypes[index].tl),
onPressed: () => widget.onTypeChanged(index),
),
),
);
}
void onTextChanged(String text) {
if (widget.searchType == 3 ||
widget.searchType == 4 ||
widget.searchType == 5) {
return;
}
if (text.isEmpty) {
setState(() {
autoCompleteItems = [];
isLoadingAutoCompleteItems = false;
});
return;
}
setState(() {
isLoadingAutoCompleteItems = true;
});
debouncer.call(() async {
var key = ++autoCompleteKey;
var res = await Network().getAutoCompleteTags(text);
if (res.error) {
return;
}
var items = res.data.map((e) {
return AutoCompleteItem(
title: e.name,
subtitle: e.translatedName,
onTap: () {
textController.text = e.name;
widget.onSearch(e.name);
},
);
}).toList();
if (key != autoCompleteKey) {
return; // ignore old request
}
setState(() {
autoCompleteItems = items;
isLoadingAutoCompleteItems = false;
});
});
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: SizedBox(
height: 42,
width: double.infinity,
child: LayoutBuilder(
builder: (context, constrains) {
return SizedBox(
height: 42,
width: constrains.maxWidth,
child: Row(
children: [
Expanded(
child: SearchField(
enableAutoComplete: widget.searchType != 3 &&
widget.searchType != 4 &&
widget.searchType != 5,
textEditingController: textController,
autoCompleteNoResultsText: "No results found".tl,
isLoadingAutoCompleteItems: isLoadingAutoCompleteItems,
autoCompleteItems: autoCompleteItems,
padding: const EdgeInsets.symmetric(horizontal: 12),
placeholder:
'${searchTypes[widget.searchType].tl} / ${"Open link".tl}',
onChanged: onTextChanged,
onSubmitted: widget.onSearch,
foregroundDecoration: WidgetStatePropertyAll(
BoxDecoration(
border: Border.all(
color: ColorScheme.of(context)
.outlineVariant
.toOpacity(0.6),
),
borderRadius: BorderRadius.circular(4),
),
),
trailing: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => widget.onSearch(textController.text),
child: const Icon(
FluentIcons.search,
size: 16,
).paddingHorizontal(12),
),
),
),
),
const SizedBox(width: 4),
FlyoutTarget(
controller: optionController,
child: Button(
child: const SizedBox(
height: 42,
child: Center(
child: Icon(FluentIcons.chevron_down),
),
),
onPressed: () {
optionController.showFlyout(
placementMode: FlyoutPlacementMode.bottomCenter,
builder: buildSearchOption,
barrierColor: Colors.transparent,
);
},
),
),
const SizedBox(width: 4),
Button(
child: const SizedBox(
height: 42,
child: Center(
child: Icon(FluentIcons.settings),
),
),
onPressed: () {
Navigator.of(context).push(SideBarRoute(SearchSettings(
isNovel: widget.searchType == 1,
)));
},
)
],
),
);
},
),
).paddingHorizontal(16),
);
}
}

View File

@@ -88,6 +88,7 @@ class _SettingsPageState extends State<SettingsPage> {
child: Text('Continue'.tl), child: Text('Continue'.tl),
onPressed: () { onPressed: () {
appdata.account = null; appdata.account = null;
appdata.writeData();
App.rootNavigatorKey.currentState!.pushAndRemoveUntil( App.rootNavigatorKey.currentState!.pushAndRemoveUntil(
AppPageRoute( AppPageRoute(
builder: (context) => const MainPage()), builder: (context) => const MainPage()),
@@ -181,6 +182,16 @@ class _SettingsPageState extends State<SettingsPage> {
child: Column( child: Column(
children: [ children: [
buildItem(title: "Version", subtitle: App.version), buildItem(title: "Version", subtitle: App.version),
buildItem(
title: "Check for updates on startup".tl,
action: ToggleSwitch(
checked: appdata.settings["checkUpdate"],
onChanged: (value) {
setState(() {
appdata.settings["checkUpdate"] = value;
});
appdata.writeData();
})),
buildItem( buildItem(
title: "Github", title: "Github",
action: IconButton( action: IconButton(
@@ -198,7 +209,7 @@ class _SettingsPageState extends State<SettingsPage> {
MdIcons.open_in_new, MdIcons.open_in_new,
size: 18, size: 18,
), ),
onPressed: () => launchUrlString("https://t.me/pica_group"), onPressed: () => launchUrlString("https://t.me/venera_dev"),
)), )),
buildItem( buildItem(
title: "Logs", title: "Logs",
@@ -217,6 +228,14 @@ class _SettingsPageState extends State<SettingsPage> {
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Column( child: Column(
children: [ children: [
buildItem(
title: "Initial Page".tl,
action: Button(
child: Text("Edit".tl).fixWidth(64),
onPressed: () {
context.to(() => const _SetInitialPageWidget());
},
)),
buildItem( buildItem(
title: "Proxy".tl, title: "Proxy".tl,
action: Button( action: Button(
@@ -262,6 +281,19 @@ class _SettingsPageState extends State<SettingsPage> {
}); });
appdata.writeData(); appdata.writeData();
})), })),
buildItem(
title: "Emphasize artworks from following artists".tl,
subtitle: "The border of the artworks will be darker".tl,
action: ToggleSwitch(
checked:
appdata.settings['emphasizeArtworksFromFollowingArtists'],
onChanged: (value) {
setState(() {
appdata.settings[
'emphasizeArtworksFromFollowingArtists'] = value;
});
appdata.writeData();
})),
], ],
), ),
); );
@@ -591,6 +623,8 @@ class _ShortcutsSettingsState extends State<ShortcutsSettings> {
"Add to favorites", "Add to favorites",
"Download", "Download",
"Follow the artist", "Follow the artist",
"Show comments",
"Show original image"
]; ];
@override @override
@@ -636,3 +670,69 @@ class _ShortcutsSettingsState extends State<ShortcutsSettings> {
); );
} }
} }
class _SetInitialPageWidget extends StatefulWidget {
const _SetInitialPageWidget();
@override
State<_SetInitialPageWidget> createState() => _SetInitialPageWidgetState();
}
class _SetInitialPageWidgetState extends State<_SetInitialPageWidget> {
int index = appdata.settings["initialPage"] ?? 4;
static const pageNames = [
"Search",
"Downloading",
"Downloaded",
"Explore",
"Bookmarks",
"Following",
"History",
"Ranking",
"Recommendation",
"Bookmarks",
"Following",
"Ranking",
];
@override
Widget build(BuildContext context) {
return ScaffoldPage(
header: TitleBar(title: "Initial Page".tl),
content: ListView.builder(
itemCount: pageNames.length + 2,
itemBuilder: (context, index) {
if (index == 3) {
return Text('${"Illustrations".tl}/${"Manga".tl}').paddingHorizontal(16).paddingVertical(8);
} else if (index > 3) {
index--;
}
if (index == 8) {
return Text("Novel".tl).paddingHorizontal(16).paddingVertical(8);
} else if (index > 8) {
index--;
}
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
padding: EdgeInsets.zero,
child: ListTile(
title: Text(pageNames[index].tl),
trailing: RadioButton(
checked: this.index - 1 == index,
onChanged: (value) {
setState(() {
this.index = index + 1;
appdata.settings["initialPage"] = index + 1;
appdata.writeData();
});
},
),
),
);
},
),
);
}
}

View File

@@ -63,7 +63,7 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
_RelatedUsers(widget.id), _RelatedUsers(widget.id),
buildInformation(), buildInformation(),
buildArtworkHeader(), buildArtworkHeader(),
if (page == 2) if (page == 4)
_UserNovels(widget.id) _UserNovels(widget.id)
else else
_UserArtworks( _UserArtworks(
@@ -228,8 +228,10 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
SegmentedButton<int>( SegmentedButton<int>(
options: [ options: [
SegmentedButtonOption(0, "Artworks".tl), SegmentedButtonOption(0, "Artworks".tl),
SegmentedButtonOption(1, "Bookmarks".tl), SegmentedButtonOption(1, "Illustrations".tl),
SegmentedButtonOption(2, "Novels".tl), SegmentedButtonOption(2, "Mangas".tl),
SegmentedButtonOption(3, "Bookmarks".tl),
SegmentedButtonOption(4, "Novels".tl),
], ],
value: page, value: page,
onPressed: (value) { onPressed: (value) {
@@ -239,15 +241,24 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
}, },
), ),
const Spacer(), const Spacer(),
if (page != 2) if (page != 4)
BatchDownloadButton( BatchDownloadButton(
request: () { request: () {
if (page == 0) { switch (page) {
return Network().getUserIllusts(data!.id.toString()); case 0:
} else { return Network()
return Network() .getUserIllusts(data!.id.toString(), null);
.getUserBookmarks(data!.id.toString()); case 1:
return Network()
.getUserIllusts(data!.id.toString(), "illust");
case 2:
return Network()
.getUserIllusts(data!.id.toString(), "manga");
case 3:
return Network()
.getUserBookmarks(data!.id.toString());
} }
throw "Invalid page";
}, },
), ),
], ],
@@ -269,14 +280,14 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: ListTile( child: ListTile(
leading: icon == null title: Row(
? null children: [
: Icon( Icon(icon, size: 20),
icon, const SizedBox(width: 8),
size: 20, Text(title)
), ],
title: Text(title), ),
subtitle: SelectableText(content), subtitle: SelectableText(content).paddingLeft(icon == null ? 0 : 28),
trailing: trailing, trailing: trailing,
), ),
); );
@@ -409,8 +420,9 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> {
return Res.error("No more data"); return Res.error("No more data");
} }
var res = nextUrl == null var res = nextUrl == null
? (widget.type == 0 ? (widget.type != 3
? await Network().getUserIllusts(widget.uid) ? await Network().getUserIllusts(
widget.uid, [null, "illust", "manga"][widget.type])
: await Network().getUserBookmarks(widget.uid)) : await Network().getUserBookmarks(widget.uid))
: await Network().getIllustsWithNextUrl(nextUrl!); : await Network().getIllustsWithNextUrl(nextUrl!);
if (!res.error) { if (!res.error) {
@@ -422,7 +434,7 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> {
} }
class _UserNovels extends StatefulWidget { class _UserNovels extends StatefulWidget {
const _UserNovels(this.uid, {super.key}); const _UserNovels(this.uid);
final String uid; final String uid;
@@ -534,7 +546,7 @@ class _RelatedUsersState
return UserPreviewWidget(data[index]).fixWidth(342); return UserPreviewWidget(data[index]).fixWidth(342);
}, },
)); ));
if (MediaQuery.of(context).size.width > 500) { if (App.isDesktop) {
content = ScrollbarTheme.merge( content = ScrollbarTheme.merge(
data: const ScrollbarThemeData( data: const ScrollbarThemeData(
thickness: 6, thickness: 6,
@@ -542,7 +554,21 @@ class _RelatedUsersState
mainAxisMargin: 4, mainAxisMargin: 4,
hoveringPadding: EdgeInsets.zero, hoveringPadding: EdgeInsets.zero,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
hoveringMainAxisMargin: 4), hoveringMainAxisMargin: 4,
crossAxisMargin: 0,
hoveringCrossAxisMargin: 0),
child: content);
} else {
content = ScrollbarTheme.merge(
data: const ScrollbarThemeData(
thickness: 4,
hoveringThickness: 4,
mainAxisMargin: 4,
hoveringPadding: EdgeInsets.zero,
padding: EdgeInsets.zero,
hoveringMainAxisMargin: 4,
crossAxisMargin: 0,
hoveringCrossAxisMargin: 0),
child: content); child: content);
} }
return MediaQuery.removePadding( return MediaQuery.removePadding(

View File

@@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:app_links/app_links.dart'; import 'package:app_links/app_links.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/log.dart'; import 'package:pixes/foundation/log.dart';
import 'package:pixes/pages/illust_page.dart'; import 'package:pixes/pages/illust_page.dart';
@@ -8,21 +9,20 @@ import 'package:pixes/pages/novel_page.dart';
import 'package:pixes/pages/search_page.dart'; import 'package:pixes/pages/search_page.dart';
import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/pages/user_info_page.dart';
import 'package:pixes/utils/ext.dart'; import 'package:pixes/utils/ext.dart';
import 'package:pixes/utils/translation.dart';
import 'package:win32_registry/win32_registry.dart'; import 'package:win32_registry/win32_registry.dart';
Future<void> _register(String scheme) async { Future<void> _register(String scheme) async {
String appPath = Platform.resolvedExecutable; String appPath = Platform.resolvedExecutable;
String protocolRegKey = 'Software\\Classes\\$scheme'; String protocolRegKey = 'Software\\Classes\\$scheme';
RegistryValue protocolRegValue = const RegistryValue( RegistryValue protocolRegValue = const RegistryValue.string(
'URL Protocol', 'URL Protocol',
RegistryValueType.string,
'', '',
); );
String protocolCmdRegKey = 'shell\\open\\command'; String protocolCmdRegKey = 'shell\\open\\command';
RegistryValue protocolCmdRegValue = RegistryValue( RegistryValue protocolCmdRegValue = RegistryValue.string(
'', '',
RegistryValueType.string,
'"$appPath" "%1"', '"$appPath" "%1"',
); );
@@ -31,13 +31,40 @@ Future<void> _register(String scheme) async {
regKey.createKey(protocolCmdRegKey).createValue(protocolCmdRegValue); regKey.createKey(protocolCmdRegKey).createValue(protocolCmdRegValue);
} }
void _registerPixiv() async {
try {
await _register("pixiv");
} catch (e) {
// 注册失败会导致登录不可用
while (App.mainNavigatorKey == null) {
await Future.delayed(const Duration(milliseconds: 100));
}
Future.delayed(const Duration(seconds: 1), () async {
showDialog(
context: App.rootNavigatorKey.currentContext!,
builder: (context) => ContentDialog(
title: Text("Error".tl),
content: Text("${"Failed to register URL scheme.".tl}\n$e"),
actions: [
FilledButton(
child: Text("Retry".tl),
onPressed: () {
context.pop();
_registerPixiv();
})
],
));
});
}
}
bool Function(Uri uri)? onLink; bool Function(Uri uri)? onLink;
bool _firstLink = true; bool _firstLink = true;
void handleLinks() async { void handleLinks() async {
if (App.isWindows) { if (App.isWindows) {
await _register("pixiv"); _registerPixiv();
} }
AppLinks().uriLinkStream.listen((uri) async { AppLinks().uriLinkStream.listen((uri) async {
if (_firstLink) { if (_firstLink) {

28
lib/utils/debounce.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'dart:async';
import 'dart:ui';
class Debounce {
final Duration delay;
VoidCallback? _action;
Timer? _timer;
Debounce({required this.delay});
void call(VoidCallback action) {
_action = action;
_timer?.cancel();
_timer = Timer(delay, _execute);
}
void _execute() {
if (_action != null) {
_action!();
_action = null;
}
}
void cancel() {
_timer?.cancel();
_action = null;
}
}

View File

@@ -12,6 +12,14 @@ extension FSExt on FileSystemEntity {
} }
} }
Future<void> deleteIgnoreError() async {
try {
await delete();
} catch (e) {
// ignore
}
}
int get size { int get size {
if (this is File) { if (this is File) {
return (this as File).lengthSync(); return (this as File).lengthSync();

69
lib/utils/update.dart Normal file
View File

@@ -0,0 +1,69 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/foundation/app.dart';
import 'package:pixes/network/app_dio.dart';
import 'package:pixes/utils/translation.dart';
import 'package:url_launcher/url_launcher_string.dart';
Future<String> getLatestVersion() async {
var dio = AppDio();
var res = await dio
.get("https://raw.githubusercontent.com/wgh136/pixes/refs/heads/master/pubspec.yaml");
var lines = (res.data as String).split("\n");
for (var line in lines) {
if (line.startsWith("version:")) {
return line.split(":")[1].split('+')[0].trim();
}
}
throw "Failed to get latest version";
}
/// Compare two versions.
/// Return `true` if `a` is greater than `b`.
bool compareVersion(String a, String b) {
var aList = a.split(".").map(int.parse).toList();
var bList = b.split(".").map(int.parse).toList();
for (var i = 0; i < aList.length; i++) {
if (aList[i] > bList[i]) {
return true;
} else if (aList[i] < bList[i]) {
return false;
}
}
return false;
}
Future<void> checkUpdate() async {
if (appdata.account == null) return;
try {
var latestVersion = await getLatestVersion();
if (compareVersion(latestVersion, App.version)) {
showDialog(
context: App.rootNavigatorKey.currentContext!,
builder: (context) => ContentDialog(
title: Text("New version available".tl),
content: Text(
"A new version of Pixes is available. Do you want to update now?"
.tl,
),
actions: [
Button(
child: Text("Cancel".tl),
onPressed: () {
Navigator.of(context).pop();
},
),
FilledButton(
child: Text("Update".tl),
onPressed: () {
Navigator.of(context).pop();
launchUrlString(
"https://github.com/wgh136/pixes/releases/latest");
})
],
));
}
} catch (e) {
// ignore
}
}

View File

@@ -6,26 +6,34 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <dynamic_color/dynamic_color_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <gtk/gtk_plugin.h> #include <gtk/gtk_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <system_theme/system_theme_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin");
flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar = g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar); gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar = g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) system_theme_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin");
system_theme_plugin_register_with_registrar(system_theme_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -3,10 +3,12 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
file_selector_linux
flutter_acrylic
gtk gtk
screen_retriever screen_retriever_linux
sqlite3_flutter_libs sqlite3_flutter_libs
system_theme
url_launcher_linux url_launcher_linux
window_manager window_manager
) )

View File

@@ -55,7 +55,7 @@ static void my_application_activate(GApplication* application) {
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);
gtk_widget_realize(GTK_WIDGET(window)); gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);

View File

@@ -6,19 +6,29 @@ import FlutterMacOS
import Foundation import Foundation
import app_links import app_links
import device_info_plus
import dynamic_color
import file_selector_macos
import flutter_acrylic
import path_provider_foundation import path_provider_foundation
import screen_retriever import screen_retriever_macos
import share_plus
import sqlite3_flutter_libs import sqlite3_flutter_libs
import system_theme
import url_launcher_macos import url_launcher_macos
import webview_flutter_wkwebview
import window_manager import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterAcrylicPlugin.register(with: registry.registrar(forPlugin: "FlutterAcrylicPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
} }

View File

@@ -3,10 +3,14 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <false/>
<key>com.apple.security.cs.allow-jit</key> <key>com.apple.security.cs.allow-jit</key>
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -5,98 +5,130 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: app_links name: app_links
sha256: "1c2b9e9c56d80d17610bcbd111b37187875c5d0ded8654caa1bda14ea753d001" sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.4.0"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
archive: archive:
dependency: "direct main" dependency: "direct main"
description: description:
name: archive name: archive
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.1" version: "4.0.7"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.13.0"
boolean_selector: boolean_selector:
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"
characters: characters:
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"
clock: clock:
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"
collection: collection:
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"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.4+1" version: "0.3.4+2"
crypto: crypto:
dependency: "direct main" dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.6"
device_info_plus: device_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.1.0" version: "11.5.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus_platform_interface name: device_info_plus_platform_interface
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.3"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.4.3+1" version: "5.8.0+1"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
dynamic_color: dynamic_color:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -109,26 +141,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.1"
file_selector: file_selector:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -141,34 +173,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_selector_android name: file_selector_android
sha256: "57265ec9591e8fd8508f613544cde6f7d045731f6b09644057e49a4c9c672b7c" sha256: "6bba3d590ee9462758879741abc132a19133600dd31832f55627442f1ebd7b54"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1+1" version: "0.5.1+14"
file_selector_ios: file_selector_ios:
dependency: transitive dependency: transitive
description: description:
name: file_selector_ios name: file_selector_ios
sha256: "7160121e434910ec23717bde3a0c514ca039e8c97b791ff35d1786da38abcb4a" sha256: "94b98ad950b8d40d96fee8fa88640c2e4bd8afcdd4817993bd04e20310f45420"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.2" version: "0.5.3+1"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:
name: file_selector_linux name: file_selector_linux
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.2+1" version: "0.9.3+2"
file_selector_macos: file_selector_macos:
dependency: transitive dependency: transitive
description: description:
name: file_selector_macos name: file_selector_macos
sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.4" version: "0.9.4+3"
file_selector_platform_interface: file_selector_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -181,34 +213,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_selector_web name: file_selector_web
sha256: "619e431b224711a3869e30dbd7d516f5f5a4f04b265013a50912f39e1abc88c8" sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.4+1" version: "0.9.4+2"
file_selector_windows: file_selector_windows:
dependency: transitive dependency: transitive
description: description:
name: file_selector_windows name: file_selector_windows
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+1" version: "0.9.3+4"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
name: fixnum name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
fluent_ui: fluent_ui:
dependency: "direct main" dependency: "direct main"
description: description:
name: fluent_ui name: fluent_ui
sha256: a8c76cb501303d108cb9bd33e516da7cfd078031ff427d68eab6069bf4492a2c sha256: "8645eabacb46bfc9632fadc6e106756cdc6f2a4efb5a4fed4410bd3131306fe8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.7" version: "4.12.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -218,18 +250,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_acrylic name: flutter_acrylic
sha256: a9a1fdf91ff1fb47858fd82507f57e255a132a5d355056694fdb9fd303633b18 sha256: "646200d98e8dd2bd4ab931d4ba4f6b4cb899475d6401414017ba5d71b0fac42b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "1.0.0+2"
flutter_file_dialog: flutter_file_dialog:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_file_dialog name: flutter_file_dialog
sha256: "5a1507833473b38839056d63c5125750a6d12e904f78131324fa4632504de513" sha256: "9344b8f07be6a1b6f9854b723fb0cf84a8094ba94761af1d213589d3cb087488"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -256,6 +288,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_to_arch:
dependency: "direct dev"
description:
name: flutter_to_arch
sha256: b68b2757a89a517ae2141cbc672acdd1f69721dd686cacad03876b6f436ff040
url: "https://pub.dev"
source: hosted
version: "1.0.1"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -273,42 +313,66 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.4.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.1.2"
image_gallery_saver_plus:
dependency: "direct main"
description:
name: image_gallery_saver_plus
sha256: "199b9e24f8d85e98f11e3d35571ab68ae50626ad40e2bb85c84383f69a6950ad"
url: "https://pub.dev"
source: hosted
version: "4.0.1"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.20.2"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.dev"
source: hosted
version: "1.0.5"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.4" version: "10.0.9"
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: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.9"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
@@ -325,86 +389,78 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
macos_window_utils:
dependency: transitive
description:
name: macos_window_utils
sha256: "230be594d26f6dee92c5a1544f4242d25138a5bfb9f185b27f14de3949ef0be8"
url: "https://pub.dev"
source: hosted
version: "1.5.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:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.11.1"
math_expressions: math_expressions:
dependency: transitive dependency: transitive
description: description:
name: math_expressions name: math_expressions
sha256: db0b72d867491c4e53a1c773e2708d5d6e94bbe06be07080fc9f896766b9cd3d sha256: "218dc65bed4726562bb31c53d8daa3cc824664b26fb72d77bc592757edf74ba0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.7.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.16.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
name: mime name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "2.0.0"
path: path:
dependency: transitive dependency: transitive
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:
name: path_provider name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.5"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.2.17"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@@ -425,16 +481,24 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
photo_view: photo_view:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "97de36fa8c500c18037f675c122785b193559e09" resolved-ref: a1255d1b5945aad4b7323303ec2ecdf0c90ffc4c
url: "https://github.com/wgh136/photo_view" url: "https://github.com/wgh136/photo_view"
source: git source: git
version: "0.14.0" version: "0.14.0"
@@ -442,10 +506,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "3.1.6"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -454,6 +518,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
posix:
dependency: transitive
description:
name: posix
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
recase: recase:
dependency: transitive dependency: transitive
description: description:
@@ -466,10 +538,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: screen_retriever name: screen_retriever
sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.9" version: "0.2.0"
screen_retriever_linux:
dependency: transitive
description:
name: screen_retriever_linux
sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18
url: "https://pub.dev"
source: hosted
version: "0.2.0"
screen_retriever_macos:
dependency: transitive
description:
name: screen_retriever_macos
sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
screen_retriever_platform_interface:
dependency: transitive
description:
name: screen_retriever_platform_interface
sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0
url: "https://pub.dev"
source: hosted
version: "0.2.0"
screen_retriever_windows:
dependency: transitive
description:
name: screen_retriever_windows
sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
scroll_pos: scroll_pos:
dependency: transitive dependency: transitive
description: description:
@@ -482,31 +586,31 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "10.1.4"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "5.0.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.0"
source_span: source_span:
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:
@@ -519,106 +623,106 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqlite3 name: sqlite3
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295 sha256: c0503c69b44d5714e6abbf4c1f51a3c3cc42b75ce785f44404765e4635481d38
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.7.6"
sqlite3_flutter_libs: sqlite3_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: fb2a106a2ea6042fe57de2c47074cc31539a941819c91e105b864744605da3f5 sha256: e07232b998755fe795655c56d1f5426e0190c9c435e1752d39e7b1cd33699c71
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.21" version: "0.5.34"
stack_trace: stack_trace:
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"
string_scanner: string_scanner:
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"
term_glyph: term_glyph:
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: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.0" version: "0.7.4"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.4.0"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.6" version: "6.3.1"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.1" version: "6.3.16"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: "direct main"
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "6.3.3"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.2.1"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.0" version: "3.2.2"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -631,26 +735,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.4.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.4"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
name: uuid name: uuid
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.5.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -663,82 +767,90 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.1" version: "15.0.0"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "1.1.1"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: webview_flutter name: webview_flutter
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "4.13.0"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91 sha256: f6e6afef6e234801da77170f7a1847ded8450778caf2fe13979d140484be3678
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.16.1" version: "4.7.0"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_platform_interface name: webview_flutter_platform_interface
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d sha256: f0dc2dc3a2b1e3a6abdd6801b9355ebfeb3b8f6cde6b9dc7c9235909c4a1f147
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.0" version: "2.13.1"
webview_flutter_wkwebview: webview_flutter_wkwebview:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
sha256: "7affdf9d680c015b11587181171d3cad8093e449db1f7d9f0f08f4f33d24f9a0" sha256: a3d461fe3467014e05f3ac4962e5fdde2a4bf44c561cb53e9ae5c586600fdbc3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.13.1" version: "3.22.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.0" version: "5.14.0"
win32_registry: win32_registry:
dependency: "direct main" dependency: "direct main"
description: description:
name: win32_registry name: win32_registry
sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "2.1.0"
window_manager: window_manager:
dependency: "direct main" dependency: "direct main"
description: description:
name: window_manager name: window_manager
sha256: b3c895bdf936c77b83c5254bec2e6b3f066710c1f89c38b20b8acc382b525494 sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.8" version: "0.4.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks: sdks:
dart: ">=3.3.4 <4.0.0" dart: ">=3.8.0 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.32.5"

View File

@@ -16,10 +16,11 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# 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
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.6+106 version: 1.2.0+120
environment: environment:
sdk: '>=3.3.4 <4.0.0' sdk: '>=3.3.4 <4.0.0'
flutter: 3.32.5
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@@ -34,30 +35,32 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
window_manager: ^0.3.8 window_manager: ^0.4.3
fluent_ui: ^4.8.7 fluent_ui: ^4.12.0
dynamic_color: ^1.7.0 dynamic_color: ^1.7.0
dio: ^5.4.3 dio: ^5.8.0
crypto: crypto: ^3.0.6
intl: intl:
path_provider: path_provider: ^2.1.5
url_launcher: ^6.1.8 url_launcher: ^6.3.1
app_links: ^6.0.1 url_launcher_ios: ^6.3.2
win32_registry: ^1.1.3 app_links: ^6.4.0
win32_registry: ^2.1.0
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
sqlite3: ^2.4.3 sqlite3: ^2.7.6
sqlite3_flutter_libs: any sqlite3_flutter_libs: any
photo_view: photo_view:
git: git:
url: https://github.com/wgh136/photo_view url: https://github.com/wgh136/photo_view
ref: main ref: main
share_plus: ^9.0.0 share_plus: ^10.1.3
file_selector: ^1.0.1 file_selector: ^1.0.1
flutter_file_dialog: 3.0.1 flutter_file_dialog: ^3.0.2
archive: ^3.5.1 archive: ^4.0.7
webview_flutter: ^4.7.0 webview_flutter: ^4.13.0
flutter_acrylic: 1.0.0+2 flutter_acrylic: 1.0.0+2
device_info_plus: ^10.1.0 device_info_plus: ^11.5.0
image_gallery_saver_plus: ^4.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@@ -68,10 +71,20 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^3.0.0 flutter_lints: ^3.0.0
flutter_to_arch: ^1.0.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
flutter_to_arch:
name: Pixes
icon: debian/gui/pixes.png
categories: Utility
keywords: Flutter;pixiv;images;
url: https://github.com/wgh136/pixes
depends:
- gtk3
# The following section is specific to Flutter packages. # The following section is specific to Flutter packages.
flutter: flutter:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 702 KiB

After

Width:  |  Height:  |  Size: 520 KiB

BIN
screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

BIN
screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

View File

@@ -2,11 +2,11 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "pixes" #define MyAppName "pixes"
#define MyAppVersion "1.0.5" #define MyAppVersion "{{version}}"
#define MyAppPublisher "Nyne" #define MyAppPublisher "Nyne"
#define MyAppURL "https://github.com/wgh136/pixes" #define MyAppURL "https://github.com/wgh136/pixes"
#define MyAppExeName "pixes.exe" #define MyAppExeName "pixes.exe"
#define RootPath "C:\Users\wgh19\IdeaProjects\pixes" #define RootPath "{{root_path}}"
[Setup] [Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
@@ -24,8 +24,8 @@ DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.) ; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest ;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog PrivilegesRequiredOverridesAllowed=dialog
OutputDir=C:\Users\wgh19\IdeaProjects\pixes\build\windows OutputDir={#RootPath}\build\windows
OutputBaseFilename=pixes-windows-installer OutputBaseFilename=pixes-{#MyAppVersion}-windows-installer
SetupIconFile={#RootPath}\windows\runner\resources\app_icon.ico SetupIconFile={#RootPath}\windows\runner\resources\app_icon.ico
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
@@ -35,7 +35,7 @@ ArchitecturesAllowed=x64 arm64
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" Name: "chinesesimplified"; MessagesFile: "{#RootPath}\windows\ChineseSimplified.isl"
[Tasks] [Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
@@ -46,7 +46,7 @@ Source: "{#RootPath}\build\windows\x64\runner\Release\app_links_plugin.dll"; Des
Source: "{#RootPath}\build\windows\x64\runner\Release\dynamic_color_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\dynamic_color_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#RootPath}\build\windows\x64\runner\Release\file_selector_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\file_selector_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#RootPath}\build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#RootPath}\build\windows\x64\runner\Release\screen_retriever_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\screen_retriever_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#RootPath}\build\windows\x64\runner\Release\share_plus_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\share_plus_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#RootPath}\build\windows\x64\runner\Release\sqlite3.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\sqlite3.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#RootPath}\build\windows\x64\runner\Release\sqlite3_flutter_libs_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RootPath}\build\windows\x64\runner\Release\sqlite3_flutter_libs_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion

View File

@@ -1,34 +1,18 @@
import subprocess import subprocess
import os import os
import httpx
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
fontUse = '''
fonts:
- family: font
fonts:
- asset: assets/SourceHanSansSC-Regular.otf
'''
file = open('pubspec.yaml', 'r') file = open('pubspec.yaml', 'r')
content = file.read() content = file.read()
file.close() file.close()
file = open('pubspec.yaml', 'a')
file.write(fontUse)
file.close()
subprocess.run(["flutter", "build", "windows"], shell=True) subprocess.run(["flutter", "build", "windows"], shell=True)
file = open('pubspec.yaml', 'w') version = str.split(str.split(content, 'version: ')[1], '+')[0]
file.write(content)
if os.path.exists("build/app-windows.zip"): subprocess.run(["tar", "-a", "-c", "-f", f"build/windows/pixes-{version}-windows.zip", "-C", "build/windows/x64/runner/Release", "*"]
os.remove("build/app-windows.zip")
subprocess.run(["tar", "-a", "-c", "-f", "build/windows/x64/app-windows.zip", "-C", "build/windows/x64/runner/Release", "."]
, shell=True) , shell=True)
version = str.split(str.split(content, 'version: ')[1], '+')[0]
issContent = "" issContent = ""
file = open('windows/build.iss', 'r') file = open('windows/build.iss', 'r')
issContent = file.read() issContent = file.read()
@@ -40,7 +24,14 @@ file = open('windows/build.iss', 'w')
file.write(newContent) file.write(newContent)
file.close() file.close()
if not os.path.exists("windows/ChineseSimplified.isl"):
# download ChineseSimplified.isl
url = "https://raw.githubusercontent.com/kira-96/Inno-Setup-Chinese-Simplified-Translation/refs/heads/main/ChineseSimplified.isl"
response = httpx.get(url)
with open('windows/ChineseSimplified.isl', 'wb') as file:
file.write(response.content)
subprocess.run(["iscc", "windows/build.iss"], shell=True) subprocess.run(["iscc", "windows/build.iss"], shell=True)
with open('windows/build.iss', 'w') as file: with open('windows/build.iss', 'w') as file:
file.write(issContent) file.write(issContent)

View File

@@ -7,21 +7,30 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h> #include <app_links/app_links_plugin_c_api.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <system_theme/system_theme_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar( AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi")); registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
ScreenRetrieverPluginRegisterWithRegistrar( DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterAcrylicPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar( Sqlite3FlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
SystemThemePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SystemThemePlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar( WindowManagerPluginRegisterWithRegistrar(

View File

@@ -4,9 +4,12 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links app_links
screen_retriever dynamic_color
file_selector_windows
flutter_acrylic
screen_retriever_windows
share_plus
sqlite3_flutter_libs sqlite3_flutter_libs
system_theme
url_launcher_windows url_launcher_windows
window_manager window_manager
) )