mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
Compare commits
1 Commits
ce0d10aeb2
...
v1.4.5-dev
Author | SHA1 | Date | |
---|---|---|---|
179f7643bb |
4
.github/workflows/fastlane.yml
vendored
4
.github/workflows/fastlane.yml
vendored
@@ -4,12 +4,8 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
paths:
|
|
||||||
- 'fastlane/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
paths:
|
|
||||||
- 'fastlane/**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
go:
|
go:
|
||||||
|
86
.github/workflows/main.yml
vendored
86
.github/workflows/main.yml
vendored
@@ -149,6 +149,45 @@ jobs:
|
|||||||
sudo rm -rf build/linux/arch/pkg
|
sudo rm -rf build/linux/arch/pkg
|
||||||
sudo rm -rf build/linux/arch/src
|
sudo rm -rf build/linux/arch/src
|
||||||
sudo rm -rf build/linux/arch/PKGBUILD
|
sudo rm -rf build/linux/arch/PKGBUILD
|
||||||
|
- name: Build AppImage
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y libfuse2
|
||||||
|
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||||
|
chmod +x appimagetool
|
||||||
|
|
||||||
|
mkdir -p Venera.AppDir
|
||||||
|
cp -r build/linux/x64/release/bundle/* Venera.AppDir/
|
||||||
|
|
||||||
|
cat > Venera.AppDir/venera.desktop << EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Venera
|
||||||
|
Exec=venera
|
||||||
|
Icon=venera
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp assets/app_icon.png Venera.AppDir/venera.png
|
||||||
|
|
||||||
|
cat > Venera.AppDir/AppRun << EOF
|
||||||
|
#!/bin/sh
|
||||||
|
HERE=\$(dirname \$(readlink -f "\${0}"))
|
||||||
|
export PATH="\${HERE}"/usr/bin/:"\${HERE}"/usr/sbin/:"\${HERE}"/usr/games/:"\${HERE}"/bin/:"\${HERE}"/sbin/:\${PATH}
|
||||||
|
export LD_LIBRARY_PATH="\${HERE}"/usr/lib/:\${LD_LIBRARY_PATH}
|
||||||
|
export XDG_DATA_DIRS="\${HERE}"/usr/share/:\${XDG_DATA_DIRS}
|
||||||
|
exec "\${HERE}"/venera "\$@"
|
||||||
|
EOF
|
||||||
|
chmod +x Venera.AppDir/AppRun
|
||||||
|
|
||||||
|
APP_VERSION=$(grep "version:" pubspec.yaml | cut -d':' -f2 | tr -d ' ')
|
||||||
|
./appimagetool Venera.AppDir Venera-${APP_VERSION}-x86_64.AppImage
|
||||||
|
|
||||||
|
mkdir -p build/linux/appimage
|
||||||
|
mv Venera-${APP_VERSION}-x86_64.AppImage build/linux/appimage/
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: appimage_build
|
||||||
|
path: build/linux/appimage
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: deb_build
|
name: deb_build
|
||||||
@@ -171,6 +210,45 @@ jobs:
|
|||||||
sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1
|
sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1
|
||||||
dart pub global activate flutter_to_debian
|
dart pub global activate flutter_to_debian
|
||||||
- run: python3 debian/build.py arm64
|
- run: python3 debian/build.py arm64
|
||||||
|
- name: Build AppImage
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y libfuse2
|
||||||
|
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage"
|
||||||
|
chmod +x appimagetool
|
||||||
|
|
||||||
|
mkdir -p Venera.AppDir
|
||||||
|
cp -r build/linux/arm64/release/bundle/* Venera.AppDir/
|
||||||
|
|
||||||
|
cat > Venera.AppDir/venera.desktop << EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Venera
|
||||||
|
Exec=venera
|
||||||
|
Icon=venera
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp assets/app_icon.png Venera.AppDir/venera.png
|
||||||
|
|
||||||
|
cat > Venera.AppDir/AppRun << EOF
|
||||||
|
#!/bin/sh
|
||||||
|
HERE=\$(dirname \$(readlink -f "\${0}"))
|
||||||
|
export PATH="\${HERE}"/usr/bin/:"\${HERE}"/usr/sbin/:"\${HERE}"/usr/games/:"\${HERE}"/bin/:"\${HERE}"/sbin/:\${PATH}
|
||||||
|
export LD_LIBRARY_PATH="\${HERE}"/usr/lib/:\${LD_LIBRARY_PATH}
|
||||||
|
export XDG_DATA_DIRS="\${HERE}"/usr/share/:\${XDG_DATA_DIRS}
|
||||||
|
exec "\${HERE}"/venera "\$@"
|
||||||
|
EOF
|
||||||
|
chmod +x Venera.AppDir/AppRun
|
||||||
|
|
||||||
|
APP_VERSION=$(grep "version:" pubspec.yaml | cut -d':' -f2 | tr -d ' ')
|
||||||
|
./appimagetool Venera.AppDir Venera-${APP_VERSION}-aarch64.AppImage
|
||||||
|
|
||||||
|
mkdir -p build/linux/appimage
|
||||||
|
mv Venera-${APP_VERSION}-aarch64.AppImage build/linux/appimage/
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: appimage_arm64_build
|
||||||
|
path: build/linux/appimage
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: deb_arm64_build
|
name: deb_arm64_build
|
||||||
@@ -209,6 +287,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: deb_arm64_build
|
name: deb_arm64_build
|
||||||
path: outputs
|
path: outputs
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: appimage_build
|
||||||
|
path: outputs
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: appimage_arm64_build
|
||||||
|
path: outputs
|
||||||
- uses: softprops/action-gh-release@v2
|
- uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ github.ref_name }}
|
||||||
|
76
.github/workflows/update_alt_store.yml
vendored
76
.github/workflows/update_alt_store.yml
vendored
@@ -1,76 +0,0 @@
|
|||||||
name: Update AltStore Source
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["Build ALL"]
|
|
||||||
types: [completed]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-source:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install requests
|
|
||||||
|
|
||||||
- name: Record job start time
|
|
||||||
id: job_start_time
|
|
||||||
run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Update AltStore source
|
|
||||||
id: update_source
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
python update_alt_store.py
|
|
||||||
git config --global user.name 'GitHub Action'
|
|
||||||
git config --global user.email 'action@github.com'
|
|
||||||
git add alt_store.json
|
|
||||||
if git diff --staged --quiet; then
|
|
||||||
echo "changes=false" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
git commit -m "Updated source with latest release"
|
|
||||||
git push
|
|
||||||
echo "changes=true" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Calculate job duration
|
|
||||||
id: duration
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
end_time=$(date +%s)
|
|
||||||
duration=$((end_time - ${{ steps.job_start_time.outputs.start_time }}))
|
|
||||||
echo "duration=$duration seconds" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Create job summary
|
|
||||||
run: |
|
|
||||||
if [[ "${{ steps.update_source.outputs.changes }}" == "true" ]]; then
|
|
||||||
echo "## Update Altstore Source Summary 🚀" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "✅ Changes Detected and Applied" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "The alt_store.json file has been updated with the latest release information." >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "## Update Altstore Source Summary 🚀" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "🔍 No Changes Detected" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "The alt_store.json file is up to date. No changes were necessary." >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "🕐 Execution Time" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "This job took ${{ steps.duration.outputs.duration }} to complete." >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "📆 Next Scheduled Run" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "The next scheduled run will be tomorrow at midnight UTC." >> $GITHUB_STEP_SUMMARY
|
|
@@ -3,7 +3,7 @@
|
|||||||
[](https://github.com/venera-app/venera/blob/master/LICENSE)
|
[](https://github.com/venera-app/venera/blob/master/LICENSE)
|
||||||
[](https://github.com/venera-app/venera/releases)
|
[](https://github.com/venera-app/venera/releases)
|
||||||
[](https://github.com/venera-app/venera/stargazers)
|
[](https://github.com/venera-app/venera/stargazers)
|
||||||
[](https://t.me/venera_release)
|
[](https://t.me/+Ws-IpmUutzkxMjhl)
|
||||||
|
|
||||||
A comic reader that support reading local and network comics.
|
A comic reader that support reading local and network comics.
|
||||||
|
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Venera",
|
|
||||||
"identifier": "com.github.wgh136.venera.source",
|
|
||||||
"website": "https://github.com/venera-app/venera",
|
|
||||||
"subtitle": "Venera official AltStore Source.",
|
|
||||||
"description": "This is the official AltStore Source for Venera.\n\n A comic reader that supports reading local and network comics",
|
|
||||||
"tintColor": "#0784FC",
|
|
||||||
"iconURL": "https://raw.githubusercontent.com/venera-app/venera/master/assets/app_icon.png",
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"beta": false,
|
|
||||||
"name": "Venera",
|
|
||||||
"bundleIdentifier": "com.github.wgh136.venera",
|
|
||||||
"developerName": "wgh136",
|
|
||||||
"subtitle": "A comic reader that supports reading local and network comics",
|
|
||||||
"version": "1.4.5",
|
|
||||||
"versionDate": "2025-06-18",
|
|
||||||
"versionDescription": "1. Fixed an abnormal single image height issue when \"imagesPerPage > 1\". 379 \r\n2. Fixed an invalid page calculation issue when \"showSingleImageOnFirstPage\" is enabled. \r\n3. Fixed an issue with incorrect reading history when displaying a single image on the first page. \r\n4. Fixed abnormal history recording when pages are not flipped. 392 \r\n5. Fixed an issue where the download task would stop after exiting the reader. 387 \r\n6. Fixed a \"RangeError\" when translating tags. 356 \r\n7. Reset the current folder to null on the favorites page if the folder is invalid. 389 \r\n8. Fixed various issues when using a custom download path on Android. 400 \r\n9. Set the initial chapter to the first downloaded chapter if no history exists when starting to read a local comic. 405 \r\n10. Removed the config file repository URL from the app.",
|
|
||||||
"downloadURL": "https://github.com/venera-app/venera/releases/download/v1.4.5/venera-ios-1.4.5%2B145.ipa",
|
|
||||||
"localizedDescription": "A comic reader that supports reading local and network comics",
|
|
||||||
"iconURL": "https://raw.githubusercontent.com/venera-app/venera/master/assets/app_icon.png",
|
|
||||||
"tintColor": "#0784FC",
|
|
||||||
"category": "utilities",
|
|
||||||
"size": 14960268,
|
|
||||||
"appPermissions": {
|
|
||||||
"entitlements": [
|
|
||||||
"application-identifier",
|
|
||||||
"com.apple.security.application-groups",
|
|
||||||
"get-task-allow",
|
|
||||||
"keychain-access-groups",
|
|
||||||
"com.apple.developer.kernel.extended-virtual-addressing",
|
|
||||||
"com.apple.developer.kernel.increased-memory-limit",
|
|
||||||
"com.apple.developer.healthkit.background-delivery"
|
|
||||||
],
|
|
||||||
"privacy": {
|
|
||||||
"NSFaceIDUsageDescription": "Face ID or Touch ID is used to protect your privacy when opening the app, ensuring secure access to your reading content.",
|
|
||||||
"NSPhotoLibraryAddUsageDescription": "Used to save comic images you've favorited or downloaded to your photo library for easy access and sharing.",
|
|
||||||
"NSPhotoLibraryUsageDescription": "Used to select images from your photo library when needed, and to save comic images you've collected to your device."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"versions": [
|
|
||||||
{
|
|
||||||
"version": "1.4.5",
|
|
||||||
"date": "2025-06-18",
|
|
||||||
"localizedDescription": "1. Fixed an abnormal single image height issue when \"imagesPerPage > 1\". 379 \r\n2. Fixed an invalid page calculation issue when \"showSingleImageOnFirstPage\" is enabled. \r\n3. Fixed an issue with incorrect reading history when displaying a single image on the first page. \r\n4. Fixed abnormal history recording when pages are not flipped. 392 \r\n5. Fixed an issue where the download task would stop after exiting the reader. 387 \r\n6. Fixed a \"RangeError\" when translating tags. 356 \r\n7. Reset the current folder to null on the favorites page if the folder is invalid. 389 \r\n8. Fixed various issues when using a custom download path on Android. 400 \r\n9. Set the initial chapter to the first downloaded chapter if no history exists when starting to read a local comic. 405 \r\n10. Removed the config file repository URL from the app.",
|
|
||||||
"downloadURL": "https://github.com/venera-app/venera/releases/download/v1.4.5/venera-ios-1.4.5%2B145.ipa",
|
|
||||||
"size": 14960268
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"news": [
|
|
||||||
{
|
|
||||||
"appID": "com.github.wgh136.venera",
|
|
||||||
"caption": "Update of Venera just got released!",
|
|
||||||
"date": "2025-06-18T09:02:01Z",
|
|
||||||
"identifier": "release-v1.4.5",
|
|
||||||
"notify": true,
|
|
||||||
"tintColor": "#0784FC",
|
|
||||||
"title": "v1.4.5 - Venera 18/06/25",
|
|
||||||
"url": "https://github.com/venera-app/venera/releases/tag/v1.4.5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@@ -1322,15 +1322,13 @@ let UI = {
|
|||||||
* Show an input dialog
|
* Show an input dialog
|
||||||
* @param title {string}
|
* @param title {string}
|
||||||
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
|
* @param validator {(string) => string | null | undefined} - A function that validates the input. If the function returns a string, the dialog will show the error message.
|
||||||
* @param image {string?} - Available since 1.4.6. An optional image to show in the dialog. You can use this to show a captcha.
|
|
||||||
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
|
* @returns {Promise<string | null>} - The input value. If the dialog is canceled, return null.
|
||||||
*/
|
*/
|
||||||
showInputDialog: (title, validator, image) => {
|
showInputDialog: (title, validator) => {
|
||||||
return sendMessage({
|
return sendMessage({
|
||||||
method: 'UI',
|
method: 'UI',
|
||||||
function: 'showInputDialog',
|
function: 'showInputDialog',
|
||||||
title: title,
|
title: title,
|
||||||
image: image,
|
|
||||||
validator: validator
|
validator: validator
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
3982
assets/opencc.txt
3982
assets/opencc.txt
File diff suppressed because it is too large
Load Diff
@@ -234,10 +234,8 @@
|
|||||||
"Please add some sources": "请添加一些源",
|
"Please add some sources": "请添加一些源",
|
||||||
"Please check your settings": "请检查您的设置",
|
"Please check your settings": "请检查您的设置",
|
||||||
"No Category Pages": "没有分类页面",
|
"No Category Pages": "没有分类页面",
|
||||||
"Group @group": "第 @group 组",
|
|
||||||
"Chapter @ep": "第 @ep 章",
|
"Chapter @ep": "第 @ep 章",
|
||||||
"Page @page": "第 @page 页",
|
"Page @page": "第 @page 页",
|
||||||
"Remove local favorite and history": "删除本地收藏和历史记录",
|
|
||||||
"Also remove files on disk": "同时删除磁盘上的文件",
|
"Also remove files on disk": "同时删除磁盘上的文件",
|
||||||
"Copy to app local path": "将漫画复制到本地存储目录中",
|
"Copy to app local path": "将漫画复制到本地存储目录中",
|
||||||
"Delete all unavailable local favorite items": "删除所有无效的本地收藏",
|
"Delete all unavailable local favorite items": "删除所有无效的本地收藏",
|
||||||
@@ -390,26 +388,13 @@
|
|||||||
"Suggestions": "建议",
|
"Suggestions": "建议",
|
||||||
"Do not report any issues related to sources to App repo.": "请不要向App仓库报告任何与源相关的问题",
|
"Do not report any issues related to sources to App repo.": "请不要向App仓库报告任何与源相关的问题",
|
||||||
"Show single image on first page": "在首页显示单张图片",
|
"Show single image on first page": "在首页显示单张图片",
|
||||||
"Show system status bar": "显示系统状态栏",
|
|
||||||
"Click to select an image": "点击选择一张图片",
|
"Click to select an image": "点击选择一张图片",
|
||||||
"Repo URL": "仓库地址",
|
"Repo URL": "仓库地址",
|
||||||
"The URL should point to a 'index.json' file": "该URL应指向一个'index.json'文件",
|
"The URL should point to a 'index.json' file": "该URL应指向一个'index.json'文件",
|
||||||
"Double tap to zoom": "双击缩放",
|
"Double tap to zoom": "双击缩放",
|
||||||
"Clear Unfavorited": "清除未收藏",
|
"Clear Unfavorited": "清除未收藏",
|
||||||
"Reverse": "反转",
|
"Reverse": "反转",
|
||||||
"Delete Chapters": "删除章节",
|
"Delete Chapters": "删除章节"
|
||||||
"Open Folder": "打开文件夹",
|
|
||||||
"Path copied to clipboard": "路径已复制到剪贴板",
|
|
||||||
"Reverse default chapter order": "反转默认章节顺序",
|
|
||||||
"Reload Configs": "重新加载配置文件",
|
|
||||||
"Reload": "重载",
|
|
||||||
"Disable Length Limitation": "禁用长度限制",
|
|
||||||
"Only valid for this run": "仅对本次运行有效",
|
|
||||||
"Logs": "日志",
|
|
||||||
"Export logs": "导出日志",
|
|
||||||
"Clear specific reader settings for all comics": "清除所有漫画的特殊阅读设置",
|
|
||||||
"Clear specific reader settings for this comic": "清除该漫画的特殊阅读设置",
|
|
||||||
"Enable comic specific settings": "为每本漫画保存特定设置"
|
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Home": "首頁",
|
"Home": "首頁",
|
||||||
@@ -646,10 +631,8 @@
|
|||||||
"Please add some sources": "請添加一些源",
|
"Please add some sources": "請添加一些源",
|
||||||
"Please check your settings": "請檢查您的設定",
|
"Please check your settings": "請檢查您的設定",
|
||||||
"No Category Pages": "沒有分類頁面",
|
"No Category Pages": "沒有分類頁面",
|
||||||
"Group @group": "第 @group 組",
|
|
||||||
"Chapter @ep": "第 @ep 章",
|
"Chapter @ep": "第 @ep 章",
|
||||||
"Page @page": "第 @page 頁",
|
"Page @page": "第 @page 頁",
|
||||||
"Remove local favorite and history": "刪除本機收藏和歷史記錄",
|
|
||||||
"Also remove files on disk": "同時刪除磁碟上的文件",
|
"Also remove files on disk": "同時刪除磁碟上的文件",
|
||||||
"Copy to app local path": "將漫畫複製到本機儲存目錄中",
|
"Copy to app local path": "將漫畫複製到本機儲存目錄中",
|
||||||
"Delete all unavailable local favorite items": "刪除所有無效的本機收藏",
|
"Delete all unavailable local favorite items": "刪除所有無效的本機收藏",
|
||||||
@@ -802,25 +785,12 @@
|
|||||||
"Suggestions": "建議",
|
"Suggestions": "建議",
|
||||||
"Do not report any issues related to sources to App repo.": "請不要向App倉庫報告任何與源相關的問題",
|
"Do not report any issues related to sources to App repo.": "請不要向App倉庫報告任何與源相關的問題",
|
||||||
"Show single image on first page": "在首頁顯示單張圖片",
|
"Show single image on first page": "在首頁顯示單張圖片",
|
||||||
"Show system status bar": "顯示系統狀態欄",
|
|
||||||
"Click to select an image": "點擊選擇一張圖片",
|
"Click to select an image": "點擊選擇一張圖片",
|
||||||
"Repo URL": "倉庫地址",
|
"Repo URL": "倉庫地址",
|
||||||
"The URL should point to a 'index.json' file": "該URL應指向一個'index.json'文件",
|
"The URL should point to a 'index.json' file": "該URL應指向一個'index.json'文件",
|
||||||
"Double tap to zoom": "雙擊縮放",
|
"Double tap to zoom": "雙擊縮放",
|
||||||
"Clear Unfavorited": "清除未收藏",
|
"Clear Unfavorited": "清除未收藏",
|
||||||
"Reverse": "反轉",
|
"Reverse": "反轉",
|
||||||
"Delete Chapters": "刪除章節",
|
"Delete Chapters": "刪除章節"
|
||||||
"Open Folder": "打開資料夾",
|
|
||||||
"Path copied to clipboard": "路徑已複製到剪貼簿",
|
|
||||||
"Reverse default chapter order": "反轉預設章節順序",
|
|
||||||
"Reload Configs": "重新載入設定檔",
|
|
||||||
"Reload": "重載",
|
|
||||||
"Disable Length Limitation": "禁用長度限制",
|
|
||||||
"Only valid for this run": "僅對本次運行有效",
|
|
||||||
"Logs": "日誌",
|
|
||||||
"Export logs": "匯出日誌",
|
|
||||||
"Clear specific reader settings for all comics": "清除所有漫畫的特殊閱讀設定",
|
|
||||||
"Clear specific reader settings for this comic": "清除該漫畫的特殊閱讀設定",
|
|
||||||
"Enable comic specific settings": "為每本漫畫保存特定設定"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -13,14 +13,6 @@ This document will describe how to write a comic source for Venera.
|
|||||||
|
|
||||||
Venera can display a list of comic sources in the app.
|
Venera can display a list of comic sources in the app.
|
||||||
|
|
||||||
You can use the following repo url:
|
|
||||||
```
|
|
||||||
https://git.nyne.dev/nyne/venera-configs/raw/branch/main/index.json
|
|
||||||
```
|
|
||||||
The repo is maintained by the Venera team.
|
|
||||||
|
|
||||||
> The link is a mirror of the original repo. To contribute your comic source, please visit the [original repo](https://github.com/venera-app/venera-configs)
|
|
||||||
|
|
||||||
You should provide a repository url to let the app load the comic source list.
|
You should provide a repository url to let the app load the comic source list.
|
||||||
The url should point to a JSON file that contains the list of comic sources.
|
The url should point to a JSON file that contains the list of comic sources.
|
||||||
|
|
||||||
@@ -41,6 +33,12 @@ The JSON file should have the following format:
|
|||||||
Only one of `url` and `filename` should be provided.
|
Only one of `url` and `filename` should be provided.
|
||||||
The description field is optional.
|
The description field is optional.
|
||||||
|
|
||||||
|
Currently, you can use the following repo url:
|
||||||
|
```
|
||||||
|
https://cdn.jsdelivr.net/gh/venera-app/venera-configs@main/index.json
|
||||||
|
```
|
||||||
|
The repo is maintained by the Venera team, and you can submit a pull request to add your comic source.
|
||||||
|
|
||||||
## Create a Comic Source
|
## Create a Comic Source
|
||||||
|
|
||||||
### Preparation
|
### Preparation
|
||||||
@@ -365,11 +363,6 @@ This part is used to load comics of a category.
|
|||||||
|
|
||||||
// enable tags suggestions
|
// enable tags suggestions
|
||||||
enableTagsSuggestions: false,
|
enableTagsSuggestions: false,
|
||||||
// [Optional] handle tag suggestion click
|
|
||||||
onTagSuggestionSelected: (namespace, tag) => {
|
|
||||||
// return the text to insert into search box
|
|
||||||
return `${namespace}:${tag}`
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
@@ -22,13 +21,11 @@ import 'package:venera/foundation/image_provider/cached_image.dart';
|
|||||||
import 'package:venera/foundation/image_provider/history_image_provider.dart';
|
import 'package:venera/foundation/image_provider/history_image_provider.dart';
|
||||||
import 'package:venera/foundation/image_provider/local_comic_image.dart';
|
import 'package:venera/foundation/image_provider/local_comic_image.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/log.dart';
|
|
||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/network/cloudflare.dart';
|
import 'package:venera/network/cloudflare.dart';
|
||||||
import 'package:venera/pages/comic_details_page/comic_page.dart';
|
import 'package:venera/pages/comic_details_page/comic_page.dart';
|
||||||
import 'package:venera/pages/favorites/favorites_page.dart';
|
import 'package:venera/pages/favorites/favorites_page.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:venera/utils/io.dart';
|
|
||||||
import 'package:venera/utils/tags_translation.dart';
|
import 'package:venera/utils/tags_translation.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
|
@@ -37,11 +37,9 @@ mixin class JsUiApi {
|
|||||||
case 'showInputDialog':
|
case 'showInputDialog':
|
||||||
var title = message['title'];
|
var title = message['title'];
|
||||||
var validator = message['validator'];
|
var validator = message['validator'];
|
||||||
var image = message['image'];
|
|
||||||
if (title is! String) return;
|
if (title is! String) return;
|
||||||
if (validator != null && validator is! JSInvokable) return;
|
if (validator != null && validator is! JSInvokable) return;
|
||||||
if (image != null && image is! String) return;
|
return _showInputDialog(title, validator);
|
||||||
return _showInputDialog(title, validator, image);
|
|
||||||
case 'showSelectDialog':
|
case 'showSelectDialog':
|
||||||
var title = message['title'];
|
var title = message['title'];
|
||||||
var options = message['options'];
|
var options = message['options'];
|
||||||
@@ -126,13 +124,12 @@ mixin class JsUiApi {
|
|||||||
controller?.close();
|
controller?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _showInputDialog(String title, JSInvokable? validator, String? image) async {
|
Future<String?> _showInputDialog(String title, JSInvokable? validator) async {
|
||||||
String? result;
|
String? result;
|
||||||
var func = validator == null ? null : JSAutoFreeFunction(validator);
|
var func = validator == null ? null : JSAutoFreeFunction(validator);
|
||||||
await showInputDialog(
|
await showInputDialog(
|
||||||
context: App.rootContext,
|
context: App.rootContext,
|
||||||
title: title,
|
title: title,
|
||||||
image: image,
|
|
||||||
onConfirm: (v) {
|
onConfirm: (v) {
|
||||||
if (func != null) {
|
if (func != null) {
|
||||||
var res = func.call([v]);
|
var res = func.call([v]);
|
||||||
|
@@ -41,22 +41,18 @@ class NetworkError extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
cfe == null ? message : "Cloudflare verification required".tl,
|
cfe == null ? message : "Cloudflare verification required".tl,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
TextButton(
|
if (retry != null)
|
||||||
onPressed: () {
|
const SizedBox(
|
||||||
saveFile(
|
height: 12,
|
||||||
data: utf8.encode(Log().toString()),
|
|
||||||
filename: 'log.txt',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text("Export logs".tl),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (retry != null)
|
if (retry != null)
|
||||||
if (cfe != null)
|
if (cfe != null)
|
||||||
FilledButton(
|
FilledButton(
|
||||||
@@ -78,11 +74,15 @@ class NetworkError extends StatelessWidget {
|
|||||||
body = Column(
|
body = Column(
|
||||||
children: [
|
children: [
|
||||||
const Appbar(title: Text("")),
|
const Appbar(title: Text("")),
|
||||||
Expanded(child: body),
|
Expanded(
|
||||||
|
child: body,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Material(child: body);
|
return Material(
|
||||||
|
child: body,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,9 @@ class ListLoadingIndicator extends StatelessWidget {
|
|||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 80,
|
height: 80,
|
||||||
child: Center(child: FiveDotLoadingAnimation()),
|
child: Center(
|
||||||
|
child: FiveDotLoadingAnimation(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,9 +108,10 @@ class SliverListLoadingIndicator extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// SliverToBoxAdapter can not been lazy loaded.
|
// SliverToBoxAdapter can not been lazy loaded.
|
||||||
// Use SliverList to make sure the animation can be lazy loaded.
|
// Use SliverList to make sure the animation can be lazy loaded.
|
||||||
return SliverList.list(
|
return SliverList.list(children: const [
|
||||||
children: const [SizedBox(), ListLoadingIndicator()],
|
SizedBox(),
|
||||||
);
|
ListLoadingIndicator(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +178,10 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildError() {
|
Widget buildError() {
|
||||||
return NetworkError(message: error!, retry: retry);
|
return NetworkError(
|
||||||
|
message: error!,
|
||||||
|
retry: retry,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -317,7 +323,11 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildError(BuildContext context, String error) {
|
Widget buildError(BuildContext context, String error) {
|
||||||
return NetworkError(withAppbar: false, message: error, retry: reset);
|
return NetworkError(
|
||||||
|
withAppbar: false,
|
||||||
|
message: error,
|
||||||
|
retry: reset,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -378,7 +388,7 @@ class _FiveDotLoadingAnimationState extends State<FiveDotLoadingAnimation>
|
|||||||
Colors.green,
|
Colors.green,
|
||||||
Colors.blue,
|
Colors.blue,
|
||||||
Colors.yellow,
|
Colors.yellow,
|
||||||
Colors.purple,
|
Colors.purple
|
||||||
];
|
];
|
||||||
|
|
||||||
static const _padding = 12.0;
|
static const _padding = 12.0;
|
||||||
@@ -395,10 +405,11 @@ class _FiveDotLoadingAnimationState extends State<FiveDotLoadingAnimation>
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: _dotSize * 5 + _padding * 6,
|
width: _dotSize * 5 + _padding * 6,
|
||||||
height: _height,
|
height: _height,
|
||||||
child: Stack(children: List.generate(5, (index) => buildDot(index))),
|
child: Stack(
|
||||||
);
|
children: List.generate(5, (index) => buildDot(index)),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDot(int index) {
|
Widget buildDot(int index) {
|
||||||
@@ -406,8 +417,7 @@ class _FiveDotLoadingAnimationState extends State<FiveDotLoadingAnimation>
|
|||||||
var startValue = index * 0.8;
|
var startValue = index * 0.8;
|
||||||
return Positioned(
|
return Positioned(
|
||||||
left: index * _dotSize + (index + 1) * _padding,
|
left: index * _dotSize + (index + 1) * _padding,
|
||||||
bottom:
|
bottom: (math.sin(math.pi / 2 * (value - startValue).clamp(0, 2))) *
|
||||||
(math.sin(math.pi / 2 * (value - startValue).clamp(0, 2))) *
|
|
||||||
(_height - _dotSize),
|
(_height - _dotSize),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: _dotSize,
|
width: _dotSize,
|
||||||
|
@@ -359,7 +359,6 @@ Future<void> showInputDialog({
|
|||||||
String confirmText = "Confirm",
|
String confirmText = "Confirm",
|
||||||
String cancelText = "Cancel",
|
String cancelText = "Cancel",
|
||||||
RegExp? inputValidator,
|
RegExp? inputValidator,
|
||||||
String? image,
|
|
||||||
}) {
|
}) {
|
||||||
var controller = TextEditingController(text: initialValue);
|
var controller = TextEditingController(text: initialValue);
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
@@ -372,14 +371,7 @@ Future<void> showInputDialog({
|
|||||||
builder: (context, setState) {
|
builder: (context, setState) {
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
title: title,
|
title: title,
|
||||||
content: Column(
|
content: TextField(
|
||||||
children: [
|
|
||||||
if (image != null)
|
|
||||||
SizedBox(
|
|
||||||
height: 108,
|
|
||||||
child: Image.network(image, fit: BoxFit.none),
|
|
||||||
).paddingBottom(8),
|
|
||||||
TextField(
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
@@ -387,8 +379,6 @@ Future<void> showInputDialog({
|
|||||||
errorText: error,
|
errorText: error,
|
||||||
),
|
),
|
||||||
).paddingHorizontal(12),
|
).paddingHorizontal(12),
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
Button.filled(
|
Button.filled(
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
|
@@ -117,25 +117,16 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
_futurePosition ??= currentLocation;
|
_futurePosition ??= currentLocation;
|
||||||
double k = (_futurePosition! - currentLocation).abs() / 1600 + 1;
|
double k = (_futurePosition! - currentLocation).abs() / 1600 + 1;
|
||||||
_futurePosition = _futurePosition! + pointerSignal.scrollDelta.dy * k;
|
_futurePosition = _futurePosition! + pointerSignal.scrollDelta.dy * k;
|
||||||
var beforeOffset = (_futurePosition! - currentLocation).abs();
|
|
||||||
_futurePosition = _futurePosition!.clamp(
|
_futurePosition = _futurePosition!.clamp(
|
||||||
_controller.position.minScrollExtent,
|
_controller.position.minScrollExtent,
|
||||||
_controller.position.maxScrollExtent,
|
_controller.position.maxScrollExtent,
|
||||||
);
|
);
|
||||||
var afterOffset = (_futurePosition! - currentLocation).abs();
|
|
||||||
if (_futurePosition == old) return;
|
if (_futurePosition == old) return;
|
||||||
var target = _futurePosition!;
|
var target = _futurePosition!;
|
||||||
var duration = _fastAnimationDuration;
|
|
||||||
if (afterOffset < beforeOffset) {
|
|
||||||
duration = duration * (afterOffset / beforeOffset);
|
|
||||||
if (duration < Duration(milliseconds: 10)) {
|
|
||||||
duration = Duration(milliseconds: 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_controller
|
_controller
|
||||||
.animateTo(
|
.animateTo(
|
||||||
_futurePosition!,
|
_futurePosition!,
|
||||||
duration: duration,
|
duration: _fastAnimationDuration,
|
||||||
curve: Curves.linear,
|
curve: Curves.linear,
|
||||||
)
|
)
|
||||||
.then((_) {
|
.then((_) {
|
||||||
|
@@ -13,7 +13,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.4.6";
|
final version = "1.4.5";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
|
@@ -26,7 +26,8 @@ class Appdata with Init {
|
|||||||
var data = jsonEncode(toJson());
|
var data = jsonEncode(toJson());
|
||||||
var file = File(FilePath.join(App.dataPath, 'appdata.json'));
|
var file = File(FilePath.join(App.dataPath, 'appdata.json'));
|
||||||
await file.writeAsString(data);
|
await file.writeAsString(data);
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
_isSavingData = false;
|
_isSavingData = false;
|
||||||
}
|
}
|
||||||
if (sync) {
|
if (sync) {
|
||||||
@@ -56,7 +57,10 @@ class Appdata with Init {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {'settings': settings._data, 'searchHistory': searchHistory};
|
return {
|
||||||
|
'settings': settings._data,
|
||||||
|
'searchHistory': searchHistory,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Following fields are related to device-specific data and should not be synced.
|
/// Following fields are related to device-specific data and should not be synced.
|
||||||
@@ -91,7 +95,8 @@ class Appdata with Init {
|
|||||||
try {
|
try {
|
||||||
var file = File(FilePath.join(App.dataPath, 'implicitData.json'));
|
var file = File(FilePath.join(App.dataPath, 'implicitData.json'));
|
||||||
await file.writeAsString(jsonEncode(implicitData));
|
await file.writeAsString(jsonEncode(implicitData));
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
_isSavingData = false;
|
_isSavingData = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +104,10 @@ class Appdata with Init {
|
|||||||
@override
|
@override
|
||||||
Future<void> doInit() async {
|
Future<void> doInit() async {
|
||||||
var dataPath = (await getApplicationSupportDirectory()).path;
|
var dataPath = (await getApplicationSupportDirectory()).path;
|
||||||
var file = File(FilePath.join(dataPath, 'appdata.json'));
|
var file = File(FilePath.join(
|
||||||
|
dataPath,
|
||||||
|
'appdata.json',
|
||||||
|
));
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -111,7 +119,8 @@ class Appdata with Init {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
searchHistory = List.from(json['searchHistory']);
|
searchHistory = List.from(json['searchHistory']);
|
||||||
} catch (e) {
|
}
|
||||||
|
catch(e) {
|
||||||
Log.error("Appdata", "Failed to load appdata", e);
|
Log.error("Appdata", "Failed to load appdata", e);
|
||||||
Log.info("Appdata", "Resetting appdata");
|
Log.info("Appdata", "Resetting appdata");
|
||||||
file.deleteIgnoreError();
|
file.deleteIgnoreError();
|
||||||
@@ -121,7 +130,8 @@ class Appdata with Init {
|
|||||||
if (await implicitDataFile.exists()) {
|
if (await implicitDataFile.exists()) {
|
||||||
implicitData = jsonDecode(await implicitDataFile.readAsString());
|
implicitData = jsonDecode(await implicitDataFile.readAsString());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
Log.error("Appdata", "Failed to load implicit data", e);
|
Log.error("Appdata", "Failed to load implicit data", e);
|
||||||
Log.info("Appdata", "Resetting implicit data");
|
Log.info("Appdata", "Resetting implicit data");
|
||||||
var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json'));
|
var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json'));
|
||||||
@@ -152,7 +162,6 @@ class Settings with ChangeNotifier {
|
|||||||
'blockedWords': [],
|
'blockedWords': [],
|
||||||
'defaultSearchTarget': null,
|
'defaultSearchTarget': null,
|
||||||
'autoPageTurningInterval': 5, // in seconds
|
'autoPageTurningInterval': 5, // in seconds
|
||||||
'enableComicSpecificSettings': false,
|
|
||||||
'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
|
'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
|
||||||
'readerScreenPicNumberForLandscape': 1, // 1 - 5
|
'readerScreenPicNumberForLandscape': 1, // 1 - 5
|
||||||
'readerScreenPicNumberForPortrait': 1, // 1 - 5
|
'readerScreenPicNumberForPortrait': 1, // 1 - 5
|
||||||
@@ -180,7 +189,7 @@ class Settings with ChangeNotifier {
|
|||||||
'customImageProcessing': defaultCustomImageProcessing,
|
'customImageProcessing': defaultCustomImageProcessing,
|
||||||
'sni': true,
|
'sni': true,
|
||||||
'autoAddLanguageFilter': 'none', // none, chinese, english, japanese
|
'autoAddLanguageFilter': 'none', // none, chinese, english, japanese
|
||||||
'comicSourceListUrl': _defaultSourceListUrl,
|
'comicSourceListUrl': '',
|
||||||
'preloadImageCount': 4,
|
'preloadImageCount': 4,
|
||||||
'followUpdatesFolder': null,
|
'followUpdatesFolder': null,
|
||||||
'initialPage': '0',
|
'initialPage': '0',
|
||||||
@@ -188,9 +197,6 @@ class Settings with ChangeNotifier {
|
|||||||
'showPageNumberInReader': true,
|
'showPageNumberInReader': true,
|
||||||
'showSingleImageOnFirstPage': false,
|
'showSingleImageOnFirstPage': false,
|
||||||
'enableDoubleTapToZoom': true,
|
'enableDoubleTapToZoom': true,
|
||||||
'reverseChapterOrder': false,
|
|
||||||
'showSystemStatusBar': false,
|
|
||||||
'comicSpecificSettings': <String, Map<String, dynamic>>{},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
operator [](String key) {
|
operator [](String key) {
|
||||||
@@ -204,60 +210,6 @@ class Settings with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool haveComicSpecificSettings(String comicId, String sourceKey, String key) {
|
|
||||||
return _data['comicSpecificSettings']?["$comicId@$sourceKey"]?.containsKey(
|
|
||||||
key,
|
|
||||||
) ??
|
|
||||||
false;
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic getReaderSetting(String comicId, String sourceKey, String key) {
|
|
||||||
if (key == 'enableComicSpecificSettings') {
|
|
||||||
return _data['enableComicSpecificSettings'];
|
|
||||||
}
|
|
||||||
if (_data['enableComicSpecificSettings'] == false) {
|
|
||||||
return _data[key];
|
|
||||||
}
|
|
||||||
return _data['comicSpecificSettings']["$comicId@$sourceKey"]?[key] ??
|
|
||||||
_data[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
void setReaderSetting(
|
|
||||||
String comicId,
|
|
||||||
String sourceKey,
|
|
||||||
String key,
|
|
||||||
dynamic value,
|
|
||||||
) {
|
|
||||||
if (key == 'enableComicSpecificSettings') {
|
|
||||||
_data['enableComicSpecificSettings'] = value;
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_data['enableComicSpecificSettings'] == false) {
|
|
||||||
_data[key] = value;
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
(_data['comicSpecificSettings'] as Map<String, dynamic>).putIfAbsent(
|
|
||||||
"$comicId@$sourceKey",
|
|
||||||
() => <String, dynamic>{},
|
|
||||||
)[key] = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetComicReaderSettings(String comicId, String sourceKey) {
|
|
||||||
final allComicSettings = _data['comicSpecificSettings'] as Map;
|
|
||||||
if (allComicSettings.containsKey("$comicId@$sourceKey")) {
|
|
||||||
allComicSettings.remove("$comicId@$sourceKey");
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetAllComicReaderSettings() {
|
|
||||||
_data['comicSpecificSettings'] = <String, Map<String, dynamic>>{};
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return _data.toString();
|
return _data.toString();
|
||||||
@@ -281,6 +233,3 @@ function processImage(image, cid, eid, page, sourceKey) {
|
|||||||
return futureImage;
|
return futureImage;
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
const _defaultSourceListUrl =
|
|
||||||
"https://git.nyne.dev/nyne/venera-configs/raw/branch/main/index.json";
|
|
||||||
|
@@ -184,9 +184,6 @@ class ComicSource {
|
|||||||
|
|
||||||
final HandleClickTagEvent? handleClickTagEvent;
|
final HandleClickTagEvent? handleClickTagEvent;
|
||||||
|
|
||||||
/// Callback when a tag suggestion is selected in search.
|
|
||||||
final TagSuggestionSelectFunc? onTagSuggestionSelected;
|
|
||||||
|
|
||||||
final LinkHandler? linkHandler;
|
final LinkHandler? linkHandler;
|
||||||
|
|
||||||
final bool enableTagsSuggestions;
|
final bool enableTagsSuggestions;
|
||||||
@@ -262,7 +259,6 @@ class ComicSource {
|
|||||||
this.idMatcher,
|
this.idMatcher,
|
||||||
this.translations,
|
this.translations,
|
||||||
this.handleClickTagEvent,
|
this.handleClickTagEvent,
|
||||||
this.onTagSuggestionSelected,
|
|
||||||
this.linkHandler,
|
this.linkHandler,
|
||||||
this.enableTagsSuggestions,
|
this.enableTagsSuggestions,
|
||||||
this.enableTagsTranslate,
|
this.enableTagsTranslate,
|
||||||
|
@@ -148,7 +148,6 @@ class ComicSourceParser {
|
|||||||
_parseIdMatch(),
|
_parseIdMatch(),
|
||||||
_parseTranslation(),
|
_parseTranslation(),
|
||||||
_parseClickTagEvent(),
|
_parseClickTagEvent(),
|
||||||
_parseTagSuggestionSelectFunc(),
|
|
||||||
_parseLinkHandler(),
|
_parseLinkHandler(),
|
||||||
_getValue("search.enableTagsSuggestions") ?? false,
|
_getValue("search.enableTagsSuggestions") ?? false,
|
||||||
_getValue("comic.enableTagsTranslate") ?? false,
|
_getValue("comic.enableTagsTranslate") ?? false,
|
||||||
@@ -1058,19 +1057,6 @@ class ComicSourceParser {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TagSuggestionSelectFunc? _parseTagSuggestionSelectFunc() {
|
|
||||||
if (!_checkExists("search.onTagSuggestionSelected")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (namespace, tag) {
|
|
||||||
var res = JsEngine().runCode("""
|
|
||||||
ComicSource.sources.$_key.search.onTagSuggestionSelected(
|
|
||||||
${jsonEncode(namespace)}, ${jsonEncode(tag)})
|
|
||||||
""");
|
|
||||||
return res is String ? res : "$namespace:$tag";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkHandler? _parseLinkHandler() {
|
LinkHandler? _parseLinkHandler() {
|
||||||
if (!_checkExists("comic.link")) {
|
if (!_checkExists("comic.link")) {
|
||||||
return null;
|
return null;
|
||||||
|
@@ -44,10 +44,5 @@ typedef VoteCommentFunc = Future<Res<int?>> Function(
|
|||||||
typedef HandleClickTagEvent = PageJumpTarget? Function(
|
typedef HandleClickTagEvent = PageJumpTarget? Function(
|
||||||
String namespace, String tag);
|
String namespace, String tag);
|
||||||
|
|
||||||
/// Handle tag suggestion selection event. Should return the text to insert
|
|
||||||
/// into the search field.
|
|
||||||
typedef TagSuggestionSelectFunc = String Function(
|
|
||||||
String namespace, String tag);
|
|
||||||
|
|
||||||
/// [rating] is the rating value, 0-10. 1 represents 0.5 star.
|
/// [rating] is the rating value, 0-10. 1 represents 0.5 star.
|
||||||
typedef StarRatingFunc = Future<Res<bool>> Function(String comicId, int rating);
|
typedef StarRatingFunc = Future<Res<bool>> Function(String comicId, int rating);
|
@@ -133,11 +133,6 @@ class History implements Comic {
|
|||||||
@override
|
@override
|
||||||
String get description {
|
String get description {
|
||||||
var res = "";
|
var res = "";
|
||||||
if (group != null){
|
|
||||||
res += "${"Group @group".tlParams({
|
|
||||||
"group": group!,
|
|
||||||
})} - ";
|
|
||||||
}
|
|
||||||
if (ep >= 1) {
|
if (ep >= 1) {
|
||||||
res += "Chapter @ep".tlParams({
|
res += "Chapter @ep".tlParams({
|
||||||
"ep": ep,
|
"ep": ep,
|
||||||
|
@@ -611,7 +611,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void batchDeleteComics(List<LocalComic> comics, [bool removeFileOnDisk = true, bool removeFavoriteAndHistory = true]) {
|
void batchDeleteComics(List<LocalComic> comics, [bool removeFileOnDisk = true]) {
|
||||||
if (comics.isEmpty) {
|
if (comics.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -640,11 +640,8 @@ class LocalManager with ChangeNotifier {
|
|||||||
_db.execute('COMMIT;');
|
_db.execute('COMMIT;');
|
||||||
|
|
||||||
var comicIDs = comics.map((e) => ComicID(e.comicType, e.id)).toList();
|
var comicIDs = comics.map((e) => ComicID(e.comicType, e.id)).toList();
|
||||||
|
|
||||||
if (removeFavoriteAndHistory) {
|
|
||||||
LocalFavoritesManager().batchDeleteComicsInAllFolders(comicIDs);
|
LocalFavoritesManager().batchDeleteComicsInAllFolders(comicIDs);
|
||||||
HistoryManager().batchDeleteHistories(comicIDs);
|
HistoryManager().batchDeleteHistories(comicIDs);
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
@@ -16,7 +16,6 @@ import 'package:venera/pages/follow_updates_page.dart';
|
|||||||
import 'package:venera/pages/settings/settings_page.dart';
|
import 'package:venera/pages/settings/settings_page.dart';
|
||||||
import 'package:venera/utils/app_links.dart';
|
import 'package:venera/utils/app_links.dart';
|
||||||
import 'package:venera/utils/handle_text_share.dart';
|
import 'package:venera/utils/handle_text_share.dart';
|
||||||
import 'package:venera/utils/opencc.dart';
|
|
||||||
import 'package:venera/utils/tags_translation.dart';
|
import 'package:venera/utils/tags_translation.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
import 'foundation/appdata.dart';
|
import 'foundation/appdata.dart';
|
||||||
@@ -45,7 +44,6 @@ Future<void> init() async {
|
|||||||
TagsTranslation.readData().wait(),
|
TagsTranslation.readData().wait(),
|
||||||
JsEngine().init().wait(),
|
JsEngine().init().wait(),
|
||||||
ComicSourceManager().init().wait(),
|
ComicSourceManager().init().wait(),
|
||||||
OpenCC.init(),
|
|
||||||
];
|
];
|
||||||
await Future.wait(futures);
|
await Future.wait(futures);
|
||||||
CacheManager().setLimitSize(appdata.settings['cacheSize']);
|
CacheManager().setLimitSize(appdata.settings['cacheSize']);
|
||||||
|
@@ -237,27 +237,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
if (widget != null) {
|
if (widget != null) {
|
||||||
/// 如果无法检测到状态栏高度设定指定高度
|
|
||||||
/// https://github.com/flutter/flutter/issues/161086
|
|
||||||
var isPaddingCheckError =
|
|
||||||
MediaQuery.of(context).viewPadding.top <= 0 ||
|
|
||||||
MediaQuery.of(context).viewPadding.top > 50;
|
|
||||||
|
|
||||||
if (isPaddingCheckError) {
|
|
||||||
widget = MediaQuery(
|
|
||||||
data: MediaQuery.of(context).copyWith(
|
|
||||||
viewPadding: const EdgeInsets.only(
|
|
||||||
top: 15,
|
|
||||||
bottom: 15,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 15,
|
|
||||||
bottom: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
widget = OverlayWidget(widget);
|
widget = OverlayWidget(widget);
|
||||||
if (App.isDesktop) {
|
if (App.isDesktop) {
|
||||||
widget = Shortcuts(
|
widget = Shortcuts(
|
||||||
|
@@ -27,7 +27,7 @@ class _NormalComicChapters extends StatefulWidget {
|
|||||||
class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
||||||
late _ComicPageState state;
|
late _ComicPageState state;
|
||||||
|
|
||||||
late bool reverse;
|
bool reverse = false;
|
||||||
|
|
||||||
bool showAll = false;
|
bool showAll = false;
|
||||||
|
|
||||||
@@ -38,7 +38,6 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
reverse = appdata.settings["reverseChapterOrder"] ?? false;
|
|
||||||
history = widget.history;
|
history = widget.history;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +176,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late _ComicPageState state;
|
late _ComicPageState state;
|
||||||
|
|
||||||
late bool reverse;
|
bool reverse = false;
|
||||||
|
|
||||||
bool showAll = false;
|
bool showAll = false;
|
||||||
|
|
||||||
@@ -192,7 +191,6 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
reverse = appdata.settings["reverseChapterOrder"] ?? false;
|
|
||||||
history = widget.history;
|
history = widget.history;
|
||||||
if (history?.group != null) {
|
if (history?.group != null) {
|
||||||
index = history!.group! - 1;
|
index = history!.group! - 1;
|
||||||
|
@@ -410,26 +410,20 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
String text;
|
String text;
|
||||||
if (haveChapter) {
|
if (haveChapter) {
|
||||||
var epName = "E$ep";
|
var epName = "E$ep";
|
||||||
String? groupName;
|
|
||||||
try {
|
try {
|
||||||
if (group == null){
|
epName = group == null
|
||||||
epName = comic.chapters!.titles.elementAt(
|
? comic.chapters!.titles.elementAt(
|
||||||
math.min(ep - 1, comic.chapters!.length - 1),
|
math.min(ep - 1, comic.chapters!.length - 1),
|
||||||
);
|
)
|
||||||
} else {
|
: comic.chapters!
|
||||||
groupName = comic.chapters!.groups.elementAt(group - 1);
|
|
||||||
epName = comic.chapters!
|
|
||||||
.getGroupByIndex(group - 1)
|
.getGroupByIndex(group - 1)
|
||||||
.values
|
.values
|
||||||
.elementAt(ep - 1);
|
.elementAt(ep - 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch(e) {
|
catch(e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
text = groupName == null
|
text = "${"Last Reading".tl}: $epName P$page";
|
||||||
? "${"Last Reading".tl}: $epName P$page"
|
|
||||||
: "${"Last Reading".tl}: $groupName $epName P$page";
|
|
||||||
} else {
|
} else {
|
||||||
text = "${"Last Reading".tl}: P$page";
|
text = "${"Last Reading".tl}: P$page";
|
||||||
}
|
}
|
||||||
|
@@ -191,6 +191,13 @@ class _BodyState extends State<_Body> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildCard(BuildContext context) {
|
Widget buildCard(BuildContext context) {
|
||||||
|
Widget buildButton({
|
||||||
|
required Widget child,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
}) {
|
||||||
|
return Button.normal(onPressed: onPressed, child: child).fixHeight(32);
|
||||||
|
}
|
||||||
|
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@@ -217,13 +224,10 @@ class _BodyState extends State<_Body> {
|
|||||||
},
|
},
|
||||||
onSubmitted: handleAddSource,
|
onSubmitted: handleAddSource,
|
||||||
).paddingHorizontal(16).paddingBottom(8),
|
).paddingHorizontal(16).paddingBottom(8),
|
||||||
Wrap(
|
ListTile(
|
||||||
spacing: 8,
|
title: Text("Comic Source list".tl),
|
||||||
runSpacing: 8,
|
trailing: buildButton(
|
||||||
children: [
|
child: Text("View".tl),
|
||||||
FilledButton.tonalIcon(
|
|
||||||
icon: Icon(Icons.article_outlined),
|
|
||||||
label: Text("Comic Source list".tl),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showPopUpWidget(
|
showPopUpWidget(
|
||||||
App.rootContext,
|
App.rootContext,
|
||||||
@@ -231,19 +235,22 @@ class _BodyState extends State<_Body> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FilledButton.tonalIcon(
|
),
|
||||||
icon: Icon(Icons.file_open_outlined),
|
ListTile(
|
||||||
label: Text("Use a config file".tl),
|
title: Text("Use a config file".tl),
|
||||||
|
trailing: buildButton(
|
||||||
onPressed: _selectFile,
|
onPressed: _selectFile,
|
||||||
|
child: Text("Select".tl),
|
||||||
),
|
),
|
||||||
FilledButton.tonalIcon(
|
|
||||||
icon: Icon(Icons.help_outline),
|
|
||||||
label: Text("Help".tl),
|
|
||||||
onPressed: help,
|
|
||||||
),
|
),
|
||||||
_CheckUpdatesButton(),
|
ListTile(
|
||||||
],
|
title: Text("Help".tl),
|
||||||
).paddingHorizontal(12).paddingVertical(8),
|
trailing: buildButton(onPressed: help, child: Text("Open".tl)),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text("Check updates".tl),
|
||||||
|
trailing: _CheckUpdatesButton(),
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -692,15 +699,11 @@ class _CheckUpdatesButtonState extends State<_CheckUpdatesButton> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FilledButton.tonalIcon(
|
return Button.normal(
|
||||||
icon: isLoading ? SizedBox(
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
|
||||||
) : Icon(Icons.update),
|
|
||||||
label: Text("Check updates".tl),
|
|
||||||
onPressed: check,
|
onPressed: check,
|
||||||
);
|
isLoading: isLoading,
|
||||||
|
child: Text("Check".tl),
|
||||||
|
).fixHeight(32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,6 @@ import 'package:venera/pages/reader/reader.dart';
|
|||||||
import 'package:venera/pages/settings/settings_page.dart';
|
import 'package:venera/pages/settings/settings_page.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:venera/utils/io.dart';
|
import 'package:venera/utils/io.dart';
|
||||||
import 'package:venera/utils/opencc.dart';
|
|
||||||
import 'package:venera/utils/tags_translation.dart';
|
import 'package:venera/utils/tags_translation.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
|
@@ -52,9 +52,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
} else {
|
} else {
|
||||||
searchResults = [];
|
searchResults = [];
|
||||||
for (var comic in comics) {
|
for (var comic in comics) {
|
||||||
if (matchKeyword(keyword, comic) ||
|
if (matchKeyword(keyword, comic)) {
|
||||||
matchKeywordT(keyword, comic) ||
|
|
||||||
matchKeywordS(keyword, comic)) {
|
|
||||||
searchResults.add(comic);
|
searchResults.add(comic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,24 +130,6 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert keyword to traditional Chinese to match comics
|
|
||||||
bool matchKeywordT(String keyword, FavoriteItem comic) {
|
|
||||||
if (!OpenCC.hasChineseSimplified(keyword)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
keyword = OpenCC.simplifiedToTraditional(keyword);
|
|
||||||
return matchKeyword(keyword, comic);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert keyword to simplified Chinese to match comics
|
|
||||||
bool matchKeywordS(String keyword, FavoriteItem comic) {
|
|
||||||
if (!OpenCC.hasChineseTraditional(keyword)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
keyword = OpenCC.traditionalToSimplified(keyword);
|
|
||||||
return matchKeyword(keyword, comic);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
favPage = context.findAncestorStateOfType<_FavoritesPageState>()!;
|
favPage = context.findAncestorStateOfType<_FavoritesPageState>()!;
|
||||||
|
@@ -14,7 +14,6 @@ import 'package:venera/utils/io.dart';
|
|||||||
import 'package:venera/utils/pdf.dart';
|
import 'package:venera/utils/pdf.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
import 'package:zip_flutter/zip_flutter.dart';
|
import 'package:zip_flutter/zip_flutter.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
class LocalComicsPage extends StatefulWidget {
|
class LocalComicsPage extends StatefulWidget {
|
||||||
const LocalComicsPage({super.key});
|
const LocalComicsPage({super.key});
|
||||||
@@ -144,14 +143,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
addFavorite(selectedComics.keys.toList());
|
addFavorite(selectedComics.keys.toList());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (selectedComics.length == 1)
|
|
||||||
MenuEntry(
|
|
||||||
icon: Icons.folder_open,
|
|
||||||
text: "Open Folder".tl,
|
|
||||||
onClick: () {
|
|
||||||
openComicFolder(selectedComics.keys.first);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (selectedComics.length == 1)
|
if (selectedComics.length == 1)
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon: Icons.chrome_reader_mode_outlined,
|
icon: Icons.chrome_reader_mode_outlined,
|
||||||
@@ -322,13 +313,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
},
|
},
|
||||||
menuBuilder: (c) {
|
menuBuilder: (c) {
|
||||||
return [
|
return [
|
||||||
MenuEntry(
|
|
||||||
icon: Icons.folder_open,
|
|
||||||
text: "Open Folder".tl,
|
|
||||||
onClick: () {
|
|
||||||
openComicFolder(c as LocalComic);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
text: "Delete".tl,
|
text: "Delete".tl,
|
||||||
@@ -377,22 +361,10 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
context: App.rootContext,
|
context: App.rootContext,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
bool removeComicFile = true;
|
bool removeComicFile = true;
|
||||||
bool removeFavoriteAndHistory = true;
|
|
||||||
return StatefulBuilder(builder: (context, state) {
|
return StatefulBuilder(builder: (context, state) {
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
title: "Delete".tl,
|
title: "Delete".tl,
|
||||||
content: Column(
|
content: CheckboxListTile(
|
||||||
children: [
|
|
||||||
CheckboxListTile(
|
|
||||||
title: Text("Remove local favorite and history".tl),
|
|
||||||
value: removeFavoriteAndHistory,
|
|
||||||
onChanged: (v) {
|
|
||||||
state(() {
|
|
||||||
removeFavoriteAndHistory = !removeFavoriteAndHistory;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
CheckboxListTile(
|
|
||||||
title: Text("Also remove files on disk".tl),
|
title: Text("Also remove files on disk".tl),
|
||||||
value: removeComicFile,
|
value: removeComicFile,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
@@ -400,8 +372,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
removeComicFile = !removeComicFile;
|
removeComicFile = !removeComicFile;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (comics.length == 1 && comics.first.hasChapters)
|
if (comics.length == 1 && comics.first.hasChapters)
|
||||||
@@ -418,7 +388,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
LocalManager().batchDeleteComics(
|
LocalManager().batchDeleteComics(
|
||||||
comics,
|
comics,
|
||||||
removeComicFile,
|
removeComicFile,
|
||||||
removeFavoriteAndHistory,
|
|
||||||
);
|
);
|
||||||
isDeleted = true;
|
isDeleted = true;
|
||||||
},
|
},
|
||||||
@@ -535,49 +504,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
typedef ExportComicFunc = Future<File> Function(
|
typedef ExportComicFunc = Future<File> Function(
|
||||||
LocalComic comic, String outFilePath);
|
LocalComic comic, String outFilePath);
|
||||||
|
|
||||||
/// Opens the folder containing the comic in the system file explorer
|
|
||||||
Future<void> openComicFolder(LocalComic comic) async {
|
|
||||||
try {
|
|
||||||
final folderPath = comic.baseDir;
|
|
||||||
|
|
||||||
if (App.isWindows) {
|
|
||||||
await Process.run('explorer', [folderPath]);
|
|
||||||
} else if (App.isMacOS) {
|
|
||||||
await Process.run('open', [folderPath]);
|
|
||||||
} else if (App.isLinux) {
|
|
||||||
// Try different file managers commonly found on Linux
|
|
||||||
try {
|
|
||||||
await Process.run('xdg-open', [folderPath]);
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback to other common file managers
|
|
||||||
try {
|
|
||||||
await Process.run('nautilus', [folderPath]);
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
await Process.run('dolphin', [folderPath]);
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
await Process.run('thunar', [folderPath]);
|
|
||||||
} catch (e) {
|
|
||||||
// Last resort: use the URL launcher with file:// protocol
|
|
||||||
await launchUrlString('file://$folderPath');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For mobile platforms, use the URL launcher with file:// protocol
|
|
||||||
await launchUrlString('file://$folderPath');
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Log.error("Open Folder", "Failed to open comic folder: $e", s);
|
|
||||||
// Show error message to user
|
|
||||||
if (App.rootContext.mounted) {
|
|
||||||
App.rootContext.showMessage(message: "Failed to open folder: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showDeleteChaptersPopWindow(BuildContext context, LocalComic comic) {
|
void showDeleteChaptersPopWindow(BuildContext context, LocalComic comic) {
|
||||||
var chapters = <String>[];
|
var chapters = <String>[];
|
||||||
|
|
||||||
|
@@ -131,11 +131,11 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
|
|||||||
}
|
}
|
||||||
if (context.reader.mode.key.startsWith('gallery')) {
|
if (context.reader.mode.key.startsWith('gallery')) {
|
||||||
if (forward) {
|
if (forward) {
|
||||||
if (!context.reader.toNextPage(reader.cid, reader.type) && !context.reader.isLastChapterOfGroup) {
|
if (!context.reader.toNextPage() && !context.reader.isLastChapterOfGroup) {
|
||||||
context.reader.toNextChapter();
|
context.reader.toNextChapter();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!context.reader.toPrevPage(reader.cid, reader.type) && !context.reader.isFirstChapterOfGroup) {
|
if (!context.reader.toPrevPage() && !context.reader.isFirstChapterOfGroup) {
|
||||||
context.reader.toPrevChapter();
|
context.reader.toPrevChapter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,8 +152,7 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
|
|||||||
|
|
||||||
bool _dragInProgress = false;
|
bool _dragInProgress = false;
|
||||||
|
|
||||||
bool get _enableDoubleTapToZoom =>
|
bool get _enableDoubleTapToZoom => appdata.settings["enableDoubleTapToZoom"];
|
||||||
appdata.settings.getReaderSetting(reader.cid, reader.type.sourceKey, 'enableDoubleTapToZoom');
|
|
||||||
|
|
||||||
void onTapUp(TapUpDetails event) {
|
void onTapUp(TapUpDetails event) {
|
||||||
if (_longPressInProgress) {
|
if (_longPressInProgress) {
|
||||||
@@ -191,8 +190,7 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
|
|||||||
} else if (context.readerScaffold.isOpen) {
|
} else if (context.readerScaffold.isOpen) {
|
||||||
context.readerScaffold.openOrClose();
|
context.readerScaffold.openOrClose();
|
||||||
} else {
|
} else {
|
||||||
if (appdata.settings.getReaderSetting(
|
if (appdata.settings['enableTapToTurnPages']) {
|
||||||
reader.cid, reader.type.sourceKey, 'enableTapToTurnPages')) {
|
|
||||||
bool isLeft = false, isRight = false, isTop = false, isBottom = false;
|
bool isLeft = false, isRight = false, isTop = false, isBottom = false;
|
||||||
final width = context.width;
|
final width = context.width;
|
||||||
final height = context.height;
|
final height = context.height;
|
||||||
@@ -209,12 +207,11 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet
|
|||||||
isBottom = true;
|
isBottom = true;
|
||||||
}
|
}
|
||||||
bool isCenter = false;
|
bool isCenter = false;
|
||||||
var prev = () => context.reader.toPrevPage(context.reader.cid, context.reader.type);
|
var prev = context.reader.toPrevPage;
|
||||||
var next = () => context.reader.toNextPage(context.reader.cid, context.reader.type);
|
var next = context.reader.toNextPage;
|
||||||
if (appdata.settings.getReaderSetting(
|
if (appdata.settings['reverseTapToTurnPages']) {
|
||||||
reader.cid, reader.type.sourceKey, 'reverseTapToTurnPages')) {
|
prev = context.reader.toNextPage;
|
||||||
prev = () => context.reader.toNextPage(context.reader.cid, context.reader.type);
|
next = context.reader.toPrevPage;
|
||||||
next = () => context.reader.toPrevPage(context.reader.cid, context.reader.type);
|
|
||||||
}
|
}
|
||||||
switch (context.reader.mode) {
|
switch (context.reader.mode) {
|
||||||
case ReaderMode.galleryLeftToRight:
|
case ReaderMode.galleryLeftToRight:
|
||||||
|
@@ -32,17 +32,10 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
|||||||
inProgress = true;
|
inProgress = true;
|
||||||
if (reader.type == ComicType.local ||
|
if (reader.type == ComicType.local ||
|
||||||
(LocalManager().isDownloaded(
|
(LocalManager().isDownloaded(
|
||||||
reader.cid,
|
reader.cid, reader.type, reader.chapter, reader.widget.chapters))) {
|
||||||
reader.type,
|
|
||||||
reader.chapter,
|
|
||||||
reader.widget.chapters,
|
|
||||||
))) {
|
|
||||||
try {
|
try {
|
||||||
var images = await LocalManager().getImages(
|
var images = await LocalManager()
|
||||||
reader.cid,
|
.getImages(reader.cid, reader.type, reader.chapter);
|
||||||
reader.type,
|
|
||||||
reader.chapter,
|
|
||||||
);
|
|
||||||
setState(() {
|
setState(() {
|
||||||
reader.images = images;
|
reader.images = images;
|
||||||
reader.isLoading = false;
|
reader.isLoading = false;
|
||||||
@@ -88,14 +81,11 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (reader.isLoading) {
|
if (reader.isLoading) {
|
||||||
load();
|
load();
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
} else if (error != null) {
|
} else if (error != null) {
|
||||||
return GestureDetector(
|
return NetworkError(
|
||||||
onTap: () {
|
|
||||||
context.readerScaffold.openOrClose();
|
|
||||||
},
|
|
||||||
child: SizedBox.expand(
|
|
||||||
child: NetworkError(
|
|
||||||
message: error!,
|
message: error!,
|
||||||
retry: () {
|
retry: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -103,14 +93,11 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
|||||||
error = null;
|
error = null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (reader.mode.isGallery) {
|
if (reader.mode.isGallery) {
|
||||||
return _GalleryMode(
|
return _GalleryMode(
|
||||||
key: Key('${reader.mode.key}_${reader.imagesPerPage}'),
|
key: Key('${reader.mode.key}_${reader.imagesPerPage}'));
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return _ContinuousMode(key: Key(reader.mode.key));
|
return _ContinuousMode(key: Key(reader.mode.key));
|
||||||
}
|
}
|
||||||
@@ -138,15 +125,11 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
/// [totalPages] is the total number of pages in the current chapter.
|
/// [totalPages] is the total number of pages in the current chapter.
|
||||||
/// More than one images can be displayed on one page.
|
/// More than one images can be displayed on one page.
|
||||||
int get totalPages {
|
int get totalPages {
|
||||||
if (!reader.showSingleImageOnFirstPage(reader.cid, reader.type)) {
|
if (!reader.showSingleImageOnFirstPage) {
|
||||||
return (reader.images!.length /
|
return (reader.images!.length / reader.imagesPerPage).ceil();
|
||||||
reader.imagesPerPage(reader.cid, reader.type))
|
|
||||||
.ceil();
|
|
||||||
} else {
|
} else {
|
||||||
return 1 +
|
return 1 +
|
||||||
((reader.images!.length - 1) /
|
((reader.images!.length - 1) / reader.imagesPerPage).ceil();
|
||||||
reader.imagesPerPage(reader.cid, reader.type))
|
|
||||||
.ceil();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,24 +152,19 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
|
|
||||||
/// Get the range of images for the given page. [page] is 1-based.
|
/// Get the range of images for the given page. [page] is 1-based.
|
||||||
(int start, int end) getPageImagesRange(int page) {
|
(int start, int end) getPageImagesRange(int page) {
|
||||||
var imagesPerPage = reader.imagesPerPage(reader.cid, reader.type);
|
if (reader.showSingleImageOnFirstPage) {
|
||||||
if (reader.showSingleImageOnFirstPage(reader.cid, reader.type)) {
|
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
return (0, 1);
|
return (0, 1);
|
||||||
} else {
|
} else {
|
||||||
int startIndex = (page - 2) * imagesPerPage + 1;
|
int startIndex = (page - 2) * reader.imagesPerPage + 1;
|
||||||
int endIndex = math.min(
|
int endIndex = math.min(
|
||||||
startIndex + imagesPerPage,
|
startIndex + reader.imagesPerPage, reader.images!.length);
|
||||||
reader.images!.length,
|
|
||||||
);
|
|
||||||
return (startIndex, endIndex);
|
return (startIndex, endIndex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int startIndex = (page - 1) * imagesPerPage;
|
int startIndex = (page - 1) * reader.imagesPerPage;
|
||||||
int endIndex = math.min(
|
int endIndex = math.min(
|
||||||
startIndex + imagesPerPage,
|
startIndex + reader.imagesPerPage, reader.images!.length);
|
||||||
reader.images!.length,
|
|
||||||
);
|
|
||||||
return (startIndex, endIndex);
|
return (startIndex, endIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,12 +210,16 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
var controller = photoViewControllers[reader.page]!;
|
var controller = photoViewControllers[reader.page]!;
|
||||||
Offset value = event.delta;
|
Offset value = event.delta;
|
||||||
if (isLongPressing) {
|
if (isLongPressing) {
|
||||||
controller.updateMultiple(position: controller.position + value);
|
controller.updateMultiple(
|
||||||
|
position: controller.position + value,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: PhotoViewGallery.builder(
|
child: PhotoViewGallery.builder(
|
||||||
backgroundDecoration: BoxDecoration(color: context.colorScheme.surface),
|
backgroundDecoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.surface,
|
||||||
|
),
|
||||||
reverse: reader.mode == ReaderMode.galleryRightToLeft,
|
reverse: reader.mode == ReaderMode.galleryRightToLeft,
|
||||||
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
|
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
|
||||||
? Axis.vertical
|
? Axis.vertical
|
||||||
@@ -250,17 +232,14 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var (startIndex, endIndex) = getPageImagesRange(index);
|
var (startIndex, endIndex) = getPageImagesRange(index);
|
||||||
List<String> pageImages = reader.images!.sublist(
|
List<String> pageImages =
|
||||||
startIndex,
|
reader.images!.sublist(startIndex, endIndex);
|
||||||
endIndex,
|
|
||||||
);
|
|
||||||
|
|
||||||
cache(index);
|
cache(index);
|
||||||
|
|
||||||
photoViewControllers[index] ??= PhotoViewController();
|
photoViewControllers[index] ??= PhotoViewController();
|
||||||
|
|
||||||
if (reader.imagesPerPage(reader.cid, reader.type) == 1 ||
|
if (reader.imagesPerPage == 1 || pageImages.length == 1) {
|
||||||
pageImages.length == 1) {
|
|
||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
controller: photoViewControllers[index],
|
controller: photoViewControllers[index],
|
||||||
@@ -301,11 +280,11 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
onPageChanged: (i) {
|
onPageChanged: (i) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
if (reader.isFirstChapterOfGroup || !reader.toPrevChapter()) {
|
if (reader.isFirstChapterOfGroup || !reader.toPrevChapter()) {
|
||||||
reader.toPage(reader.cid, reader.type, 1);
|
reader.toPage(1);
|
||||||
}
|
}
|
||||||
} else if (i == totalPages + 1) {
|
} else if (i == totalPages + 1) {
|
||||||
if (reader.isLastChapterOfGroup || !reader.toNextChapter()) {
|
if (reader.isLastChapterOfGroup || !reader.toNextChapter()) {
|
||||||
reader.toPage(reader.cid, reader.type, totalPages);
|
reader.toPage(totalPages);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reader.setPage(i);
|
reader.setPage(i);
|
||||||
@@ -370,16 +349,13 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
onInit: (state) => imageStates.add(state),
|
onInit: (state) => imageStates.add(state),
|
||||||
onDispose: (state) => imageStates.remove(state),
|
onDispose: (state) => imageStates.remove(state),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
imageWidgets = images.map((imageKey) {
|
imageWidgets = images.map((imageKey) {
|
||||||
startIndex++;
|
startIndex++;
|
||||||
ImageProvider imageProvider = _createImageProviderFromKey(
|
ImageProvider imageProvider =
|
||||||
imageKey,
|
_createImageProviderFromKey(imageKey, context, startIndex);
|
||||||
context,
|
|
||||||
startIndex,
|
|
||||||
);
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: ComicImage(
|
child: ComicImage(
|
||||||
image: imageProvider,
|
image: imageProvider,
|
||||||
@@ -440,7 +416,10 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
} else {
|
} else {
|
||||||
zoomPosition = Offset(0, 0);
|
zoomPosition = Offset(0, 0);
|
||||||
}
|
}
|
||||||
photoViewController.animateScale?.call(target, zoomPosition);
|
photoViewController.animateScale?.call(
|
||||||
|
target,
|
||||||
|
zoomPosition,
|
||||||
|
);
|
||||||
isLongPressing = true;
|
isLongPressing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,14 +464,14 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
keyRepeatTimer = null;
|
keyRepeatTimer = null;
|
||||||
}
|
}
|
||||||
if (forward == true) {
|
if (forward == true) {
|
||||||
reader.toPage(reader.cid, reader.type, reader.page + 1);
|
reader.toPage(reader.page+1);
|
||||||
} else if (forward == false) {
|
} else if (forward == false) {
|
||||||
reader.toPage(reader.cid, reader.type, reader.page - 1);
|
reader.toPage(reader.page-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event is KeyRepeatEvent && keyRepeatTimer == null) {
|
if (event is KeyRepeatEvent && keyRepeatTimer == null) {
|
||||||
keyRepeatTimer = Timer.periodic(
|
keyRepeatTimer = Timer.periodic(
|
||||||
reader.enablePageAnimation(reader.cid, reader.type)
|
reader.enablePageAnimation
|
||||||
? const Duration(milliseconds: 200)
|
? const Duration(milliseconds: 200)
|
||||||
: const Duration(milliseconds: 50),
|
: const Duration(milliseconds: 50),
|
||||||
(timer) {
|
(timer) {
|
||||||
@@ -500,9 +479,9 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
timer.cancel();
|
timer.cancel();
|
||||||
return;
|
return;
|
||||||
} else if (forward == true) {
|
} else if (forward == true) {
|
||||||
reader.toPage(reader.cid, reader.type, reader.page + 1);
|
reader.toPage(reader.page+1);
|
||||||
} else if (forward == false) {
|
} else if (forward == false) {
|
||||||
reader.toPage(reader.cid, reader.type, reader.page - 1);
|
reader.toPage(reader.page-1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -526,15 +505,15 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
return await File(imageKey.substring(7)).readAsBytes();
|
return await File(imageKey.substring(7)).readAsBytes();
|
||||||
} else {
|
} else {
|
||||||
return (await CacheManager().findCache(
|
return (await CacheManager().findCache(
|
||||||
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
|
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
||||||
))!.readAsBytes();
|
.readAsBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? getImageKeyByOffset(Offset offset) {
|
String? getImageKeyByOffset(Offset offset) {
|
||||||
String? imageKey;
|
String? imageKey;
|
||||||
if (reader.imagesPerPage(reader.cid, reader.type) == 1) {
|
if (reader.imagesPerPage == 1) {
|
||||||
imageKey = reader.images![reader.page - 1];
|
imageKey = reader.images![reader.page - 1];
|
||||||
} else {
|
} else {
|
||||||
for (var imageState in imageStates) {
|
for (var imageState in imageStates) {
|
||||||
@@ -552,7 +531,7 @@ const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
|
|||||||
PointerDeviceKind.mouse,
|
PointerDeviceKind.mouse,
|
||||||
PointerDeviceKind.stylus,
|
PointerDeviceKind.stylus,
|
||||||
PointerDeviceKind.invertedStylus,
|
PointerDeviceKind.invertedStylus,
|
||||||
PointerDeviceKind.unknown,
|
PointerDeviceKind.unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
const double _kChangeChapterOffset = 160;
|
const double _kChangeChapterOffset = 160;
|
||||||
@@ -687,12 +666,10 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
void onScroll() {
|
void onScroll() {
|
||||||
if (prepareToPrevChapter) {
|
if (prepareToPrevChapter) {
|
||||||
jumpToNextChapter = false;
|
jumpToNextChapter = false;
|
||||||
jumpToPrevChapter =
|
jumpToPrevChapter = scrollController.offset <
|
||||||
scrollController.offset <
|
|
||||||
scrollController.position.minScrollExtent - _kChangeChapterOffset;
|
scrollController.position.minScrollExtent - _kChangeChapterOffset;
|
||||||
} else if (prepareToNextChapter) {
|
} else if (prepareToNextChapter) {
|
||||||
jumpToNextChapter =
|
jumpToNextChapter = scrollController.offset >
|
||||||
scrollController.offset >
|
|
||||||
scrollController.position.maxScrollExtent + _kChangeChapterOffset;
|
scrollController.position.maxScrollExtent + _kChangeChapterOffset;
|
||||||
jumpToPrevChapter = false;
|
jumpToPrevChapter = false;
|
||||||
}
|
}
|
||||||
@@ -766,10 +743,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
scrollBehavior: const MaterialScrollBehavior()
|
||||||
scrollbars: false,
|
.copyWith(scrollbars: false, dragDevices: _kTouchLikeDeviceTypes),
|
||||||
dragDevices: _kTouchLikeDeviceTypes,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
widget = Stack(
|
widget = Stack(
|
||||||
@@ -913,14 +888,20 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return PhotoView.customChild(
|
return PhotoView.customChild(
|
||||||
backgroundDecoration: BoxDecoration(color: context.colorScheme.surface),
|
backgroundDecoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.surface,
|
||||||
|
),
|
||||||
childSize: Size(width, height),
|
childSize: Size(width, height),
|
||||||
minScale: 1.0,
|
minScale: 1.0,
|
||||||
maxScale: 2.5,
|
maxScale: 2.5,
|
||||||
strictScale: true,
|
strictScale: true,
|
||||||
controller: photoViewController,
|
controller: photoViewController,
|
||||||
onScaleUpdate: onScaleUpdate,
|
onScaleUpdate: onScaleUpdate,
|
||||||
child: SizedBox(width: width, height: height, child: widget),
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: widget,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -990,7 +971,10 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
} else {
|
} else {
|
||||||
zoomPosition = Offset(0, 0);
|
zoomPosition = Offset(0, 0);
|
||||||
}
|
}
|
||||||
photoViewController.animateScale?.call(target, zoomPosition);
|
photoViewController.animateScale?.call(
|
||||||
|
target,
|
||||||
|
zoomPosition,
|
||||||
|
);
|
||||||
onScaleUpdate(target);
|
onScaleUpdate(target);
|
||||||
isLongPressing = true;
|
isLongPressing = true;
|
||||||
}
|
}
|
||||||
@@ -1078,8 +1062,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
return await File(imageKey.substring(7)).readAsBytes();
|
return await File(imageKey.substring(7)).readAsBytes();
|
||||||
} else {
|
} else {
|
||||||
return (await CacheManager().findCache(
|
return (await CacheManager().findCache(
|
||||||
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
|
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
||||||
))!.readAsBytes();
|
.readAsBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1123,7 +1107,10 @@ void _precacheImage(int page, BuildContext context) {
|
|||||||
if (page <= 0 || page > context.reader.images!.length) {
|
if (page <= 0 || page > context.reader.images!.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
precacheImage(_createImageProvider(page, context), context);
|
precacheImage(
|
||||||
|
_createImageProvider(page, context),
|
||||||
|
context,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [_preDownloadImage] is used to download the image for the given page.
|
/// [_preDownloadImage] is used to download the image for the given page.
|
||||||
@@ -1144,7 +1131,10 @@ void _preDownloadImage(int page, BuildContext context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SwipeChangeChapterProgress extends StatefulWidget {
|
class _SwipeChangeChapterProgress extends StatefulWidget {
|
||||||
const _SwipeChangeChapterProgress({this.controller, required this.isPrev});
|
const _SwipeChangeChapterProgress({
|
||||||
|
this.controller,
|
||||||
|
required this.isPrev,
|
||||||
|
});
|
||||||
|
|
||||||
final ScrollController? controller;
|
final ScrollController? controller;
|
||||||
|
|
||||||
@@ -1261,12 +1251,7 @@ class _ProgressPainter extends CustomPainter {
|
|||||||
paint.color = color;
|
paint.color = color;
|
||||||
canvas.drawRRect(
|
canvas.drawRRect(
|
||||||
RRect.fromLTRBR(
|
RRect.fromLTRBR(
|
||||||
0,
|
0, 0, size.width * value, size.height, Radius.circular(16)),
|
||||||
0,
|
|
||||||
size.width * value,
|
|
||||||
size.height,
|
|
||||||
Radius.circular(16),
|
|
||||||
),
|
|
||||||
paint,
|
paint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -115,10 +115,10 @@ class _ReaderState extends State<Reader>
|
|||||||
if (images == null) {
|
if (images == null) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!showSingleImageOnFirstPage(cid, type)) {
|
if (!showSingleImageOnFirstPage) {
|
||||||
return (images!.length / imagesPerPage(cid, type)).ceil();
|
return (images!.length / imagesPerPage).ceil();
|
||||||
} else {
|
} else {
|
||||||
return 1 + ((images!.length - 1) / imagesPerPage(cid, type)).ceil();
|
return 1 + ((images!.length - 1) / imagesPerPage).ceil();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,14 +162,11 @@ class _ReaderState extends State<Reader>
|
|||||||
if (widget.initialPage != null) {
|
if (widget.initialPage != null) {
|
||||||
page = widget.initialPage!;
|
page = widget.initialPage!;
|
||||||
}
|
}
|
||||||
// mode = ReaderMode.fromKey(appdata.settings['readerMode']);
|
mode = ReaderMode.fromKey(appdata.settings['readerMode']);
|
||||||
mode = ReaderMode.fromKey(appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerMode'));
|
|
||||||
history = widget.history;
|
history = widget.history;
|
||||||
if (!appdata.settings.getReaderSetting(cid, type.sourceKey, 'showSystemStatusBar')) {
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||||
}
|
if (appdata.settings['enableTurnPageByVolumeKey']) {
|
||||||
if (appdata.settings.getReaderSetting(cid, type.sourceKey, 'enableTurnPageByVolumeKey')) {
|
handleVolumeEvent();
|
||||||
handleVolumeEvent(cid, type);
|
|
||||||
}
|
}
|
||||||
setImageCacheSize();
|
setImageCacheSize();
|
||||||
Future.delayed(const Duration(milliseconds: 200), () {
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
@@ -178,18 +175,10 @@ class _ReaderState extends State<Reader>
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isInitialized = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
if (!_isInitialized) {
|
initImagesPerPage(widget.initialPage ?? 1);
|
||||||
initImagesPerPage(cid, type, widget.initialPage ?? 1);
|
|
||||||
_isInitialized = true;
|
|
||||||
} else {
|
|
||||||
// For orientation changed
|
|
||||||
_checkImagesPerPageChange(cid, type);
|
|
||||||
}
|
|
||||||
initReaderWindow();
|
initReaderWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +219,7 @@ class _ReaderState extends State<Reader>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_checkImagesPerPageChange(cid, type);
|
_checkImagesPerPageChange();
|
||||||
return KeyboardListener(
|
return KeyboardListener(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -275,13 +264,13 @@ class _ReaderState extends State<Reader>
|
|||||||
history!.page = images?.length ?? 1;
|
history!.page = images?.length ?? 1;
|
||||||
} else {
|
} else {
|
||||||
/// Record the first image of the page
|
/// Record the first image of the page
|
||||||
if (!showSingleImageOnFirstPage(cid, type) || imagesPerPage(cid, type) == 1) {
|
if (!showSingleImageOnFirstPage || imagesPerPage == 1) {
|
||||||
history!.page = (page - 1) * imagesPerPage(cid, type) + 1;
|
history!.page = (page - 1) * imagesPerPage + 1;
|
||||||
} else {
|
} else {
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
history!.page = 1;
|
history!.page = 1;
|
||||||
} else {
|
} else {
|
||||||
history!.page = (page - 2) * imagesPerPage(cid, type) + 2;
|
history!.page = (page - 2) * imagesPerPage + 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,8 +343,6 @@ class _ReaderState extends State<Reader>
|
|||||||
abstract mixin class _ImagePerPageHandler {
|
abstract mixin class _ImagePerPageHandler {
|
||||||
late int _lastImagesPerPage;
|
late int _lastImagesPerPage;
|
||||||
|
|
||||||
late bool _lastOrientation;
|
|
||||||
|
|
||||||
bool get isPortrait;
|
bool get isPortrait;
|
||||||
|
|
||||||
int get page;
|
int get page;
|
||||||
@@ -364,77 +351,53 @@ abstract mixin class _ImagePerPageHandler {
|
|||||||
|
|
||||||
ReaderMode get mode;
|
ReaderMode get mode;
|
||||||
|
|
||||||
void initImagesPerPage(String cid, ComicType type, int initialPage) {
|
void initImagesPerPage(int initialPage) {
|
||||||
_lastImagesPerPage = imagesPerPage(cid, type);
|
_lastImagesPerPage = imagesPerPage;
|
||||||
_lastOrientation = isPortrait;
|
if (imagesPerPage != 1) {
|
||||||
if (imagesPerPage(cid, type) != 1) {
|
if (showSingleImageOnFirstPage) {
|
||||||
if (showSingleImageOnFirstPage(cid, type)) {
|
page = ((initialPage - 1) / imagesPerPage).ceil() + 1;
|
||||||
page = ((initialPage - 1) / imagesPerPage(cid, type)).ceil() + 1;
|
|
||||||
} else {
|
} else {
|
||||||
page = (initialPage / imagesPerPage(cid, type)).ceil();
|
page = (initialPage / imagesPerPage).ceil();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool showSingleImageOnFirstPage(String cid, ComicType type) =>
|
bool get showSingleImageOnFirstPage =>
|
||||||
appdata.settings.getReaderSetting(cid, type.sourceKey, 'showSingleImageOnFirstPage');
|
appdata.settings["showSingleImageOnFirstPage"];
|
||||||
|
|
||||||
/// The number of images displayed on one screen
|
/// The number of images displayed on one screen
|
||||||
int imagesPerPage(String cid, ComicType type) {
|
int get imagesPerPage {
|
||||||
if (mode.isContinuous) return 1;
|
if (mode.isContinuous) return 1;
|
||||||
if (isPortrait) {
|
if (isPortrait) {
|
||||||
return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForPortrait') ?? 1;
|
return appdata.settings['readerScreenPicNumberForPortrait'] ?? 1;
|
||||||
} else {
|
} else {
|
||||||
return appdata.settings.getReaderSetting(cid, type.sourceKey, 'readerScreenPicNumberForLandscape') ?? 1;
|
return appdata.settings['readerScreenPicNumberForLandscape'] ?? 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the number of images per page has changed
|
/// Check if the number of images per page has changed
|
||||||
void _checkImagesPerPageChange(String cid, ComicType type) {
|
void _checkImagesPerPageChange() {
|
||||||
int currentImagesPerPage = imagesPerPage(cid, type);
|
int currentImagesPerPage = imagesPerPage;
|
||||||
bool currentOrientation = isPortrait;
|
if (_lastImagesPerPage != currentImagesPerPage) {
|
||||||
|
|
||||||
if (_lastImagesPerPage != currentImagesPerPage || _lastOrientation != currentOrientation) {
|
|
||||||
_adjustPageForImagesPerPageChange(
|
_adjustPageForImagesPerPageChange(
|
||||||
cid, type, _lastImagesPerPage, currentImagesPerPage);
|
_lastImagesPerPage, currentImagesPerPage);
|
||||||
_lastImagesPerPage = currentImagesPerPage;
|
_lastImagesPerPage = currentImagesPerPage;
|
||||||
_lastOrientation = currentOrientation;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the page number when the number of images per page changes
|
/// Adjust the page number when the number of images per page changes
|
||||||
void _adjustPageForImagesPerPageChange(
|
void _adjustPageForImagesPerPageChange(
|
||||||
String cid, ComicType type, int oldImagesPerPage, int newImagesPerPage) {
|
int oldImagesPerPage, int newImagesPerPage) {
|
||||||
int previousImageIndex = 1;
|
int previousImageIndex = (page - 1) * oldImagesPerPage;
|
||||||
if (!showSingleImageOnFirstPage(cid, type) || oldImagesPerPage == 1) {
|
int newPage = (previousImageIndex ~/ newImagesPerPage) + 1;
|
||||||
previousImageIndex = (page - 1) * oldImagesPerPage + 1;
|
page = newPage;
|
||||||
} else {
|
|
||||||
if (page == 1) {
|
|
||||||
previousImageIndex = 1;
|
|
||||||
} else {
|
|
||||||
previousImageIndex = (page - 2) * oldImagesPerPage + 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int newPage;
|
|
||||||
if (newImagesPerPage != 1) {
|
|
||||||
if (showSingleImageOnFirstPage(cid, type)) {
|
|
||||||
newPage = ((previousImageIndex - 1) / newImagesPerPage).ceil() + 1;
|
|
||||||
} else {
|
|
||||||
newPage = (previousImageIndex / newImagesPerPage).ceil();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newPage = previousImageIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
page = newPage>0 ? newPage : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract mixin class _VolumeListener {
|
abstract mixin class _VolumeListener {
|
||||||
bool toNextPage(String cid, ComicType type);
|
bool toNextPage();
|
||||||
|
|
||||||
bool toPrevPage(String cid, ComicType type);
|
bool toPrevPage();
|
||||||
|
|
||||||
bool toNextChapter();
|
bool toNextChapter();
|
||||||
|
|
||||||
@@ -442,19 +405,19 @@ abstract mixin class _VolumeListener {
|
|||||||
|
|
||||||
VolumeListener? volumeListener;
|
VolumeListener? volumeListener;
|
||||||
|
|
||||||
void onDown(String cid, ComicType type) {
|
void onDown() {
|
||||||
if (!toNextPage(cid, type)) {
|
if (!toNextPage()) {
|
||||||
toNextChapter();
|
toNextChapter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUp(String cid, ComicType type) {
|
void onUp() {
|
||||||
if (!toPrevPage(cid, type)) {
|
if (!toPrevPage()) {
|
||||||
toPrevChapter();
|
toPrevChapter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleVolumeEvent(String cid, ComicType type) {
|
void handleVolumeEvent() {
|
||||||
if (!App.isAndroid) {
|
if (!App.isAndroid) {
|
||||||
// Currently only support Android
|
// Currently only support Android
|
||||||
return;
|
return;
|
||||||
@@ -463,8 +426,8 @@ abstract mixin class _VolumeListener {
|
|||||||
volumeListener?.cancel();
|
volumeListener?.cancel();
|
||||||
}
|
}
|
||||||
volumeListener = VolumeListener(
|
volumeListener = VolumeListener(
|
||||||
onDown: () => onDown(cid, type),
|
onDown: onDown,
|
||||||
onUp: () => onUp(cid, type),
|
onUp: onUp,
|
||||||
)..listen();
|
)..listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +459,7 @@ abstract mixin class _ReaderLocation {
|
|||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
bool enablePageAnimation(String cid, ComicType type) => appdata.settings.getReaderSetting(cid, type.sourceKey, 'enablePageAnimation');
|
bool get enablePageAnimation => appdata.settings['enablePageAnimation'];
|
||||||
|
|
||||||
_ImageViewController? _imageViewController;
|
_ImageViewController? _imageViewController;
|
||||||
|
|
||||||
@@ -515,25 +478,25 @@ abstract mixin class _ReaderLocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the page is changed
|
/// Returns true if the page is changed
|
||||||
bool toNextPage(String cid, ComicType type) {
|
bool toNextPage() {
|
||||||
return toPage(cid, type, page + 1);
|
return toPage(page + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the page is changed
|
/// Returns true if the page is changed
|
||||||
bool toPrevPage(String cid, ComicType type) {
|
bool toPrevPage() {
|
||||||
return toPage(cid, type, page - 1);
|
return toPage(page - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int _animationCount = 0;
|
int _animationCount = 0;
|
||||||
|
|
||||||
bool toPage(String cid, ComicType type, int page) {
|
bool toPage(int page) {
|
||||||
if (_validatePage(page)) {
|
if (_validatePage(page)) {
|
||||||
if (page == this.page && page != 1 && page != maxPage) {
|
if (page == this.page && page != 1 && page != maxPage) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.page = page;
|
this.page = page;
|
||||||
update();
|
update();
|
||||||
if (enablePageAnimation(cid, type)) {
|
if (enablePageAnimation) {
|
||||||
_animationCount++;
|
_animationCount++;
|
||||||
_imageViewController!.animateToPage(page).then((_) {
|
_imageViewController!.animateToPage(page).then((_) {
|
||||||
_animationCount--;
|
_animationCount--;
|
||||||
@@ -572,17 +535,17 @@ abstract mixin class _ReaderLocation {
|
|||||||
|
|
||||||
Timer? autoPageTurningTimer;
|
Timer? autoPageTurningTimer;
|
||||||
|
|
||||||
void autoPageTurning(String cid, ComicType type) {
|
void autoPageTurning() {
|
||||||
if (autoPageTurningTimer != null) {
|
if (autoPageTurningTimer != null) {
|
||||||
autoPageTurningTimer!.cancel();
|
autoPageTurningTimer!.cancel();
|
||||||
autoPageTurningTimer = null;
|
autoPageTurningTimer = null;
|
||||||
} else {
|
} else {
|
||||||
int interval = appdata.settings.getReaderSetting(cid, type.sourceKey, 'autoPageTurningInterval');
|
int interval = appdata.settings['autoPageTurningInterval'];
|
||||||
autoPageTurningTimer = Timer.periodic(Duration(seconds: interval), (_) {
|
autoPageTurningTimer = Timer.periodic(Duration(seconds: interval), (_) {
|
||||||
if (page == maxPage) {
|
if (page == maxPage) {
|
||||||
autoPageTurningTimer!.cancel();
|
autoPageTurningTimer!.cancel();
|
||||||
}
|
}
|
||||||
toNextPage(cid, type);
|
toNextPage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -107,11 +107,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
if (!_isOpen) {
|
if (!_isOpen) {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
} else {
|
} else {
|
||||||
if (!appdata.settings['showSystemStatusBar']) {
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||||
} else {
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_isOpen = !_isOpen;
|
_isOpen = !_isOpen;
|
||||||
@@ -128,7 +124,9 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: widget.child),
|
Positioned.fill(
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
if (appdata.settings['showPageNumberInReader'] == true)
|
if (appdata.settings['showPageNumberInReader'] == true)
|
||||||
buildPageInfoText(),
|
buildPageInfoText(),
|
||||||
buildStatusInfo(),
|
buildStatusInfo(),
|
||||||
@@ -166,7 +164,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.colorScheme.surface.toOpacity(0.92),
|
color: context.colorScheme.surface.toOpacity(0.92),
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(color: Colors.grey.toOpacity(0.5), width: 0.5),
|
bottom: BorderSide(
|
||||||
|
color: Colors.grey.toOpacity(0.5),
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -212,8 +213,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
if (context.reader.images![0].contains('file://')) {
|
if (context.reader.images![0].contains('file://')) {
|
||||||
showToast(
|
showToast(
|
||||||
message: "Local comic collection is not supported at present".tl,
|
message: "Local comic collection is not supported at present".tl,
|
||||||
context: context,
|
context: context);
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String id = context.reader.cid;
|
String id = context.reader.cid;
|
||||||
@@ -230,10 +230,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
List<String> tags = context.reader.widget.tags;
|
List<String> tags = context.reader.widget.tags;
|
||||||
String author = context.reader.widget.author;
|
String author = context.reader.widget.author;
|
||||||
|
|
||||||
var epName =
|
var epName = context.reader.widget.chapters?.titles
|
||||||
context.reader.widget.chapters?.titles.elementAtOrNull(
|
.elementAtOrNull(context.reader.chapter - 1) ??
|
||||||
context.reader.chapter - 1,
|
|
||||||
) ??
|
|
||||||
"E${context.reader.chapter}";
|
"E${context.reader.chapter}";
|
||||||
var translatedTags = tags.map((e) => e.translateTagsToCN).toList();
|
var translatedTags = tags.map((e) => e.translateTagsToCN).toList();
|
||||||
|
|
||||||
@@ -246,7 +244,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ImageFavoriteManager().deleteImageFavorite([
|
ImageFavoriteManager().deleteImageFavorite([
|
||||||
ImageFavorite(page, imageKey, null, eid, id, ep, sourceKey, epName),
|
ImageFavorite(page, imageKey, null, eid, id, ep, sourceKey, epName)
|
||||||
]);
|
]);
|
||||||
showToast(
|
showToast(
|
||||||
message: "Uncollected the image".tl,
|
message: "Uncollected the image".tl,
|
||||||
@@ -254,8 +252,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
seconds: 1,
|
seconds: 1,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var imageFavoritesComic =
|
var imageFavoritesComic = ImageFavoriteManager().find(id, sourceKey) ??
|
||||||
ImageFavoriteManager().find(id, sourceKey) ??
|
|
||||||
ImageFavoritesComic(
|
ImageFavoritesComic(
|
||||||
id,
|
id,
|
||||||
[],
|
[],
|
||||||
@@ -269,19 +266,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
subTitle,
|
subTitle,
|
||||||
maxPage,
|
maxPage,
|
||||||
);
|
);
|
||||||
ImageFavorite imageFavorite = ImageFavorite(
|
ImageFavorite imageFavorite =
|
||||||
page,
|
ImageFavorite(page, imageKey, null, eid, id, ep, sourceKey, epName);
|
||||||
imageKey,
|
ImageFavoritesEp? imageFavoritesEp =
|
||||||
null,
|
imageFavoritesComic.imageFavoritesEp.firstWhereOrNull((e) {
|
||||||
eid,
|
|
||||||
id,
|
|
||||||
ep,
|
|
||||||
sourceKey,
|
|
||||||
epName,
|
|
||||||
);
|
|
||||||
ImageFavoritesEp? imageFavoritesEp = imageFavoritesComic
|
|
||||||
.imageFavoritesEp
|
|
||||||
.firstWhereOrNull((e) {
|
|
||||||
return e.ep == ep;
|
return e.ep == ep;
|
||||||
});
|
});
|
||||||
if (imageFavoritesEp == null) {
|
if (imageFavoritesEp == null) {
|
||||||
@@ -293,20 +281,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
);
|
);
|
||||||
// 不是第一页的话, 自动塞一个封面进去
|
// 不是第一页的话, 自动塞一个封面进去
|
||||||
imageFavoritesEp = ImageFavoritesEp(
|
imageFavoritesEp = ImageFavoritesEp(
|
||||||
eid,
|
eid, ep, [copy, imageFavorite], epName, maxPage);
|
||||||
ep,
|
|
||||||
[copy, imageFavorite],
|
|
||||||
epName,
|
|
||||||
maxPage,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
imageFavoritesEp = ImageFavoritesEp(
|
imageFavoritesEp =
|
||||||
eid,
|
ImageFavoritesEp(eid, ep, [imageFavorite], epName, maxPage);
|
||||||
ep,
|
|
||||||
[imageFavorite],
|
|
||||||
epName,
|
|
||||||
maxPage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
imageFavoritesComic.imageFavoritesEp.add(imageFavoritesEp);
|
imageFavoritesComic.imageFavoritesEp.add(imageFavoritesEp);
|
||||||
} else {
|
} else {
|
||||||
@@ -330,10 +308,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
|
|
||||||
ImageFavoriteManager().addOrUpdateOrDelete(imageFavoritesComic);
|
ImageFavoriteManager().addOrUpdateOrDelete(imageFavoritesComic);
|
||||||
showToast(
|
showToast(
|
||||||
message: "Successfully collected".tl,
|
message: "Successfully collected".tl, context: context, seconds: 1);
|
||||||
context: context,
|
|
||||||
seconds: 1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
@@ -352,7 +327,9 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
height: kBottomBarHeight,
|
height: kBottomBarHeight,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 8),
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -360,45 +337,34 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
onPressed: () => !isReversed
|
onPressed: () => !isReversed
|
||||||
? context.reader.chapter > 1
|
? context.reader.chapter > 1
|
||||||
? context.reader.toPrevChapter()
|
? context.reader.toPrevChapter()
|
||||||
: context.reader.toPage(
|
: context.reader.toPage(1)
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
: context.reader.chapter < context.reader.maxChapter
|
: context.reader.chapter < context.reader.maxChapter
|
||||||
? context.reader.toNextChapter()
|
? context.reader.toNextChapter()
|
||||||
: context.reader.toPage(
|
: context.reader.toPage(context.reader.maxPage),
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type,
|
|
||||||
context.reader.maxPage,
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.first_page),
|
icon: const Icon(Icons.first_page),
|
||||||
),
|
),
|
||||||
Expanded(child: buildSlider()),
|
Expanded(
|
||||||
|
child: buildSlider(),
|
||||||
|
),
|
||||||
IconButton.filledTonal(
|
IconButton.filledTonal(
|
||||||
onPressed: () => !isReversed
|
onPressed: () => !isReversed
|
||||||
? context.reader.chapter < context.reader.maxChapter
|
? context.reader.chapter < context.reader.maxChapter
|
||||||
? context.reader.toNextChapter()
|
? context.reader.toNextChapter()
|
||||||
: context.reader.toPage(
|
: context.reader.toPage(context.reader.maxPage)
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type,
|
|
||||||
context.reader.maxPage,
|
|
||||||
)
|
|
||||||
: context.reader.chapter > 1
|
: context.reader.chapter > 1
|
||||||
? context.reader.toPrevChapter()
|
? context.reader.toPrevChapter()
|
||||||
: context.reader.toPage(
|
: context.reader.toPage(1),
|
||||||
context.reader.cid,
|
icon: const Icon(Icons.last_page)),
|
||||||
context.reader.type,
|
const SizedBox(
|
||||||
1,
|
width: 8,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.last_page),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 16),
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 24,
|
height: 24,
|
||||||
padding: const EdgeInsets.fromLTRB(6, 2, 6, 0),
|
padding: const EdgeInsets.fromLTRB(6, 2, 6, 0),
|
||||||
@@ -406,15 +372,16 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Center(child: Text(text)),
|
child: Center(
|
||||||
|
child: Text(text),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: "Collect the image".tl,
|
message: "Collect the image".tl,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(
|
icon:
|
||||||
isLiked() ? Icons.favorite : Icons.favorite_border,
|
Icon(isLiked() ? Icons.favorite : Icons.favorite_border),
|
||||||
),
|
|
||||||
onPressed: addImageFavorite,
|
onPressed: addImageFavorite,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -456,15 +423,14 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
});
|
});
|
||||||
SystemChrome.setPreferredOrientations([
|
SystemChrome.setPreferredOrientations([
|
||||||
DeviceOrientation.landscapeLeft,
|
DeviceOrientation.landscapeLeft,
|
||||||
DeviceOrientation.landscapeRight,
|
DeviceOrientation.landscapeRight
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
rotation = null;
|
rotation = null;
|
||||||
});
|
});
|
||||||
SystemChrome.setPreferredOrientations(
|
SystemChrome.setPreferredOrientations(
|
||||||
DeviceOrientation.values,
|
DeviceOrientation.values);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -476,10 +442,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
? const Icon(Icons.timer)
|
? const Icon(Icons.timer)
|
||||||
: const Icon(Icons.timer_sharp),
|
: const Icon(Icons.timer_sharp),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.reader.autoPageTurning(
|
context.reader.autoPageTurning();
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type,
|
|
||||||
);
|
|
||||||
update();
|
update();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -506,9 +469,9 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
onPressed: share,
|
onPressed: share,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4)
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -539,26 +502,19 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
focusNode: sliderFocus,
|
focusNode: sliderFocus,
|
||||||
value: context.reader.page.toDouble(),
|
value: context.reader.page.toDouble(),
|
||||||
min: 1,
|
min: 1,
|
||||||
max: context.reader.maxPage
|
max:
|
||||||
.clamp(context.reader.page, 1 << 16)
|
context.reader.maxPage.clamp(context.reader.page, 1 << 16).toDouble(),
|
||||||
.toDouble(),
|
|
||||||
reversed: isReversed,
|
reversed: isReversed,
|
||||||
divisions: (context.reader.maxPage - 1).clamp(2, 1 << 16),
|
divisions: (context.reader.maxPage - 1).clamp(2, 1 << 16),
|
||||||
onChanged: (i) {
|
onChanged: (i) {
|
||||||
context.reader.toPage(
|
context.reader.toPage(i.toInt());
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type,
|
|
||||||
i.toInt(),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPageInfoText() {
|
Widget buildPageInfoText() {
|
||||||
var epName =
|
var epName = context.reader.widget.chapters?.titles
|
||||||
context.reader.widget.chapters?.titles.elementAtOrNull(
|
.elementAtOrNull(context.reader.chapter - 1) ??
|
||||||
context.reader.chapter - 1,
|
|
||||||
) ??
|
|
||||||
"E${context.reader.chapter}";
|
"E${context.reader.chapter}";
|
||||||
if (epName.length > 8) {
|
if (epName.length > 8) {
|
||||||
epName = "${epName.substring(0, 8)}...";
|
epName = "${epName.substring(0, 8)}...";
|
||||||
@@ -634,35 +590,24 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
}
|
}
|
||||||
var fileType = detectFileType(data);
|
var fileType = detectFileType(data);
|
||||||
var filename = "${context.reader.page}${fileType.ext}";
|
var filename = "${context.reader.page}${fileType.ext}";
|
||||||
Share.shareFile(data: data, filename: filename, mime: fileType.mime);
|
Share.shareFile(
|
||||||
|
data: data,
|
||||||
|
filename: filename,
|
||||||
|
mime: fileType.mime,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openSetting() {
|
void openSetting() {
|
||||||
showSideBar(
|
showSideBar(
|
||||||
context,
|
context,
|
||||||
ReaderSettings(
|
ReaderSettings(
|
||||||
comicId: context.reader.cid,
|
|
||||||
comicSource: context.reader.type.sourceKey,
|
|
||||||
onChanged: (key) {
|
onChanged: (key) {
|
||||||
if (key == "readerMode") {
|
if (key == "readerMode") {
|
||||||
context.reader.mode = ReaderMode.fromKey(
|
context.reader.mode = ReaderMode.fromKey(appdata.settings[key]);
|
||||||
appdata.settings.getReaderSetting(
|
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type.sourceKey,
|
|
||||||
key,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (key == "enableTurnPageByVolumeKey") {
|
if (key == "enableTurnPageByVolumeKey") {
|
||||||
if (appdata.settings.getReaderSetting(
|
if (appdata.settings[key]) {
|
||||||
context.reader.cid,
|
context.reader.handleVolumeEvent();
|
||||||
context.reader.type.sourceKey,
|
|
||||||
key,
|
|
||||||
)) {
|
|
||||||
context.reader.handleVolumeEvent(
|
|
||||||
context.reader.cid,
|
|
||||||
context.reader.type,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
context.reader.stopVolumeEvent();
|
context.reader.stopVolumeEvent();
|
||||||
}
|
}
|
||||||
@@ -767,8 +712,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
return await File(imageKey.substring(7)).readAsBytes();
|
return await File(imageKey.substring(7)).readAsBytes();
|
||||||
} else {
|
} else {
|
||||||
return (await CacheManager().findCache(
|
return (await CacheManager().findCache(
|
||||||
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
|
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
||||||
))!.readAsBytes();
|
.readAsBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,17 +729,14 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
entry = OverlayEntry(
|
entry = OverlayEntry(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: _SelectImageOverlayContent(
|
child: _SelectImageOverlayContent(onTap: (offset) {
|
||||||
onTap: (offset) {
|
|
||||||
completer.complete(offset);
|
completer.complete(offset);
|
||||||
entry!.remove();
|
entry!.remove();
|
||||||
},
|
}, onDispose: () {
|
||||||
onDispose: () {
|
|
||||||
if (!completer.isCompleted) {
|
if (!completer.isCompleted) {
|
||||||
completer.complete(null);
|
completer.complete(null);
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -894,7 +836,9 @@ class _BatteryWidgetState extends State<_BatteryWidget> {
|
|||||||
size: 16,
|
size: 16,
|
||||||
color: batteryColor,
|
color: batteryColor,
|
||||||
// Stroke
|
// Stroke
|
||||||
shadows: List.generate(9, (index) {
|
shadows: List.generate(
|
||||||
|
9,
|
||||||
|
(index) {
|
||||||
if (index == 4) {
|
if (index == 4) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -904,7 +848,8 @@ class _BatteryWidgetState extends State<_BatteryWidget> {
|
|||||||
color: context.colorScheme.onInverseSurface,
|
color: context.colorScheme.onInverseSurface,
|
||||||
offset: Offset(offsetX, offsetY),
|
offset: Offset(offsetX, offsetY),
|
||||||
);
|
);
|
||||||
}).whereType<Shadow>().toList(),
|
},
|
||||||
|
).whereType<Shadow>().toList(),
|
||||||
),
|
),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
@@ -991,12 +936,10 @@ class _SelectImageOverlayContent extends StatefulWidget {
|
|||||||
final void Function() onDispose;
|
final void Function() onDispose;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_SelectImageOverlayContent> createState() =>
|
State<_SelectImageOverlayContent> createState() => _SelectImageOverlayContentState();
|
||||||
_SelectImageOverlayContentState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SelectImageOverlayContentState
|
class _SelectImageOverlayContentState extends State<_SelectImageOverlayContent> {
|
||||||
extends State<_SelectImageOverlayContent> {
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
widget.onDispose();
|
widget.onDispose();
|
||||||
@@ -1013,14 +956,19 @@ class _SelectImageOverlayContentState
|
|||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withAlpha(50),
|
color: Colors.black.withAlpha(50),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment(0, -0.8),
|
alignment: Alignment(
|
||||||
|
0,
|
||||||
|
-0.8,
|
||||||
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 232,
|
width: 232,
|
||||||
height: 42,
|
height: 42,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.colorScheme.surface,
|
color: context.colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: context.colorScheme.outlineVariant),
|
border: Border.all(
|
||||||
|
color: context.colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
@@ -376,16 +376,11 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
controller.text =
|
controller.text =
|
||||||
controller.text.replaceLast(words[words.length - 1], "");
|
controller.text.replaceLast(words[words.length - 1], "");
|
||||||
}
|
}
|
||||||
final source = ComicSource.find(searchTarget);
|
if (type != null) {
|
||||||
String insert;
|
controller.text += "${type.name}:$text ";
|
||||||
if (source?.onTagSuggestionSelected != null) {
|
|
||||||
insert = source!.onTagSuggestionSelected!(type?.name ?? '', text);
|
|
||||||
} else {
|
} else {
|
||||||
var t = text;
|
controller.text += "$text ";
|
||||||
if (t.contains(' ')) t = "'$t'";
|
|
||||||
insert = type != null ? "${type.name}:$t" : t;
|
|
||||||
}
|
}
|
||||||
controller.text += "$insert ";
|
|
||||||
suggestions.clear();
|
suggestions.clear();
|
||||||
update();
|
update();
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
|
@@ -124,7 +124,7 @@ class _SearchResultPageState extends State<SearchResultPage> {
|
|||||||
options = widget.options ?? const [];
|
options = widget.options ?? const [];
|
||||||
validateOptions();
|
validateOptions();
|
||||||
appdata.addSearchHistory(text);
|
appdata.addSearchHistory(text);
|
||||||
suggestionsController = _SuggestionsController(controller, sourceKey);
|
suggestionsController = _SuggestionsController(controller);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,8 +213,6 @@ class _SuggestionsController {
|
|||||||
|
|
||||||
final SearchBarController controller;
|
final SearchBarController controller;
|
||||||
|
|
||||||
final String sourceKey;
|
|
||||||
|
|
||||||
OverlayEntry? entry;
|
OverlayEntry? entry;
|
||||||
|
|
||||||
void updateWidget() {
|
void updateWidget() {
|
||||||
@@ -272,7 +270,7 @@ class _SuggestionsController {
|
|||||||
find(TagsTranslation.cosplayerTags, TranslationType.cosplayer);
|
find(TagsTranslation.cosplayerTags, TranslationType.cosplayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
_SuggestionsController(this.controller, this.sourceKey);
|
_SuggestionsController(this.controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Suggestions extends StatefulWidget {
|
class _Suggestions extends StatefulWidget {
|
||||||
@@ -402,16 +400,14 @@ class _SuggestionsState extends State<_Suggestions> {
|
|||||||
controller.text =
|
controller.text =
|
||||||
controller.text.replaceLast(words[words.length - 1], "");
|
controller.text.replaceLast(words[words.length - 1], "");
|
||||||
}
|
}
|
||||||
final source = ComicSource.find(widget.controller.sourceKey);
|
if (text.contains(' ')) {
|
||||||
String insert;
|
text = "'$text'";
|
||||||
if (source?.onTagSuggestionSelected != null) {
|
}
|
||||||
insert = source!.onTagSuggestionSelected!(type?.name ?? '', text);
|
if (type != null) {
|
||||||
} else {
|
controller.text += "${type.name}:$text ";
|
||||||
var t = text;
|
} else {
|
||||||
if (t.contains(' ')) t = "'$t'";
|
controller.text += "$text ";
|
||||||
insert = type != null ? "${type.name}:$t" : t;
|
|
||||||
}
|
}
|
||||||
controller.text += "$insert ";
|
|
||||||
widget.controller.suggestions.clear();
|
widget.controller.suggestions.clear();
|
||||||
widget.controller.remove();
|
widget.controller.remove();
|
||||||
}
|
}
|
||||||
|
@@ -193,46 +193,12 @@ class LogsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LogsPageState extends State<LogsPage> {
|
class _LogsPageState extends State<LogsPage> {
|
||||||
String logLevelToShow = "all";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var logToShow = logLevelToShow == "all"
|
|
||||||
? Log.logs
|
|
||||||
: Log.logs.where((log) => log.level.name == logLevelToShow).toList();
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: Appbar(
|
appBar: Appbar(
|
||||||
title: Text("Logs".tl),
|
title: const Text("Logs"),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
|
||||||
onPressed: () => setState(() {
|
|
||||||
final RelativeRect position = RelativeRect.fromLTRB(
|
|
||||||
MediaQuery.of(context).size.width,
|
|
||||||
MediaQuery.of(context).padding.top + kToolbarHeight,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
);
|
|
||||||
showMenu(context: context, position: position, items: [
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text("all"),
|
|
||||||
onTap: () => setState(() => logLevelToShow = "all")
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text("info"),
|
|
||||||
onTap: () => setState(() => logLevelToShow = "info")
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text("warning"),
|
|
||||||
onTap: () => setState(() => logLevelToShow = "warning")
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text("error"),
|
|
||||||
onTap: () => setState(() => logLevelToShow = "error")
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}),
|
|
||||||
icon: const Icon(Icons.filter_list_outlined)
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => setState(() {
|
onPressed: () => setState(() {
|
||||||
final RelativeRect position = RelativeRect.fromLTRB(
|
final RelativeRect position = RelativeRect.fromLTRB(
|
||||||
@@ -251,7 +217,7 @@ class _LogsPageState extends State<LogsPage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Log.ignoreLimitation = true;
|
Log.ignoreLimitation = true;
|
||||||
context.showMessage(
|
context.showMessage(
|
||||||
message: "Only valid for this run".tl);
|
message: "Only valid for this run");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
@@ -266,9 +232,9 @@ class _LogsPageState extends State<LogsPage> {
|
|||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
itemCount: logToShow.length,
|
itemCount: Log.logs.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
index = logToShow.length - index - 1;
|
index = Log.logs.length - index - 1;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||||
child: SelectionArea(
|
child: SelectionArea(
|
||||||
@@ -287,7 +253,7 @@ class _LogsPageState extends State<LogsPage> {
|
|||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(5, 0, 5, 1),
|
padding: const EdgeInsets.fromLTRB(5, 0, 5, 1),
|
||||||
child: Text(logToShow[index].title),
|
child: Text(Log.logs[index].title),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -299,16 +265,16 @@ class _LogsPageState extends State<LogsPage> {
|
|||||||
Theme.of(context).colorScheme.error,
|
Theme.of(context).colorScheme.error,
|
||||||
Theme.of(context).colorScheme.errorContainer,
|
Theme.of(context).colorScheme.errorContainer,
|
||||||
Theme.of(context).colorScheme.primaryContainer
|
Theme.of(context).colorScheme.primaryContainer
|
||||||
][logToShow[index].level.index],
|
][Log.logs[index].level.index],
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.all(Radius.circular(16)),
|
const BorderRadius.all(Radius.circular(16)),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(5, 0, 5, 1),
|
padding: const EdgeInsets.fromLTRB(5, 0, 5, 1),
|
||||||
child: Text(
|
child: Text(
|
||||||
logToShow[index].level.name,
|
Log.logs[index].level.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: logToShow[index].level.index == 0
|
color: Log.logs[index].level.index == 0
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.black),
|
: Colors.black),
|
||||||
),
|
),
|
||||||
@@ -316,14 +282,14 @@ class _LogsPageState extends State<LogsPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(logToShow[index].content),
|
Text(Log.logs[index].content),
|
||||||
Text(logToShow[index].time
|
Text(Log.logs[index].time
|
||||||
.toString()
|
.toString()
|
||||||
.replaceAll(RegExp(r"\.\w+"), "")),
|
.replaceAll(RegExp(r"\.\w+"), "")),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(text: logToShow[index].content));
|
ClipboardData(text: Log.logs[index].content));
|
||||||
},
|
},
|
||||||
child: Text("Copy".tl),
|
child: Text("Copy".tl),
|
||||||
),
|
),
|
||||||
|
@@ -18,8 +18,8 @@ class DebugPageState extends State<DebugPage> {
|
|||||||
slivers: [
|
slivers: [
|
||||||
SliverAppbar(title: Text("Debug".tl)),
|
SliverAppbar(title: Text("Debug".tl)),
|
||||||
_CallbackSetting(
|
_CallbackSetting(
|
||||||
title: "Reload Configs".tl,
|
title: "Reload Configs",
|
||||||
actionTitle: "Reload".tl,
|
actionTitle: "Reload",
|
||||||
callback: () {
|
callback: () {
|
||||||
ComicSourceManager().reload();
|
ComicSourceManager().reload();
|
||||||
},
|
},
|
||||||
|
@@ -52,10 +52,6 @@ class _ExploreSettingsState extends State<ExploreSettings> {
|
|||||||
title: "Show history on comic tile".tl,
|
title: "Show history on comic tile".tl,
|
||||||
settingKey: "showHistoryStatusOnTile",
|
settingKey: "showHistoryStatusOnTile",
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SwitchSetting(
|
|
||||||
title: "Reverse default chapter order".tl,
|
|
||||||
settingKey: "reverseChapterOrder",
|
|
||||||
).toSliver(),
|
|
||||||
_PopupWindowSetting(
|
_PopupWindowSetting(
|
||||||
title: "Keyword blocking".tl,
|
title: "Keyword blocking".tl,
|
||||||
builder: () => const _ManageBlockingWordView(),
|
builder: () => const _ManageBlockingWordView(),
|
||||||
|
@@ -1,16 +1,9 @@
|
|||||||
part of 'settings_page.dart';
|
part of 'settings_page.dart';
|
||||||
|
|
||||||
class ReaderSettings extends StatefulWidget {
|
class ReaderSettings extends StatefulWidget {
|
||||||
const ReaderSettings({
|
const ReaderSettings({super.key, this.onChanged});
|
||||||
super.key,
|
|
||||||
this.onChanged,
|
|
||||||
this.comicId,
|
|
||||||
this.comicSource,
|
|
||||||
});
|
|
||||||
|
|
||||||
final void Function(String key)? onChanged;
|
final void Function(String key)? onChanged;
|
||||||
final String? comicId;
|
|
||||||
final String? comicSource;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ReaderSettings> createState() => _ReaderSettingsState();
|
State<ReaderSettings> createState() => _ReaderSettingsState();
|
||||||
@@ -28,8 +21,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("enableTapToTurnPages");
|
widget.onChanged?.call("enableTapToTurnPages");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: "Reverse tap to turn Pages".tl,
|
title: "Reverse tap to turn Pages".tl,
|
||||||
@@ -37,8 +28,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("reverseTapToTurnPages");
|
widget.onChanged?.call("reverseTapToTurnPages");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: "Page animation".tl,
|
title: "Page animation".tl,
|
||||||
@@ -46,15 +35,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("enablePageAnimation");
|
widget.onChanged?.call("enablePageAnimation");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
|
||||||
_SwitchSetting(
|
|
||||||
title: "Enable comic specific settings".tl,
|
|
||||||
settingKey: "enableComicSpecificSettings",
|
|
||||||
onChanged: () {
|
|
||||||
widget.onChanged?.call("enableComicSpecificSettings");
|
|
||||||
},
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SelectSetting(
|
SelectSetting(
|
||||||
title: "Reading mode".tl,
|
title: "Reading mode".tl,
|
||||||
@@ -78,8 +58,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
}
|
}
|
||||||
widget.onChanged?.call("readerMode");
|
widget.onChanged?.call("readerMode");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SliderSetting(
|
_SliderSetting(
|
||||||
title: "Auto page turning interval".tl,
|
title: "Auto page turning interval".tl,
|
||||||
@@ -91,8 +69,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
widget.onChanged?.call("autoPageTurningInterval");
|
widget.onChanged?.call("autoPageTurningInterval");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SliverAnimatedVisibility(
|
SliverAnimatedVisibility(
|
||||||
visible: appdata.settings['readerMode']!.startsWith('gallery'),
|
visible: appdata.settings['readerMode']!.startsWith('gallery'),
|
||||||
@@ -108,8 +84,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
widget.onChanged?.call("readerScreenPicNumberForLandscape");
|
widget.onChanged?.call("readerScreenPicNumberForLandscape");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverAnimatedVisibility(
|
SliverAnimatedVisibility(
|
||||||
@@ -125,13 +99,10 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("readerScreenPicNumberForPortrait");
|
widget.onChanged?.call("readerScreenPicNumberForPortrait");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverAnimatedVisibility(
|
SliverAnimatedVisibility(
|
||||||
visible:
|
visible: appdata.settings['readerMode']!.startsWith('gallery') &&
|
||||||
appdata.settings['readerMode']!.startsWith('gallery') &&
|
|
||||||
(appdata.settings['readerScreenPicNumberForLandscape'] > 1 ||
|
(appdata.settings['readerScreenPicNumberForLandscape'] > 1 ||
|
||||||
appdata.settings['readerScreenPicNumberForPortrait'] > 1),
|
appdata.settings['readerScreenPicNumberForPortrait'] > 1),
|
||||||
child: _SwitchSetting(
|
child: _SwitchSetting(
|
||||||
@@ -140,8 +111,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("showSingleImageOnFirstPage");
|
widget.onChanged?.call("showSingleImageOnFirstPage");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
@@ -151,8 +120,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
widget.onChanged?.call('enableDoubleTapToZoom');
|
widget.onChanged?.call('enableDoubleTapToZoom');
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: 'Long press to zoom'.tl,
|
title: 'Long press to zoom'.tl,
|
||||||
@@ -161,8 +128,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
widget.onChanged?.call('enableLongPressToZoom');
|
widget.onChanged?.call('enableLongPressToZoom');
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SliverAnimatedVisibility(
|
SliverAnimatedVisibility(
|
||||||
visible: appdata.settings['enableLongPressToZoom'] == true,
|
visible: appdata.settings['enableLongPressToZoom'] == true,
|
||||||
@@ -173,8 +138,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
"press": "Press position".tl,
|
"press": "Press position".tl,
|
||||||
"center": "Screen center".tl,
|
"center": "Screen center".tl,
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
@@ -184,8 +147,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call('limitImageWidth');
|
widget.onChanged?.call('limitImageWidth');
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
if (App.isAndroid)
|
if (App.isAndroid)
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
@@ -194,8 +155,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call('enableTurnPageByVolumeKey');
|
widget.onChanged?.call('enableTurnPageByVolumeKey');
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: "Display time & battery info in reader".tl,
|
title: "Display time & battery info in reader".tl,
|
||||||
@@ -203,17 +162,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("enableClockAndBatteryInfoInReader");
|
widget.onChanged?.call("enableClockAndBatteryInfoInReader");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
|
||||||
_SwitchSetting(
|
|
||||||
title: "Show system status bar".tl,
|
|
||||||
settingKey: "showSystemStatusBar",
|
|
||||||
onChanged: () {
|
|
||||||
widget.onChanged?.call("showSystemStatusBar");
|
|
||||||
},
|
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SelectSetting(
|
SelectSetting(
|
||||||
title: "Quick collect image".tl,
|
title: "Quick collect image".tl,
|
||||||
@@ -229,8 +177,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
help:
|
help:
|
||||||
"On the image browsing page, you can quickly collect images by sliding horizontally or vertically according to your reading mode"
|
"On the image browsing page, you can quickly collect images by sliding horizontally or vertically according to your reading mode"
|
||||||
.tl,
|
.tl,
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_CallbackSetting(
|
_CallbackSetting(
|
||||||
title: "Custom Image Processing".tl,
|
title: "Custom Image Processing".tl,
|
||||||
@@ -243,8 +189,6 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
interval: 1,
|
interval: 1,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 16,
|
max: 16,
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: "Show Page Number".tl,
|
title: "Show Page Number".tl,
|
||||||
@@ -252,39 +196,7 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
onChanged: () {
|
onChanged: () {
|
||||||
widget.onChanged?.call("showPageNumberInReader");
|
widget.onChanged?.call("showPageNumberInReader");
|
||||||
},
|
},
|
||||||
comicId: widget.comicId,
|
|
||||||
comicSource: widget.comicSource,
|
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
// reset button
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (widget.comicId == null) {
|
|
||||||
appdata.settings.resetAllComicReaderSettings();
|
|
||||||
} else {
|
|
||||||
var keys = appdata
|
|
||||||
.settings['comicSpecificSettings']["${widget.comicId}@${widget.comicSource}"]
|
|
||||||
?.keys;
|
|
||||||
appdata.settings.resetComicReaderSettings(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
);
|
|
||||||
if (keys != null) {
|
|
||||||
setState(() {});
|
|
||||||
for (var key in keys) {
|
|
||||||
widget.onChanged?.call(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
(widget.comicId == null
|
|
||||||
? "Clear specific reader settings for all comics"
|
|
||||||
: "Clear specific reader settings for this comic")
|
|
||||||
.tl,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -329,7 +241,7 @@ class __CustomImageProcessingState extends State<_CustomImageProcessing> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Text("Reset".tl),
|
child: Text("Reset".tl),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
@@ -355,7 +267,7 @@ class __CustomImageProcessingState extends State<_CustomImageProcessing> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -6,8 +6,6 @@ class _SwitchSetting extends StatefulWidget {
|
|||||||
required this.settingKey,
|
required this.settingKey,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.comicId,
|
|
||||||
this.comicSource,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
@@ -18,10 +16,6 @@ class _SwitchSetting extends StatefulWidget {
|
|||||||
|
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
|
|
||||||
final String? comicId;
|
|
||||||
|
|
||||||
final String? comicSource;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_SwitchSetting> createState() => _SwitchSettingState();
|
State<_SwitchSetting> createState() => _SwitchSettingState();
|
||||||
}
|
}
|
||||||
@@ -29,33 +23,16 @@ class _SwitchSetting extends StatefulWidget {
|
|||||||
class _SwitchSettingState extends State<_SwitchSetting> {
|
class _SwitchSettingState extends State<_SwitchSetting> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var value = widget.comicId == null
|
assert(appdata.settings[widget.settingKey] is bool);
|
||||||
? appdata.settings[widget.settingKey]
|
|
||||||
: appdata.settings.getReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(value is bool);
|
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
subtitle: widget.subtitle == null ? null : Text(widget.subtitle!),
|
subtitle: widget.subtitle == null ? null : Text(widget.subtitle!),
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: value,
|
value: appdata.settings[widget.settingKey],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.comicId == null) {
|
|
||||||
appdata.settings[widget.settingKey] = value;
|
appdata.settings[widget.settingKey] = value;
|
||||||
} else {
|
|
||||||
appdata.settings.setReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingKey,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
appdata.saveData().then((_) {
|
appdata.saveData().then((_) {
|
||||||
widget.onChanged?.call();
|
widget.onChanged?.call();
|
||||||
@@ -74,8 +51,6 @@ class SelectSetting extends StatelessWidget {
|
|||||||
required this.optionTranslation,
|
required this.optionTranslation,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.help,
|
this.help,
|
||||||
this.comicId,
|
|
||||||
this.comicSource,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
@@ -88,10 +63,6 @@ class SelectSetting extends StatelessWidget {
|
|||||||
|
|
||||||
final String? help;
|
final String? help;
|
||||||
|
|
||||||
final String? comicId;
|
|
||||||
|
|
||||||
final String? comicSource;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -105,8 +76,6 @@ class SelectSetting extends StatelessWidget {
|
|||||||
optionTranslation: optionTranslation,
|
optionTranslation: optionTranslation,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
help: help,
|
help: help,
|
||||||
comicId: comicId,
|
|
||||||
comicSource: comicSource,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return _EndSelectorSelectSetting(
|
return _EndSelectorSelectSetting(
|
||||||
@@ -115,8 +84,6 @@ class SelectSetting extends StatelessWidget {
|
|||||||
optionTranslation: optionTranslation,
|
optionTranslation: optionTranslation,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
help: help,
|
help: help,
|
||||||
comicId: comicId,
|
|
||||||
comicSource: comicSource,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -132,8 +99,6 @@ class _DoubleLineSelectSettings extends StatefulWidget {
|
|||||||
required this.optionTranslation,
|
required this.optionTranslation,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.help,
|
this.help,
|
||||||
this.comicId,
|
|
||||||
this.comicSource,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
@@ -146,10 +111,6 @@ class _DoubleLineSelectSettings extends StatefulWidget {
|
|||||||
|
|
||||||
final String? help;
|
final String? help;
|
||||||
|
|
||||||
final String? comicId;
|
|
||||||
|
|
||||||
final String? comicSource;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_DoubleLineSelectSettings> createState() =>
|
State<_DoubleLineSelectSettings> createState() =>
|
||||||
_DoubleLineSelectSettingsState();
|
_DoubleLineSelectSettingsState();
|
||||||
@@ -158,14 +119,6 @@ class _DoubleLineSelectSettings extends StatefulWidget {
|
|||||||
class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
|
class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var value = widget.comicId == null
|
|
||||||
? appdata.settings[widget.settingKey]
|
|
||||||
: appdata.settings.getReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -181,9 +134,9 @@ class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
title: "Help".tl,
|
title: "Help".tl,
|
||||||
content: Text(
|
content: Text(widget.help!)
|
||||||
widget.help!,
|
.paddingHorizontal(16)
|
||||||
).paddingHorizontal(16).fixWidth(double.infinity),
|
.fixWidth(double.infinity),
|
||||||
actions: [
|
actions: [
|
||||||
Button.filled(
|
Button.filled(
|
||||||
onPressed: context.pop,
|
onPressed: context.pop,
|
||||||
@@ -197,7 +150,9 @@ class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Text(widget.optionTranslation[value] ?? "None"),
|
subtitle: Text(
|
||||||
|
widget.optionTranslation[appdata.settings[widget.settingKey]] ??
|
||||||
|
"None"),
|
||||||
trailing: const Icon(Icons.arrow_drop_down),
|
trailing: const Icon(Icons.arrow_drop_down),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
var renderBox = context.findRenderObject() as RenderBox;
|
var renderBox = context.findRenderObject() as RenderBox;
|
||||||
@@ -215,27 +170,16 @@ class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
|
|||||||
Offset.zero & MediaQuery.of(context).size,
|
Offset.zero & MediaQuery.of(context).size,
|
||||||
),
|
),
|
||||||
items: widget.optionTranslation.keys
|
items: widget.optionTranslation.keys
|
||||||
.map(
|
.map((key) => PopupMenuItem(
|
||||||
(key) => PopupMenuItem(
|
|
||||||
value: key,
|
value: key,
|
||||||
height: App.isMobile ? 46 : 40,
|
height: App.isMobile ? 46 : 40,
|
||||||
child: Text(widget.optionTranslation[key]!),
|
child: Text(widget.optionTranslation[key]!),
|
||||||
),
|
))
|
||||||
)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.comicId == null) {
|
|
||||||
appdata.settings[widget.settingKey] = value;
|
appdata.settings[widget.settingKey] = value;
|
||||||
} else {
|
|
||||||
appdata.settings.setReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingKey,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
appdata.saveData();
|
appdata.saveData();
|
||||||
widget.onChanged?.call();
|
widget.onChanged?.call();
|
||||||
@@ -253,8 +197,6 @@ class _EndSelectorSelectSetting extends StatefulWidget {
|
|||||||
required this.optionTranslation,
|
required this.optionTranslation,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.help,
|
this.help,
|
||||||
this.comicId,
|
|
||||||
this.comicSource,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
@@ -267,10 +209,6 @@ class _EndSelectorSelectSetting extends StatefulWidget {
|
|||||||
|
|
||||||
final String? help;
|
final String? help;
|
||||||
|
|
||||||
final String? comicId;
|
|
||||||
|
|
||||||
final String? comicSource;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_EndSelectorSelectSetting> createState() =>
|
State<_EndSelectorSelectSetting> createState() =>
|
||||||
_EndSelectorSelectSettingState();
|
_EndSelectorSelectSettingState();
|
||||||
@@ -280,13 +218,6 @@ class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var options = widget.optionTranslation;
|
var options = widget.optionTranslation;
|
||||||
var value = widget.comicId == null
|
|
||||||
? appdata.settings[widget.settingKey]
|
|
||||||
: appdata.settings.getReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingKey,
|
|
||||||
);
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -302,9 +233,9 @@ class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
title: "Help".tl,
|
title: "Help".tl,
|
||||||
content: Text(
|
content: Text(widget.help!)
|
||||||
widget.help!,
|
.paddingHorizontal(16)
|
||||||
).paddingHorizontal(16).fixWidth(double.infinity),
|
.fixWidth(double.infinity),
|
||||||
actions: [
|
actions: [
|
||||||
Button.filled(
|
Button.filled(
|
||||||
onPressed: context.pop,
|
onPressed: context.pop,
|
||||||
@@ -319,22 +250,12 @@ class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: Select(
|
trailing: Select(
|
||||||
current: options[value],
|
current: options[appdata.settings[widget.settingKey]],
|
||||||
values: options.values.toList(),
|
values: options.values.toList(),
|
||||||
minWidth: 64,
|
minWidth: 64,
|
||||||
onTap: (index) {
|
onTap: (index) {
|
||||||
setState(() {
|
setState(() {
|
||||||
var value = options.keys.elementAt(index);
|
appdata.settings[widget.settingKey] = options.keys.elementAt(index);
|
||||||
if (widget.comicId == null) {
|
|
||||||
appdata.settings[widget.settingKey] = value;
|
|
||||||
} else {
|
|
||||||
appdata.settings.setReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingKey,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
appdata.saveData();
|
appdata.saveData();
|
||||||
widget.onChanged?.call();
|
widget.onChanged?.call();
|
||||||
@@ -352,8 +273,6 @@ class _SliderSetting extends StatefulWidget {
|
|||||||
required this.min,
|
required this.min,
|
||||||
required this.max,
|
required this.max,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.comicId,
|
|
||||||
this.comicSource,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
@@ -368,10 +287,6 @@ class _SliderSetting extends StatefulWidget {
|
|||||||
|
|
||||||
final VoidCallback? onChanged;
|
final VoidCallback? onChanged;
|
||||||
|
|
||||||
final String? comicId;
|
|
||||||
|
|
||||||
final String? comicSource;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_SliderSetting> createState() => _SliderSettingState();
|
State<_SliderSetting> createState() => _SliderSettingState();
|
||||||
}
|
}
|
||||||
@@ -379,52 +294,28 @@ class _SliderSetting extends StatefulWidget {
|
|||||||
class _SliderSettingState extends State<_SliderSetting> {
|
class _SliderSettingState extends State<_SliderSetting> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var value =
|
|
||||||
(widget.comicId == null
|
|
||||||
? appdata.settings[widget.settingsIndex]
|
|
||||||
: appdata.settings.getReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingsIndex,
|
|
||||||
))
|
|
||||||
.toDouble();
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(widget.title),
|
Text(widget.title),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(value.toString(), style: ts.s12),
|
Text(
|
||||||
|
appdata.settings[widget.settingsIndex].toString(),
|
||||||
|
style: ts.s12,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Slider(
|
subtitle: Slider(
|
||||||
value: value,
|
value: appdata.settings[widget.settingsIndex].toDouble(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value.toInt() == value) {
|
if (value.toInt() == value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.comicId == null) {
|
|
||||||
appdata.settings[widget.settingsIndex] = value.toInt();
|
appdata.settings[widget.settingsIndex] = value.toInt();
|
||||||
} else {
|
|
||||||
appdata.settings.setReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingsIndex,
|
|
||||||
value.toInt(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
appdata.saveData();
|
appdata.saveData();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.comicId == null) {
|
|
||||||
appdata.settings[widget.settingsIndex] = value;
|
appdata.settings[widget.settingsIndex] = value;
|
||||||
} else {
|
|
||||||
appdata.settings.setReaderSetting(
|
|
||||||
widget.comicId!,
|
|
||||||
widget.comicSource!,
|
|
||||||
widget.settingsIndex,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
appdata.saveData();
|
appdata.saveData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -514,8 +405,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
|
|||||||
color: Colors.black12,
|
color: Colors.black12,
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
offset: Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2)
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onReorder: (reorderFunc) {
|
onReorder: (reorderFunc) {
|
||||||
@@ -545,7 +435,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
|
|||||||
label: Text("Add".tl),
|
label: Text("Add".tl),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
onPressed: showAddDialog,
|
onPressed: showAddDialog,
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
body: view,
|
body: view,
|
||||||
);
|
);
|
||||||
@@ -560,8 +450,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
|
|||||||
keys.remove(key);
|
keys.remove(key);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.delete_outline),
|
icon: const Icon(Icons.delete_outline)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
@@ -569,7 +458,10 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
|
|||||||
key: Key(key),
|
key: Key(key),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [removeButton, const Icon(Icons.drag_handle)],
|
children: [
|
||||||
|
removeButton,
|
||||||
|
const Icon(Icons.drag_handle),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -585,8 +477,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(builder: (context, setState) {
|
||||||
builder: (context, setState) {
|
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
title: "Add".tl,
|
title: "Add".tl,
|
||||||
content: Column(
|
content: Column(
|
||||||
@@ -643,8 +534,7 @@ class _MultiPagesFilterState extends State<_MultiPagesFilter> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -28,8 +28,6 @@ final _resolver = MimeTypeResolver()
|
|||||||
..addMagicNumber([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed')
|
..addMagicNumber([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed')
|
||||||
// rar
|
// rar
|
||||||
..addMagicNumber([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar')
|
..addMagicNumber([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar')
|
||||||
// avif
|
|
||||||
..addMagicNumber([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66], 'image/avif')
|
|
||||||
;
|
;
|
||||||
|
|
||||||
FileType detectFileType(List<int> data) {
|
FileType detectFileType(List<int> data) {
|
||||||
|
@@ -1,67 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
abstract class OpenCC {
|
|
||||||
static late final Map<int, int> _s2t;
|
|
||||||
static late final Map<int, int> _t2s;
|
|
||||||
|
|
||||||
static Future<void> init() async {
|
|
||||||
var data = await rootBundle.load("assets/opencc.txt");
|
|
||||||
var txt = utf8.decode(data.buffer.asUint8List());
|
|
||||||
_s2t = <int, int>{};
|
|
||||||
_t2s = <int, int>{};
|
|
||||||
for (var line in txt.split('\n')) {
|
|
||||||
if (line.isEmpty || line.startsWith('#') || line.length != 2) continue;
|
|
||||||
var s = line.runes.elementAt(0);
|
|
||||||
var t = line.runes.elementAt(1);
|
|
||||||
_s2t[s] = t;
|
|
||||||
_t2s[t] = s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hasChineseSimplified(String text) {
|
|
||||||
if (text != "监禁") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (var rune in text.runes) {
|
|
||||||
if (_s2t.containsKey(rune)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hasChineseTraditional(String text) {
|
|
||||||
for (var rune in text.runes) {
|
|
||||||
if (_t2s.containsKey(rune)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String simplifiedToTraditional(String text) {
|
|
||||||
var sb = StringBuffer();
|
|
||||||
for (var rune in text.runes) {
|
|
||||||
if (_s2t.containsKey(rune)) {
|
|
||||||
sb.write(String.fromCharCodes([_s2t[rune]!]));
|
|
||||||
} else {
|
|
||||||
sb.write(String.fromCharCodes([rune]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static String traditionalToSimplified(String text) {
|
|
||||||
var sb = StringBuffer();
|
|
||||||
for (var rune in text.runes) {
|
|
||||||
if (_t2s.containsKey(rune)) {
|
|
||||||
sb.write(String.fromCharCodes([_t2s[rune]!]));
|
|
||||||
} else {
|
|
||||||
sb.write(String.fromCharCodes([rune]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1116,4 +1116,4 @@ packages:
|
|||||||
version: "0.0.12"
|
version: "0.0.12"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.32.6"
|
flutter: ">=3.32.4"
|
||||||
|
@@ -2,11 +2,11 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.4.6+146
|
version: 1.4.5+145
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
flutter: 3.32.6
|
flutter: 3.32.4
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -103,7 +103,6 @@ flutter:
|
|||||||
- assets/app_icon.png
|
- assets/app_icon.png
|
||||||
- assets/tags.json
|
- assets/tags.json
|
||||||
- assets/tags_tw.json
|
- assets/tags_tw.json
|
||||||
- assets/opencc.txt
|
|
||||||
|
|
||||||
flutter_to_arch:
|
flutter_to_arch:
|
||||||
name: Venera
|
name: Venera
|
||||||
|
@@ -1,150 +0,0 @@
|
|||||||
import json
|
|
||||||
import plistlib
|
|
||||||
import re
|
|
||||||
import requests
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
def prepare_description(text):
|
|
||||||
text = re.sub('<[^<]+?>', '', text) # Remove HTML tags
|
|
||||||
text = re.sub(r'#{1,6}\s?', '', text) # Remove markdown header tags
|
|
||||||
text = re.sub(r'\*{2}', '', text) # Remove all occurrences of two consecutive asterisks
|
|
||||||
text = re.sub(r'(?<=\r|\n)-', '•', text) # Only replace - with • if it is preceded by \r or \n
|
|
||||||
text = re.sub(r'`', '"', text) # Replace ` with "
|
|
||||||
text = re.sub(r'\r\n\r\n', '\r \n', text) # Replace \r\n\r\n with \r \n (avoid incorrect display of the description regarding paragraphs)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def fetch_latest_release(repo_url):
|
|
||||||
api_url = f"https://api.github.com/repos/{repo_url}/releases"
|
|
||||||
headers = {
|
|
||||||
"Accept": "application/vnd.github+json",
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
response = requests.get(api_url, headers=headers)
|
|
||||||
response.raise_for_status()
|
|
||||||
release = response.json()
|
|
||||||
return release
|
|
||||||
except requests.RequestException as e:
|
|
||||||
print(f"Error fetching releases: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_file_size(url):
|
|
||||||
try:
|
|
||||||
response = requests.head(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
return int(response.headers.get('Content-Length', 0))
|
|
||||||
except requests.RequestException as e:
|
|
||||||
print(f"Error getting file size: {e}")
|
|
||||||
return 194586
|
|
||||||
|
|
||||||
def update_json_file_release(json_file, latest_release):
|
|
||||||
if isinstance(latest_release, list) and latest_release:
|
|
||||||
latest_release = latest_release[0]
|
|
||||||
else:
|
|
||||||
print("Error getting latest release")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(json_file, "r") as file:
|
|
||||||
data = json.load(file)
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
print(f"Error reading JSON file: {e}")
|
|
||||||
data = {"apps": []}
|
|
||||||
raise
|
|
||||||
|
|
||||||
app = data["apps"][0]
|
|
||||||
|
|
||||||
full_version = latest_release["tag_name"]
|
|
||||||
tag = latest_release["tag_name"]
|
|
||||||
# Extract version like 1.4.5 from tag, which may be like 'v1.4.5'
|
|
||||||
version_match = re.search(r"(\d+\.\d+\.\d+)", full_version)
|
|
||||||
if version_match:
|
|
||||||
version = version_match.group(1)
|
|
||||||
else:
|
|
||||||
print("Error: Could not parse version from tag_name.")
|
|
||||||
return
|
|
||||||
version_date = latest_release["published_at"]
|
|
||||||
date_obj = datetime.strptime(version_date, "%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
version_date = date_obj.strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
description = latest_release["body"]
|
|
||||||
description = prepare_description(description)
|
|
||||||
|
|
||||||
assets = latest_release.get("assets", [])
|
|
||||||
download_url = None
|
|
||||||
size = None
|
|
||||||
for asset in assets:
|
|
||||||
# venera-ios-1.4.5+145.ipa
|
|
||||||
if asset["name"] == f"venera-ios-{version}+{version.replace('.', '')}.ipa":
|
|
||||||
download_url = asset["browser_download_url"]
|
|
||||||
size = asset["size"]
|
|
||||||
break
|
|
||||||
|
|
||||||
if download_url is None or size is None:
|
|
||||||
print("Error: IPA file not found in release assets.")
|
|
||||||
return
|
|
||||||
|
|
||||||
version_entry = {
|
|
||||||
"version": version,
|
|
||||||
"date": version_date,
|
|
||||||
"localizedDescription": description,
|
|
||||||
"downloadURL": download_url,
|
|
||||||
"size": size
|
|
||||||
}
|
|
||||||
|
|
||||||
duplicate_entries = [item for item in app["versions"] if item["version"] == version]
|
|
||||||
if duplicate_entries:
|
|
||||||
app["versions"].remove(duplicate_entries[0])
|
|
||||||
|
|
||||||
app["versions"].insert(0, version_entry)
|
|
||||||
|
|
||||||
app.update({
|
|
||||||
"version": version,
|
|
||||||
"versionDate": version_date,
|
|
||||||
"versionDescription": description,
|
|
||||||
"downloadURL": download_url,
|
|
||||||
"size": size
|
|
||||||
})
|
|
||||||
|
|
||||||
if "news" not in data:
|
|
||||||
data["news"] = []
|
|
||||||
|
|
||||||
news_identifier = f"release-{full_version}"
|
|
||||||
date_string = date_obj.strftime("%d/%m/%y")
|
|
||||||
news_entry = {
|
|
||||||
"appID": "com.github.wgh136.venera",
|
|
||||||
"caption": f"Update of Venera just got released!",
|
|
||||||
"date": latest_release["published_at"],
|
|
||||||
"identifier": news_identifier,
|
|
||||||
"notify": True,
|
|
||||||
"tintColor": "#0784FC",
|
|
||||||
"title": f"{full_version} - Venera {date_string}",
|
|
||||||
"url": f"https://github.com/venera-app/venera/releases/tag/{tag}"
|
|
||||||
}
|
|
||||||
|
|
||||||
news_entry_exists = any(item["identifier"] == news_identifier for item in data["news"])
|
|
||||||
if not news_entry_exists:
|
|
||||||
data["news"].append(news_entry)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(json_file, "w") as file:
|
|
||||||
json.dump(data, file, indent=2)
|
|
||||||
print("JSON file updated successfully.")
|
|
||||||
except IOError as e:
|
|
||||||
print(f"Error writing to JSON file: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def main():
|
|
||||||
repo_url = "venera-app/venera"
|
|
||||||
is_nightly = "NIGHTLY_LINK" in os.environ
|
|
||||||
|
|
||||||
try:
|
|
||||||
fetched_data_latest = fetch_latest_release(repo_url)
|
|
||||||
json_file = "alt_store.json"
|
|
||||||
update_json_file_release(json_file, fetched_data_latest)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"An error occurred: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@@ -98,20 +98,14 @@ bool FlutterWindow::OnCreate() {
|
|||||||
else
|
else
|
||||||
result->Success(flutter::EncodableValue("No Proxy"));
|
result->Success(flutter::EncodableValue("No Proxy"));
|
||||||
delete(res);
|
delete(res);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
#ifdef NDEBUG
|
|
||||||
else if (call.method_name() == "heartBeat") {
|
else if (call.method_name() == "heartBeat") {
|
||||||
|
|
||||||
if (monitorThread == nullptr) {
|
if (monitorThread == nullptr) {
|
||||||
monitorThread = new std::thread{ monitorUIThread };
|
monitorThread = new std::thread{ monitorUIThread };
|
||||||
}
|
}
|
||||||
lastHeartbeat = std::chrono::steady_clock::now();
|
lastHeartbeat = std::chrono::steady_clock::now();
|
||||||
result->Success();
|
result->Success();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
result->Success(); // Default response for unhandled method calls
|
|
||||||
});
|
});
|
||||||
|
|
||||||
flutter::EventChannel<> channel2(
|
flutter::EventChannel<> channel2(
|
||||||
|
Reference in New Issue
Block a user