mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
@@ -67,7 +67,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.github.wgh136.venera"
|
applicationId = "com.github.wgh136.venera"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
@@ -125,6 +124,6 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.activity:activity-ktx:1.9.2"
|
implementation "androidx.activity:activity-ktx:1.10.1"
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
}
|
}
|
||||||
|
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||||
|
@@ -18,7 +18,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '8.3.2' apply false
|
id "com.android.application" version '8.9.0' apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
|
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -140,18 +140,18 @@
|
|||||||
"Block": "屏蔽",
|
"Block": "屏蔽",
|
||||||
"Add new favorite to": "添加新收藏到",
|
"Add new favorite to": "添加新收藏到",
|
||||||
"Move favorite after reading": "阅读后移动收藏",
|
"Move favorite after reading": "阅读后移动收藏",
|
||||||
"Delete folder?" : "删除文件夹?",
|
"Delete folder?": "删除文件夹?",
|
||||||
"Delete folder '@f' ?" : "删除文件夹 '@f' ?",
|
"Delete folder '@f' ?": "删除文件夹 '@f' ?",
|
||||||
"Import from file": "从文件导入",
|
"Import from file": "从文件导入",
|
||||||
"Failed to import": "导入失败",
|
"Failed to import": "导入失败",
|
||||||
"Cache Limit": "缓存限制",
|
"Cache Limit": "缓存限制",
|
||||||
"Set Cache Limit": "设置缓存限制",
|
"Set Cache Limit": "设置缓存限制",
|
||||||
"Size in MB": "大小(MB)",
|
"Size in MB": "大小(MB)",
|
||||||
"Select a directory which contains the comic directories." : "选择一个包含漫画文件夹的目录",
|
"Select a directory which contains the comic directories.": "选择一个包含漫画文件夹的目录",
|
||||||
"Help": "帮助",
|
"Help": "帮助",
|
||||||
"Export as cbz": "导出为cbz",
|
"Export as cbz": "导出为cbz",
|
||||||
"Select an archive file (cbz, zip, 7z, cb7)" : "选择一个归档文件 (cbz, zip, 7z, cb7)",
|
"Select an archive file (cbz, zip, 7z, cb7)": "选择一个归档文件 (cbz, zip, 7z, cb7)",
|
||||||
"An archive file" : "一个归档文件",
|
"An archive file": "一个归档文件",
|
||||||
"Fullscreen": "全屏",
|
"Fullscreen": "全屏",
|
||||||
"Exit": "退出",
|
"Exit": "退出",
|
||||||
"View more": "查看更多",
|
"View more": "查看更多",
|
||||||
@@ -198,9 +198,9 @@
|
|||||||
"Long press on the favorite button to quickly add to this folder": "长按收藏按钮快速添加到这个文件夹",
|
"Long press on the favorite button to quickly add to this folder": "长按收藏按钮快速添加到这个文件夹",
|
||||||
"Added": "已添加",
|
"Added": "已添加",
|
||||||
"Turn page by volume keys": "使用音量键翻页",
|
"Turn page by volume keys": "使用音量键翻页",
|
||||||
"Display time & battery info in reader":"在阅读器中显示时间和电量信息",
|
"Display time & battery info in reader": "在阅读器中显示时间和电量信息",
|
||||||
"EhViewer downloads":"EhViewer下载",
|
"EhViewer downloads": "EhViewer下载",
|
||||||
"Select an EhViewer database and a download folder.":"选择EhViewer的下载数据(导出的db文件)与存放下载内容的目录",
|
"Select an EhViewer database and a download folder.": "选择EhViewer的下载数据(导出的db文件)与存放下载内容的目录",
|
||||||
"(EhViewer)Default": "(EhViewer)默认",
|
"(EhViewer)Default": "(EhViewer)默认",
|
||||||
"If you import an EhViewer's database, program will automatically create folders according to the download label in that database.": "若通过EhViewer数据库导入漫画,程序将会按其中的下载标签自动创建收藏文件夹。",
|
"If you import an EhViewer's database, program will automatically create folders according to the download label in that database.": "若通过EhViewer数据库导入漫画,程序将会按其中的下载标签自动创建收藏文件夹。",
|
||||||
"Multi-Select": "进入多选模式",
|
"Multi-Select": "进入多选模式",
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"Delete all unavailable local favorite items": "删除所有无效的本地收藏",
|
"Delete all unavailable local favorite items": "删除所有无效的本地收藏",
|
||||||
"Deleted @a favorite items.": "已删除 @a 条无效收藏",
|
"Deleted @a favorite items.": "已删除 @a 条无效收藏",
|
||||||
"New version available": "有新版本可用",
|
"New version available": "有新版本可用",
|
||||||
"A new version is available. Do you want to update now?" : "有新版本可用。您要现在更新吗?",
|
"A new version is available. Do you want to update now?": "有新版本可用。您要现在更新吗?",
|
||||||
"No new version available": "没有新版本可用",
|
"No new version available": "没有新版本可用",
|
||||||
"Export as pdf": "导出为pdf",
|
"Export as pdf": "导出为pdf",
|
||||||
"Export as epub": "导出为epub",
|
"Export as epub": "导出为epub",
|
||||||
@@ -288,15 +288,15 @@
|
|||||||
"Copy the title successfully": "复制标题成功",
|
"Copy the title successfully": "复制标题成功",
|
||||||
"The comic is invalid, please long press to delete, you can double click the title to copy": "该漫画已失效, 请长按删除, 可以双击标题进行复制",
|
"The comic is invalid, please long press to delete, you can double click the title to copy": "该漫画已失效, 请长按删除, 可以双击标题进行复制",
|
||||||
"No search results found": "未找到搜索结果",
|
"No search results found": "未找到搜索结果",
|
||||||
"Added @c comics to download queue." : "已添加 @c 本漫画到下载队列",
|
"Added @c comics to download queue.": "已添加 @c 本漫画到下载队列",
|
||||||
"Download started": "下载已开始",
|
"Download started": "下载已开始",
|
||||||
"Click favorite": "点击收藏",
|
"Click favorite": "点击收藏",
|
||||||
"End": "末尾",
|
"End": "末尾",
|
||||||
"None": "无",
|
"None": "无",
|
||||||
"View Detail": "查看详情",
|
"View Detail": "查看详情",
|
||||||
"Select a directory which contains multiple archive files." : "选择一个包含多个归档文件的目录",
|
"Select a directory which contains multiple archive files.": "选择一个包含多个归档文件的目录",
|
||||||
"Multiple archive files" : "多个归档文件",
|
"Multiple archive files": "多个归档文件",
|
||||||
"No valid comics found" : "未找到有效的漫画",
|
"No valid comics found": "未找到有效的漫画",
|
||||||
"Enable DNS Overrides": "启用DNS覆写",
|
"Enable DNS Overrides": "启用DNS覆写",
|
||||||
"DNS Overrides": "DNS覆写",
|
"DNS Overrides": "DNS覆写",
|
||||||
"Custom Image Processing": "自定义图片处理",
|
"Custom Image Processing": "自定义图片处理",
|
||||||
@@ -342,12 +342,12 @@
|
|||||||
"Replies": "回复",
|
"Replies": "回复",
|
||||||
"Follow Updates": "追更",
|
"Follow Updates": "追更",
|
||||||
"Not Configured": "未配置",
|
"Not Configured": "未配置",
|
||||||
"Choose a folder to follow updates." : "选择一个文件夹以追更",
|
"Choose a folder to follow updates.": "选择一个文件夹以追更",
|
||||||
"Choose Folder": "选择文件夹",
|
"Choose Folder": "选择文件夹",
|
||||||
"No folders available": "没有可用的文件夹",
|
"No folders available": "没有可用的文件夹",
|
||||||
"Updating comics...": "更新漫画中...",
|
"Updating comics...": "更新漫画中...",
|
||||||
"Automatic update checking enabled." : "已启用自动更新检查",
|
"Automatic update checking enabled.": "已启用自动更新检查",
|
||||||
"The app will check for updates at most once a day." : "APP将每天最多检查一次更新",
|
"The app will check for updates at most once a day.": "APP将每天最多检查一次更新",
|
||||||
"Change Folder": "更改文件夹",
|
"Change Folder": "更改文件夹",
|
||||||
"Check Now": "立即检查",
|
"Check Now": "立即检查",
|
||||||
"Updates": "更新",
|
"Updates": "更新",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"Disabled": "已禁用",
|
"Disabled": "已禁用",
|
||||||
"Auto Sync Data": "自动同步数据",
|
"Auto Sync Data": "自动同步数据",
|
||||||
"Mark all as read": "全部标记为已读",
|
"Mark all as read": "全部标记为已读",
|
||||||
"Do you want to mark all as read?" : "您要全部标记为已读吗?",
|
"Do you want to mark all as read?": "您要全部标记为已读吗?",
|
||||||
"Swipe down for previous chapter": "向下滑动查看上一章",
|
"Swipe down for previous chapter": "向下滑动查看上一章",
|
||||||
"Swipe up for next chapter": "向上滑动查看下一章",
|
"Swipe up for next chapter": "向上滑动查看下一章",
|
||||||
"Initial Page": "初始页面",
|
"Initial Page": "初始页面",
|
||||||
@@ -378,7 +378,16 @@
|
|||||||
"Page": "页面",
|
"Page": "页面",
|
||||||
"Jump": "跳转",
|
"Jump": "跳转",
|
||||||
"Copy Image": "复制图片",
|
"Copy Image": "复制图片",
|
||||||
"A valid WebDav directory URL": "有效的WebDav目录URL"
|
"A valid WebDav directory URL": "有效的WebDav目录URL",
|
||||||
|
"Shut Down": "关闭",
|
||||||
|
"Uploading data...": "正在上传数据...",
|
||||||
|
"Pages": "页数",
|
||||||
|
"Long press zoom position": "长按缩放位置",
|
||||||
|
"Press position": "按压位置",
|
||||||
|
"Screen center": "屏幕中心",
|
||||||
|
"Suggestions": "建议",
|
||||||
|
"Do not report any issues related to sources to App repo.": "请不要向App仓库报告任何与源相关的问题",
|
||||||
|
"Click the setting icon to change the source list url.": "点击设置图标更改源列表URL"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Home": "首頁",
|
"Home": "首頁",
|
||||||
@@ -520,18 +529,18 @@
|
|||||||
"Block": "封鎖",
|
"Block": "封鎖",
|
||||||
"Add new favorite to": "添加新收藏到",
|
"Add new favorite to": "添加新收藏到",
|
||||||
"Move favorite after reading": "閱讀後移動收藏",
|
"Move favorite after reading": "閱讀後移動收藏",
|
||||||
"Delete folder?" : "刪除資料夾?",
|
"Delete folder?": "刪除資料夾?",
|
||||||
"Delete folder '@f' ?" : "刪除資料夾 '@f' ?",
|
"Delete folder '@f' ?": "刪除資料夾 '@f' ?",
|
||||||
"Import from file": "從文件匯入",
|
"Import from file": "從文件匯入",
|
||||||
"Failed to import": "匯入失敗",
|
"Failed to import": "匯入失敗",
|
||||||
"Cache Limit": "快取限制",
|
"Cache Limit": "快取限制",
|
||||||
"Set Cache Limit": "設定快取限制",
|
"Set Cache Limit": "設定快取限制",
|
||||||
"Size in MB": "大小(MB)",
|
"Size in MB": "大小(MB)",
|
||||||
"Select a directory which contains the comic directories." : "選擇一個包含漫畫資料夾的目錄",
|
"Select a directory which contains the comic directories.": "選擇一個包含漫畫資料夾的目錄",
|
||||||
"Help": "幫助",
|
"Help": "幫助",
|
||||||
"Export as cbz": "匯出為cbz",
|
"Export as cbz": "匯出為cbz",
|
||||||
"Select an archive file (cbz, zip, 7z, cb7)" : "選擇一個歸檔文件 (cbz, zip, 7z, cb7)",
|
"Select an archive file (cbz, zip, 7z, cb7)": "選擇一個歸檔文件 (cbz, zip, 7z, cb7)",
|
||||||
"An archive file" : "一個歸檔文件",
|
"An archive file": "一個歸檔文件",
|
||||||
"Fullscreen": "全螢幕",
|
"Fullscreen": "全螢幕",
|
||||||
"Exit": "退出",
|
"Exit": "退出",
|
||||||
"View more": "查看更多",
|
"View more": "查看更多",
|
||||||
@@ -622,13 +631,13 @@
|
|||||||
"Delete all unavailable local favorite items": "刪除所有無效的本機收藏",
|
"Delete all unavailable local favorite items": "刪除所有無效的本機收藏",
|
||||||
"Deleted @a favorite items.": "已刪除 @a 條無效收藏",
|
"Deleted @a favorite items.": "已刪除 @a 條無效收藏",
|
||||||
"New version available": "有新版本可用",
|
"New version available": "有新版本可用",
|
||||||
"A new version is available. Do you want to update now?" : "有新版本可用。您要現在更新嗎?",
|
"A new version is available. Do you want to update now?": "有新版本可用。您要現在更新嗎?",
|
||||||
"No new version available": "沒有新版本可用",
|
"No new version available": "沒有新版本可用",
|
||||||
"Export as pdf": "匯出為pdf",
|
"Export as pdf": "匯出為pdf",
|
||||||
"Export as epub": "匯出為epub",
|
"Export as epub": "匯出為epub",
|
||||||
"Aggregated Search": "聚合搜尋",
|
"Aggregated Search": "聚合搜尋",
|
||||||
"No search results found": "未找到搜尋結果",
|
"No search results found": "未找到搜尋結果",
|
||||||
"Added @c comics to download queue." : "已添加 @c 本漫畫到下載佇列",
|
"Added @c comics to download queue.": "已添加 @c 本漫畫到下載佇列",
|
||||||
"Download started": "下載已開始",
|
"Download started": "下載已開始",
|
||||||
"Click favorite": "點擊收藏",
|
"Click favorite": "點擊收藏",
|
||||||
"Local comic collection is not supported at present": "本機收藏暫不支援",
|
"Local comic collection is not supported at present": "本機收藏暫不支援",
|
||||||
@@ -675,9 +684,9 @@
|
|||||||
"End": "末尾",
|
"End": "末尾",
|
||||||
"None": "無",
|
"None": "無",
|
||||||
"View Detail": "查看詳情",
|
"View Detail": "查看詳情",
|
||||||
"Select a directory which contains multiple archive files." : "選擇一個包含多個歸檔文件的目錄",
|
"Select a directory which contains multiple archive files.": "選擇一個包含多個歸檔文件的目錄",
|
||||||
"Multiple archive files" : "多個歸檔文件",
|
"Multiple archive files": "多個歸檔文件",
|
||||||
"No valid comics found" : "未找到有效的漫畫",
|
"No valid comics found": "未找到有效的漫畫",
|
||||||
"Enable DNS Overrides": "啟用DNS覆寫",
|
"Enable DNS Overrides": "啟用DNS覆寫",
|
||||||
"DNS Overrides": "DNS覆寫",
|
"DNS Overrides": "DNS覆寫",
|
||||||
"Custom Image Processing": "自訂圖片處理",
|
"Custom Image Processing": "自訂圖片處理",
|
||||||
@@ -723,12 +732,12 @@
|
|||||||
"Replies": "回覆",
|
"Replies": "回覆",
|
||||||
"Follow Updates": "追更",
|
"Follow Updates": "追更",
|
||||||
"Not Configured": "未配置",
|
"Not Configured": "未配置",
|
||||||
"Choose a folder to follow updates." : "選擇一個資料夾以追更",
|
"Choose a folder to follow updates.": "選擇一個資料夾以追更",
|
||||||
"Choose Folder": "選擇資料夾",
|
"Choose Folder": "選擇資料夾",
|
||||||
"No folders available": "沒有可用的資料夾",
|
"No folders available": "沒有可用的資料夾",
|
||||||
"Updating comics...": "更新漫畫中...",
|
"Updating comics...": "更新漫畫中...",
|
||||||
"Automatic update checking enabled." : "已啟用自動更新檢查",
|
"Automatic update checking enabled.": "已啟用自動更新檢查",
|
||||||
"The app will check for updates at most once a day." : "APP將每天最多檢查一次更新",
|
"The app will check for updates at most once a day.": "APP將每天最多檢查一次更新",
|
||||||
"Change Folder": "更改資料夾",
|
"Change Folder": "更改資料夾",
|
||||||
"Check Now": "立即檢查",
|
"Check Now": "立即檢查",
|
||||||
"Updates": "更新",
|
"Updates": "更新",
|
||||||
@@ -741,7 +750,7 @@
|
|||||||
"Disabled": "已停用",
|
"Disabled": "已停用",
|
||||||
"Auto Sync Data": "自動同步資料",
|
"Auto Sync Data": "自動同步資料",
|
||||||
"Mark all as read": "全部標記為已讀",
|
"Mark all as read": "全部標記為已讀",
|
||||||
"Do you want to mark all as read?" : "您要全部標記為已讀嗎?",
|
"Do you want to mark all as read?": "您要全部標記為已讀嗎?",
|
||||||
"Swipe down for previous chapter": "向下滑動查看上一章",
|
"Swipe down for previous chapter": "向下滑動查看上一章",
|
||||||
"Swipe up for next chapter": "向上滑動查看下一章",
|
"Swipe up for next chapter": "向上滑動查看下一章",
|
||||||
"Initial Page": "初始頁面",
|
"Initial Page": "初始頁面",
|
||||||
@@ -759,6 +768,15 @@
|
|||||||
"Page": "頁面",
|
"Page": "頁面",
|
||||||
"Jump": "跳轉",
|
"Jump": "跳轉",
|
||||||
"Copy Image": "複製圖片",
|
"Copy Image": "複製圖片",
|
||||||
"A valid WebDav directory URL": "有效的WebDav目錄URL"
|
"A valid WebDav directory URL": "有效的WebDav目錄URL",
|
||||||
|
"Shut Down": "關閉",
|
||||||
|
"Uploading data...": "正在上傳數據...",
|
||||||
|
"Pages": "頁數",
|
||||||
|
"Long press zoom position": "長按縮放位置",
|
||||||
|
"Press position": "按壓位置",
|
||||||
|
"Screen center": "螢幕中心",
|
||||||
|
"Suggestions": "建議",
|
||||||
|
"Do not report any issues related to sources to App repo.": "請不要向App倉庫報告任何與源相關的問題",
|
||||||
|
"Click the setting icon to change the source list url.": "點擊設定圖示更改源列表URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
debian/gui/venera.png
vendored
BIN
debian/gui/venera.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 64 KiB |
@@ -334,7 +334,12 @@ class ComicTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var children = <Widget>[];
|
var children = <Widget>[];
|
||||||
for (var line in text.split('\n')) {
|
var lines = text.split('\n');
|
||||||
|
lines.removeWhere((e) => e.trim().isEmpty);
|
||||||
|
if (lines.length > 3) {
|
||||||
|
lines = lines.sublist(0, 3);
|
||||||
|
}
|
||||||
|
for (var line in lines) {
|
||||||
children.add(Container(
|
children.add(Container(
|
||||||
margin: const EdgeInsets.fromLTRB(2, 0, 2, 2),
|
margin: const EdgeInsets.fromLTRB(2, 0, 2, 2),
|
||||||
padding: constraints.maxWidth < 80
|
padding: constraints.maxWidth < 80
|
||||||
|
@@ -163,3 +163,29 @@ class SliverLazyToBoxAdapter extends StatelessWidget {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SliverAnimatedVisibility extends StatelessWidget {
|
||||||
|
const SliverAnimatedVisibility({
|
||||||
|
super.key,
|
||||||
|
required this.visible,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool visible;
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var child = visible ? this.child : const SizedBox.shrink();
|
||||||
|
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -51,10 +51,32 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
|
|
||||||
static bool _isMouseScroll = App.isDesktop;
|
static bool _isMouseScroll = App.isDesktop;
|
||||||
|
|
||||||
|
late int id;
|
||||||
|
|
||||||
|
static int _id = 0;
|
||||||
|
|
||||||
|
var activeChildren = <int>{};
|
||||||
|
|
||||||
|
ScrollState? parent;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_controller = widget.controller ?? ScrollController();
|
_controller = widget.controller ?? ScrollController();
|
||||||
super.initState();
|
super.initState();
|
||||||
|
id = _id;
|
||||||
|
_id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
parent = ScrollState.maybeOf(context);
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
parent?.onChildInactive(id);
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -66,8 +88,7 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
const BouncingScrollPhysics(),
|
const BouncingScrollPhysics(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Listener(
|
var child = Listener(
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onPointerDown: (event) {
|
onPointerDown: (event) {
|
||||||
_futurePosition = null;
|
_futurePosition = null;
|
||||||
if (_isMouseScroll) {
|
if (_isMouseScroll) {
|
||||||
@@ -77,6 +98,9 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPointerSignal: (pointerSignal) {
|
onPointerSignal: (pointerSignal) {
|
||||||
|
if (activeChildren.isNotEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (pointerSignal is PointerScrollEvent) {
|
if (pointerSignal is PointerScrollEvent) {
|
||||||
if (HardwareKeyboard.instance.isShiftPressed) {
|
if (HardwareKeyboard.instance.isShiftPressed) {
|
||||||
return;
|
return;
|
||||||
@@ -113,8 +137,14 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ScrollControllerProvider._(
|
child: ScrollState._(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
|
onChildActive: (id) {
|
||||||
|
activeChildren.add(id);
|
||||||
|
},
|
||||||
|
onChildInactive: (id) {
|
||||||
|
activeChildren.remove(id);
|
||||||
|
},
|
||||||
child: widget.builder(
|
child: widget.builder(
|
||||||
context,
|
context,
|
||||||
_controller,
|
_controller,
|
||||||
@@ -124,25 +154,49 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (parent != null) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) {
|
||||||
|
parent!.onChildActive(id);
|
||||||
|
},
|
||||||
|
onExit: (_) {
|
||||||
|
parent!.onChildInactive(id);
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScrollControllerProvider extends InheritedWidget {
|
class ScrollState extends InheritedWidget {
|
||||||
const ScrollControllerProvider._({
|
const ScrollState._({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required super.child,
|
required super.child,
|
||||||
|
required this.onChildActive,
|
||||||
|
required this.onChildInactive,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ScrollController controller;
|
final ScrollController controller;
|
||||||
|
|
||||||
static ScrollController of(BuildContext context) {
|
final void Function(int id) onChildActive;
|
||||||
final ScrollControllerProvider? provider =
|
|
||||||
context.dependOnInheritedWidgetOfExactType<ScrollControllerProvider>();
|
final void Function(int id) onChildInactive;
|
||||||
return provider!.controller;
|
|
||||||
|
static ScrollState of(BuildContext context) {
|
||||||
|
final ScrollState? provider =
|
||||||
|
context.dependOnInheritedWidgetOfExactType<ScrollState>();
|
||||||
|
return provider!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ScrollState? maybeOf(BuildContext context) {
|
||||||
|
return context.dependOnInheritedWidgetOfExactType<ScrollState>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ScrollControllerProvider oldWidget) {
|
bool updateShouldNotify(ScrollState oldWidget) {
|
||||||
return oldWidget.controller != controller;
|
return oldWidget.controller != controller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -82,10 +82,7 @@ class _WindowFrameState extends State<WindowFrame> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
windowManager.close().then((_) {
|
|
||||||
// Make sure the app exits when the window is closed.
|
|
||||||
exit(0);
|
exit(0);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -570,7 +567,6 @@ class _VirtualWindowFrameState extends State<VirtualWindowFrame>
|
|||||||
boxShadow: <BoxShadow>[
|
boxShadow: <BoxShadow>[
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.toOpacity(_isFocused ? 0.4 : 0.2),
|
color: Colors.black.toOpacity(_isFocused ? 0.4 : 0.2),
|
||||||
offset: Offset(0.0, 2),
|
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@@ -13,7 +13,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.3.4";
|
final version = "1.4.0";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ class _App {
|
|||||||
|
|
||||||
late String dataPath;
|
late String dataPath;
|
||||||
late String cachePath;
|
late String cachePath;
|
||||||
|
String? externalStoragePath;
|
||||||
|
|
||||||
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@@ -77,6 +78,9 @@ class _App {
|
|||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
cachePath = (await getApplicationCacheDirectory()).path;
|
cachePath = (await getApplicationCacheDirectory()).path;
|
||||||
dataPath = (await getApplicationSupportDirectory()).path;
|
dataPath = (await getApplicationSupportDirectory()).path;
|
||||||
|
if (isAndroid) {
|
||||||
|
externalStoragePath = (await getExternalStorageDirectory())!.path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initComponents() async {
|
Future<void> initComponents() async {
|
||||||
|
@@ -161,6 +161,7 @@ class Settings with ChangeNotifier {
|
|||||||
'cacheSize': 2048, // in MB
|
'cacheSize': 2048, // in MB
|
||||||
'downloadThreads': 5,
|
'downloadThreads': 5,
|
||||||
'enableLongPressToZoom': true,
|
'enableLongPressToZoom': true,
|
||||||
|
'longPressZoomPosition': "press", // press, center
|
||||||
'checkUpdateOnStart': false,
|
'checkUpdateOnStart': false,
|
||||||
'limitImageWidth': true,
|
'limitImageWidth': true,
|
||||||
'webdav': [], // empty means not configured
|
'webdav': [], // empty means not configured
|
||||||
|
@@ -34,24 +34,28 @@ class CategoryButtonData {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CategoryItem {
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
final PageJumpTarget target;
|
||||||
|
|
||||||
|
const CategoryItem(this.label, this.target);
|
||||||
|
}
|
||||||
|
|
||||||
abstract class BaseCategoryPart {
|
abstract class BaseCategoryPart {
|
||||||
String get title;
|
String get title;
|
||||||
|
|
||||||
List<String> get categories;
|
List<CategoryItem> get categories;
|
||||||
|
|
||||||
List<String>? get categoryParams => null;
|
|
||||||
|
|
||||||
bool get enableRandom;
|
bool get enableRandom;
|
||||||
|
|
||||||
String get categoryType;
|
|
||||||
|
|
||||||
/// Data class for building a part of category page.
|
/// Data class for building a part of category page.
|
||||||
const BaseCategoryPart();
|
const BaseCategoryPart();
|
||||||
}
|
}
|
||||||
|
|
||||||
class FixedCategoryPart extends BaseCategoryPart {
|
class FixedCategoryPart extends BaseCategoryPart {
|
||||||
@override
|
@override
|
||||||
final List<String> categories;
|
final List<CategoryItem> categories;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get enableRandom => false;
|
bool get enableRandom => false;
|
||||||
@@ -59,19 +63,12 @@ class FixedCategoryPart extends BaseCategoryPart {
|
|||||||
@override
|
@override
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
@override
|
|
||||||
final String categoryType;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final List<String>? categoryParams;
|
|
||||||
|
|
||||||
/// A [BaseCategoryPart] that show fixed tags on category page.
|
/// A [BaseCategoryPart] that show fixed tags on category page.
|
||||||
const FixedCategoryPart(this.title, this.categories, this.categoryType,
|
const FixedCategoryPart(this.title, this.categories);
|
||||||
[this.categoryParams]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RandomCategoryPart extends BaseCategoryPart {
|
class RandomCategoryPart extends BaseCategoryPart {
|
||||||
final List<String> tags;
|
final List<CategoryItem> all;
|
||||||
|
|
||||||
final int randomNumber;
|
final int randomNumber;
|
||||||
|
|
||||||
@@ -81,67 +78,59 @@ class RandomCategoryPart extends BaseCategoryPart {
|
|||||||
@override
|
@override
|
||||||
bool get enableRandom => true;
|
bool get enableRandom => true;
|
||||||
|
|
||||||
@override
|
List<CategoryItem> _categories() {
|
||||||
final String categoryType;
|
if (randomNumber >= all.length) {
|
||||||
|
return all;
|
||||||
List<String> _categories() {
|
|
||||||
if (randomNumber >= tags.length) {
|
|
||||||
return tags;
|
|
||||||
}
|
}
|
||||||
var start = math.Random().nextInt(tags.length - randomNumber);
|
var start = math.Random().nextInt(all.length - randomNumber);
|
||||||
return tags.sublist(start, start + randomNumber);
|
return all.sublist(start, start + randomNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get categories => _categories();
|
List<CategoryItem> get categories => _categories();
|
||||||
|
|
||||||
/// A [BaseCategoryPart] that show random tags on category page.
|
/// A [BaseCategoryPart] that show a part of random tags on category page.
|
||||||
const RandomCategoryPart(
|
const RandomCategoryPart(
|
||||||
this.title, this.tags, this.randomNumber, this.categoryType);
|
this.title,
|
||||||
|
this.all,
|
||||||
|
this.randomNumber,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RandomCategoryPartWithRuntimeData extends BaseCategoryPart {
|
class DynamicCategoryPart extends BaseCategoryPart {
|
||||||
final Iterable<String> Function() loadTags;
|
final JSAutoFreeFunction loader;
|
||||||
|
|
||||||
final int randomNumber;
|
final String sourceKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String title;
|
List<CategoryItem> get categories {
|
||||||
|
var data = loader([]);
|
||||||
@override
|
if (data is! List) {
|
||||||
bool get enableRandom => true;
|
throw "DynamicCategoryPart loader must return a List";
|
||||||
|
|
||||||
@override
|
|
||||||
final String categoryType;
|
|
||||||
|
|
||||||
static final random = math.Random();
|
|
||||||
|
|
||||||
List<String> _categories() {
|
|
||||||
var tags = loadTags();
|
|
||||||
if (randomNumber >= tags.length) {
|
|
||||||
return tags.toList();
|
|
||||||
}
|
}
|
||||||
final start = random.nextInt(tags.length - randomNumber);
|
var res = <CategoryItem>[];
|
||||||
var res = List.filled(randomNumber, '');
|
for (var item in data) {
|
||||||
int index = -1;
|
if (item is! Map) {
|
||||||
for (var s in tags) {
|
throw "DynamicCategoryPart loader must return a List of Map";
|
||||||
index++;
|
|
||||||
if (start > index) {
|
|
||||||
continue;
|
|
||||||
} else if (index == start + randomNumber) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
res[index - start] = s;
|
var label = item['label'];
|
||||||
|
var target = PageJumpTarget.parse(sourceKey, item['target']);
|
||||||
|
if (label is! String) {
|
||||||
|
throw "Category label must be a String";
|
||||||
|
}
|
||||||
|
res.add(CategoryItem(label, target));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get categories => _categories();
|
bool get enableRandom => false;
|
||||||
|
|
||||||
/// A [BaseCategoryPart] that show random tags on category page.
|
@override
|
||||||
RandomCategoryPartWithRuntimeData(
|
final String title;
|
||||||
this.title, this.loadTags, this.randomNumber, this.categoryType);
|
|
||||||
|
/// A [BaseCategoryPart] that show dynamic tags on category page.
|
||||||
|
const DynamicCategoryPart(this.title, this.loader, this.sourceKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
CategoryData getCategoryDataWithKey(String key) {
|
CategoryData getCategoryDataWithKey(String key) {
|
||||||
|
@@ -11,6 +11,8 @@ import 'package:venera/foundation/app.dart';
|
|||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
import 'package:venera/foundation/history.dart';
|
import 'package:venera/foundation/history.dart';
|
||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
|
import 'package:venera/pages/category_comics_page.dart';
|
||||||
|
import 'package:venera/pages/search_result_page.dart';
|
||||||
import 'package:venera/utils/data_sync.dart';
|
import 'package:venera/utils/data_sync.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:venera/utils/init.dart';
|
import 'package:venera/utils/init.dart';
|
||||||
@@ -349,7 +351,7 @@ class ExplorePagePart {
|
|||||||
/// - category:categoryName
|
/// - category:categoryName
|
||||||
///
|
///
|
||||||
/// End with `@`+`param` if the category has a parameter.
|
/// End with `@`+`param` if the category has a parameter.
|
||||||
final String? viewMore;
|
final PageJumpTarget? viewMore;
|
||||||
|
|
||||||
const ExplorePagePart(this.title, this.comics, this.viewMore);
|
const ExplorePagePart(this.title, this.comics, this.viewMore);
|
||||||
}
|
}
|
||||||
|
@@ -169,7 +169,9 @@ class ComicDetails with HistoryMixin {
|
|||||||
static Map<String, List<String>> _generateMap(Map<dynamic, dynamic> map) {
|
static Map<String, List<String>> _generateMap(Map<dynamic, dynamic> map) {
|
||||||
var res = <String, List<String>>{};
|
var res = <String, List<String>>{};
|
||||||
map.forEach((key, value) {
|
map.forEach((key, value) {
|
||||||
|
if (value is List) {
|
||||||
res[key] = List<String>.from(value);
|
res[key] = List<String>.from(value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -342,7 +344,8 @@ class ComicChapters {
|
|||||||
} else if (groupedChapters.isNotEmpty) {
|
} else if (groupedChapters.isNotEmpty) {
|
||||||
return ComicChapters.grouped(groupedChapters);
|
return ComicChapters.grouped(groupedChapters);
|
||||||
} else {
|
} else {
|
||||||
throw ArgumentError("Empty chapter list");
|
// return a empty list.
|
||||||
|
return ComicChapters(chapters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,3 +432,110 @@ class ComicChapters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PageJumpTarget {
|
||||||
|
final String sourceKey;
|
||||||
|
|
||||||
|
final String page;
|
||||||
|
|
||||||
|
final Map<String, dynamic>? attributes;
|
||||||
|
|
||||||
|
const PageJumpTarget(this.sourceKey, this.page, this.attributes);
|
||||||
|
|
||||||
|
static PageJumpTarget parse(String sourceKey, dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
if (value['page'] != null) {
|
||||||
|
return PageJumpTarget(
|
||||||
|
sourceKey,
|
||||||
|
value["page"] ?? "search",
|
||||||
|
value["attributes"],
|
||||||
|
);
|
||||||
|
} else if (value["action"] != null) {
|
||||||
|
// old version `onClickTag`
|
||||||
|
var page = value["action"];
|
||||||
|
if (page == "search") {
|
||||||
|
return PageJumpTarget(
|
||||||
|
sourceKey,
|
||||||
|
"search",
|
||||||
|
{
|
||||||
|
"text": value["keyword"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (page == "category") {
|
||||||
|
return PageJumpTarget(
|
||||||
|
sourceKey,
|
||||||
|
"category",
|
||||||
|
{
|
||||||
|
"category": value["keyword"],
|
||||||
|
"param": value["param"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return PageJumpTarget(sourceKey, page, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (value is String) {
|
||||||
|
// old version string encoding. search: `search:keyword`, category: `category:keyword` or `category:keyword@param`
|
||||||
|
var segments = value.split(":");
|
||||||
|
var page = segments[0];
|
||||||
|
if (page == "search") {
|
||||||
|
return PageJumpTarget(
|
||||||
|
sourceKey,
|
||||||
|
"search",
|
||||||
|
{
|
||||||
|
"text": segments[1],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (page == "category") {
|
||||||
|
var c = segments[1];
|
||||||
|
if (c.contains('@')) {
|
||||||
|
var parts = c.split('@');
|
||||||
|
return PageJumpTarget(
|
||||||
|
sourceKey,
|
||||||
|
"category",
|
||||||
|
{
|
||||||
|
"category": parts[0],
|
||||||
|
"param": parts[1],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return PageJumpTarget(
|
||||||
|
sourceKey,
|
||||||
|
"category",
|
||||||
|
{
|
||||||
|
"category": c,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return PageJumpTarget(sourceKey, page, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PageJumpTarget(sourceKey, "Invalid Data", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void jump(BuildContext context) {
|
||||||
|
if (page == "search") {
|
||||||
|
context.to(
|
||||||
|
() => SearchResultPage(
|
||||||
|
text: attributes?["text"] ?? attributes?["keyword"] ?? "",
|
||||||
|
sourceKey: sourceKey,
|
||||||
|
options: List.from(attributes?["options"] ?? []),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (page == "category") {
|
||||||
|
var key = ComicSource.find(sourceKey)!.categoryData!.key;
|
||||||
|
context.to(
|
||||||
|
() => CategoryComicsPage(
|
||||||
|
categoryKey: key,
|
||||||
|
category: attributes?["category"] ??
|
||||||
|
(throw ArgumentError("Category name is required")),
|
||||||
|
options: List.from(attributes?["options"] ?? []),
|
||||||
|
param: attributes?["param"],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Log.error("Page Jump", "Unknown page: $page");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -80,9 +80,8 @@ class ComicSourceParser {
|
|||||||
|
|
||||||
Future<ComicSource> parse(String js, String filePath) async {
|
Future<ComicSource> parse(String js, String filePath) async {
|
||||||
js = js.replaceAll("\r\n", "\n");
|
js = js.replaceAll("\r\n", "\n");
|
||||||
var line1 = js
|
var line1 =
|
||||||
.split('\n')
|
js.split('\n').firstWhereOrNull((e) => e.trim().startsWith("class "));
|
||||||
.firstWhereOrNull((e) => e.trim().startsWith("class "));
|
|
||||||
if (line1 == null ||
|
if (line1 == null ||
|
||||||
!line1.startsWith("class ") ||
|
!line1.startsWith("class ") ||
|
||||||
!line1.contains("extends ComicSource")) {
|
!line1.contains("extends ComicSource")) {
|
||||||
@@ -336,7 +335,7 @@ class ComicSourceParser {
|
|||||||
(e['comics'] as List).map((e) {
|
(e['comics'] as List).map((e) {
|
||||||
return Comic.fromJson(e, _key!);
|
return Comic.fromJson(e, _key!);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
e['viewMore'],
|
PageJumpTarget.parse(_key!, e['viewMore']),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -404,6 +403,43 @@ class ComicSourceParser {
|
|||||||
var categoryParts = <BaseCategoryPart>[];
|
var categoryParts = <BaseCategoryPart>[];
|
||||||
|
|
||||||
for (var c in doc["parts"]) {
|
for (var c in doc["parts"]) {
|
||||||
|
if (c["categories"] != null && c["categories"] is! List) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List? categories = c["categories"];
|
||||||
|
if (categories == null || categories[0] is Map) {
|
||||||
|
// new format
|
||||||
|
final String name = c["name"];
|
||||||
|
final String type = c["type"];
|
||||||
|
final cs = categories
|
||||||
|
?.map(
|
||||||
|
(e) => CategoryItem(
|
||||||
|
e['label'],
|
||||||
|
PageJumpTarget.parse(_key!, e['target']),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (type != "dynamic" && (cs == null || cs.isEmpty)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (type == "fixed") {
|
||||||
|
categoryParts.add(FixedCategoryPart(name, cs!));
|
||||||
|
} else if (type == "random") {
|
||||||
|
categoryParts
|
||||||
|
.add(RandomCategoryPart(name, cs!, c["randomNumber"] ?? 1));
|
||||||
|
} else if (type == "dynamic" && categories == null) {
|
||||||
|
var loader = c["loader"];
|
||||||
|
if (loader is! JSInvokable) {
|
||||||
|
throw "DynamicCategoryPart loader must be a function";
|
||||||
|
}
|
||||||
|
categoryParts.add(DynamicCategoryPart(
|
||||||
|
name,
|
||||||
|
JSAutoFreeFunction(loader),
|
||||||
|
_key!,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// old format
|
||||||
final String name = c["name"];
|
final String name = c["name"];
|
||||||
final String type = c["type"];
|
final String type = c["type"];
|
||||||
final List<String> tags = List.from(c["categories"]);
|
final List<String> tags = List.from(c["categories"]);
|
||||||
@@ -413,12 +449,45 @@ class ComicSourceParser {
|
|||||||
if (groupParam != null) {
|
if (groupParam != null) {
|
||||||
categoryParams = List.filled(tags.length, groupParam);
|
categoryParams = List.filled(tags.length, groupParam);
|
||||||
}
|
}
|
||||||
|
var cs = <CategoryItem>[];
|
||||||
|
for (int i = 0; i < tags.length; i++) {
|
||||||
|
PageJumpTarget target;
|
||||||
|
if (itemType == 'category') {
|
||||||
|
target = PageJumpTarget(
|
||||||
|
_key!,
|
||||||
|
'category',
|
||||||
|
{
|
||||||
|
"category": tags[i],
|
||||||
|
"param": categoryParams?.elementAtOrNull(i),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (itemType == 'search') {
|
||||||
|
target = PageJumpTarget(
|
||||||
|
_key!,
|
||||||
|
'search',
|
||||||
|
{
|
||||||
|
"keyword": tags[i],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (itemType == 'search_with_namespace') {
|
||||||
|
target = PageJumpTarget(
|
||||||
|
_key!,
|
||||||
|
'search',
|
||||||
|
{
|
||||||
|
"keyword": "$name:$tags[i]",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
target = PageJumpTarget(_key!, itemType, null);
|
||||||
|
}
|
||||||
|
cs.add(CategoryItem(tags[i], target));
|
||||||
|
}
|
||||||
if (type == "fixed") {
|
if (type == "fixed") {
|
||||||
categoryParts
|
categoryParts.add(FixedCategoryPart(name, cs));
|
||||||
.add(FixedCategoryPart(name, tags, itemType, categoryParams));
|
|
||||||
} else if (type == "random") {
|
} else if (type == "random") {
|
||||||
categoryParts.add(
|
categoryParts
|
||||||
RandomCategoryPart(name, tags, c["randomNumber"] ?? 1, itemType));
|
.add(RandomCategoryPart(name, cs, c["randomNumber"] ?? 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,7 +689,8 @@ class ComicSourceParser {
|
|||||||
|
|
||||||
final bool multiFolder = _getValue("favorites.multiFolder");
|
final bool multiFolder = _getValue("favorites.multiFolder");
|
||||||
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
|
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
|
||||||
final bool? singleFolderForSingleComic = _getValue("favorites.singleFolderForSingleComic");
|
final bool? singleFolderForSingleComic =
|
||||||
|
_getValue("favorites.singleFolderForSingleComic");
|
||||||
|
|
||||||
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
|
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
|
||||||
if (!ComicSource.find(_key!)!.isLogged) {
|
if (!ComicSource.find(_key!)!.isLogged) {
|
||||||
@@ -978,9 +1048,12 @@ class ComicSourceParser {
|
|||||||
var res = JsEngine().runCode("""
|
var res = JsEngine().runCode("""
|
||||||
ComicSource.sources.$_key.comic.onClickTag(${jsonEncode(namespace)}, ${jsonEncode(tag)})
|
ComicSource.sources.$_key.comic.onClickTag(${jsonEncode(namespace)}, ${jsonEncode(tag)})
|
||||||
""");
|
""");
|
||||||
var r = Map<String, String?>.from(res);
|
if (res is! Map) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var r = Map<String, dynamic>.from(res);
|
||||||
r.removeWhere((key, value) => value == null);
|
r.removeWhere((key, value) => value == null);
|
||||||
return Map.from(r);
|
return PageJumpTarget.parse(_key!, r);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,7 +41,7 @@ typedef LikeCommentFunc = Future<Res<int?>> Function(
|
|||||||
typedef VoteCommentFunc = Future<Res<int?>> Function(
|
typedef VoteCommentFunc = Future<Res<int?>> Function(
|
||||||
String comicId, String? subId, String commentId, bool isUp, bool isCancel);
|
String comicId, String? subId, String commentId, bool isUp, bool isCancel);
|
||||||
|
|
||||||
typedef HandleClickTagEvent = Map<String, String> Function(
|
typedef HandleClickTagEvent = PageJumpTarget? Function(
|
||||||
String namespace, String tag);
|
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.
|
||||||
|
@@ -461,6 +461,10 @@ class LocalManager with ChangeNotifier {
|
|||||||
if (comic != null) {
|
if (comic != null) {
|
||||||
return Directory(FilePath.join(path, comic.directory));
|
return Directory(FilePath.join(path, comic.directory));
|
||||||
}
|
}
|
||||||
|
const comicDirectoryMaxLength = 128;
|
||||||
|
if (name.length > comicDirectoryMaxLength) {
|
||||||
|
name = name.substring(0, comicDirectoryMaxLength);
|
||||||
|
}
|
||||||
var dir = findValidDirectoryName(path, name);
|
var dir = findValidDirectoryName(path, name);
|
||||||
return Directory(FilePath.join(path, dir)).create().then((value) => value);
|
return Directory(FilePath.join(path, dir)).create().then((value) => value);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
|
import 'package:venera/utils/io.dart';
|
||||||
|
|
||||||
class LogItem {
|
class LogItem {
|
||||||
final LogLevel level;
|
final LogLevel level;
|
||||||
@@ -28,9 +28,6 @@ class Log {
|
|||||||
|
|
||||||
static bool ignoreLimitation = false;
|
static bool ignoreLimitation = false;
|
||||||
|
|
||||||
/// only for debug
|
|
||||||
static const String? logFile = null;
|
|
||||||
|
|
||||||
static void printWarning(String text) {
|
static void printWarning(String text) {
|
||||||
debugPrint('\x1B[33m$text\x1B[0m');
|
debugPrint('\x1B[33m$text\x1B[0m');
|
||||||
}
|
}
|
||||||
@@ -39,7 +36,20 @@ class Log {
|
|||||||
debugPrint('\x1B[31m$text\x1B[0m');
|
debugPrint('\x1B[31m$text\x1B[0m');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IOSink? _file;
|
||||||
|
|
||||||
static void addLog(LogLevel level, String title, String content) {
|
static void addLog(LogLevel level, String title, String content) {
|
||||||
|
if (_file == null) {
|
||||||
|
Directory dir;
|
||||||
|
if (App.isAndroid) {
|
||||||
|
dir = Directory(App.externalStoragePath!);
|
||||||
|
} else {
|
||||||
|
dir = Directory(App.dataPath);
|
||||||
|
}
|
||||||
|
var file = dir.joinFile("logs.txt");
|
||||||
|
_file = file.openWrite();
|
||||||
|
}
|
||||||
|
|
||||||
if (!ignoreLimitation && content.length > maxLogLength) {
|
if (!ignoreLimitation && content.length > maxLogLength) {
|
||||||
content = "${content.substring(0, maxLogLength)}...";
|
content = "${content.substring(0, maxLogLength)}...";
|
||||||
}
|
}
|
||||||
@@ -62,8 +72,8 @@ class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logs.add(newLog);
|
_logs.add(newLog);
|
||||||
if(logFile != null) {
|
if(_file != null) {
|
||||||
File(logFile!).writeAsString(newLog.toString(), mode: FileMode.append);
|
_file!.write(newLog.toString());
|
||||||
}
|
}
|
||||||
if (_logs.length > maxLogNumber) {
|
if (_logs.length > maxLogNumber) {
|
||||||
var res = _logs.remove(
|
var res = _logs.remove(
|
||||||
|
@@ -35,8 +35,14 @@ void main(List<String> args) {
|
|||||||
}
|
}
|
||||||
await windowManager.setMinimumSize(const Size(500, 600));
|
await windowManager.setMinimumSize(const Size(500, 600));
|
||||||
var placement = await WindowPlacement.loadFromFile();
|
var placement = await WindowPlacement.loadFromFile();
|
||||||
|
if (App.isLinux) {
|
||||||
|
await windowManager.show();
|
||||||
|
await placement.applyToWindow();
|
||||||
|
} else {
|
||||||
await placement.applyToWindow();
|
await placement.applyToWindow();
|
||||||
await windowManager.show();
|
await windowManager.show();
|
||||||
|
}
|
||||||
|
|
||||||
WindowPlacement.loop();
|
WindowPlacement.loop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -482,7 +482,7 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
chapters: comic!.chapters,
|
chapters: comic!.chapters,
|
||||||
cover: File(_cover!.split("file://").last).name,
|
cover: File(_cover!.split("file://").last).name,
|
||||||
comicType: ComicType(source.key.hashCode),
|
comicType: ComicType(source.key.hashCode),
|
||||||
downloadedChapters: chapters ?? [],
|
downloadedChapters: chapters ?? comic?.chapters?.ids.toList() ?? [],
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -4,12 +4,10 @@ import 'package:venera/foundation/app.dart';
|
|||||||
import 'package:venera/foundation/appdata.dart';
|
import 'package:venera/foundation/appdata.dart';
|
||||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
import 'package:venera/pages/ranking_page.dart';
|
import 'package:venera/pages/ranking_page.dart';
|
||||||
import 'package:venera/pages/search_result_page.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/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
import 'category_comics_page.dart';
|
|
||||||
import 'comic_source_page.dart';
|
import 'comic_source_page.dart';
|
||||||
|
|
||||||
class CategoriesPage extends StatefulWidget {
|
class CategoriesPage extends StatefulWidget {
|
||||||
@@ -147,43 +145,6 @@ class _CategoryPage extends StatelessWidget {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleClick(
|
|
||||||
String tag,
|
|
||||||
String? param,
|
|
||||||
String type,
|
|
||||||
String namespace,
|
|
||||||
String categoryKey,
|
|
||||||
) {
|
|
||||||
if (type == 'search') {
|
|
||||||
App.mainNavigatorKey?.currentContext?.to(
|
|
||||||
() => SearchResultPage(
|
|
||||||
text: tag,
|
|
||||||
options: const [],
|
|
||||||
sourceKey: findComicSourceKey(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (type == "search_with_namespace") {
|
|
||||||
if (tag.contains(" ")) {
|
|
||||||
tag = '"$tag"';
|
|
||||||
}
|
|
||||||
App.mainNavigatorKey?.currentContext?.to(
|
|
||||||
() => SearchResultPage(
|
|
||||||
text: "$namespace:$tag",
|
|
||||||
options: const [],
|
|
||||||
sourceKey: findComicSourceKey(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (type == "category") {
|
|
||||||
App.mainNavigatorKey!.currentContext!.to(
|
|
||||||
() => CategoryComicsPage(
|
|
||||||
category: tag,
|
|
||||||
categoryKey: categoryKey,
|
|
||||||
param: param,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var children = <Widget>[];
|
var children = <Widget>[];
|
||||||
@@ -194,11 +155,11 @@ class _CategoryPage extends StatelessWidget {
|
|||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: [
|
children: [
|
||||||
if (data.enableRankingPage)
|
if (data.enableRankingPage)
|
||||||
buildTag("Ranking".tl, (p0, p1) {
|
buildTag("Ranking".tl, () {
|
||||||
context.to(() => RankingPage(categoryKey: data.key));
|
context.to(() => RankingPage(categoryKey: data.key));
|
||||||
}),
|
}),
|
||||||
for (var buttonData in data.buttons)
|
for (var buttonData in data.buttons)
|
||||||
buildTag(buttonData.label.tl, (p0, p1) => buttonData.onTap())
|
buildTag(buttonData.label.tl, buttonData.onTap)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
@@ -212,36 +173,14 @@ class _CategoryPage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildTitleWithRefresh(part.title, () => updater(() {})),
|
buildTitleWithRefresh(part.title, () => updater(() {})),
|
||||||
buildTagsWithParams(
|
buildTags(part.categories)
|
||||||
part.categories,
|
|
||||||
part.categoryParams,
|
|
||||||
part.title,
|
|
||||||
(key, param) => handleClick(
|
|
||||||
key,
|
|
||||||
param,
|
|
||||||
part.categoryType,
|
|
||||||
part.title,
|
|
||||||
category,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
children.add(buildTitle(part.title));
|
children.add(buildTitle(part.title));
|
||||||
children.add(
|
children.add(
|
||||||
buildTagsWithParams(
|
buildTags(part.categories),
|
||||||
part.categories,
|
|
||||||
part.categoryParams,
|
|
||||||
part.title,
|
|
||||||
(tag, param) => handleClick(
|
|
||||||
tag,
|
|
||||||
param,
|
|
||||||
part.categoryType,
|
|
||||||
part.title,
|
|
||||||
data.key,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,30 +219,28 @@ class _CategoryPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildTagsWithParams(
|
Widget buildTags(
|
||||||
List<String> tags,
|
List<CategoryItem> categories,
|
||||||
List<String>? params,
|
|
||||||
String? namespace,
|
|
||||||
ClickTagCallback onClick,
|
|
||||||
) {
|
) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
|
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: List<Widget>.generate(
|
children: List<Widget>.generate(
|
||||||
tags.length,
|
categories.length,
|
||||||
(index) => buildTag(
|
(index) => buildCategory(categories[index]),
|
||||||
tags[index],
|
|
||||||
onClick,
|
|
||||||
namespace,
|
|
||||||
params?.elementAtOrNull(index),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildTag(String tag, ClickTagCallback onClick,
|
Widget buildCategory(CategoryItem c) {
|
||||||
[String? namespace, String? param]) {
|
return buildTag(c.label, () {
|
||||||
|
var context = App.mainNavigatorKey!.currentContext!;
|
||||||
|
c.target.jump(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTag(String label, VoidCallback onClick) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 6, 8, 6),
|
padding: const EdgeInsets.fromLTRB(8, 6, 8, 6),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
@@ -313,10 +250,10 @@ class _CategoryPage extends StatelessWidget {
|
|||||||
color: context.colorScheme.primaryContainer.toOpacity(0.72),
|
color: context.colorScheme.primaryContainer.toOpacity(0.72),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
onTap: () => onClick(tag, param),
|
onTap: onClick,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||||
child: Text(tag),
|
child: Text(label),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -9,6 +9,7 @@ class CategoryComicsPage extends StatefulWidget {
|
|||||||
required this.category,
|
required this.category,
|
||||||
this.param,
|
this.param,
|
||||||
required this.categoryKey,
|
required this.categoryKey,
|
||||||
|
this.options,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ class CategoryComicsPage extends StatefulWidget {
|
|||||||
|
|
||||||
final String categoryKey;
|
final String categoryKey;
|
||||||
|
|
||||||
|
final List<String>? options;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CategoryComicsPage> createState() => _CategoryComicsPageState();
|
State<CategoryComicsPage> createState() => _CategoryComicsPageState();
|
||||||
}
|
}
|
||||||
@@ -31,6 +34,9 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
void findData() {
|
void findData() {
|
||||||
for (final source in ComicSource.all()) {
|
for (final source in ComicSource.all()) {
|
||||||
if (source.categoryData?.key == widget.categoryKey) {
|
if (source.categoryData?.key == widget.categoryKey) {
|
||||||
|
if (source.categoryComicsData == null) {
|
||||||
|
throw "The comic source ${source.name} does not support category comics";
|
||||||
|
}
|
||||||
data = source.categoryComicsData!;
|
data = source.categoryComicsData!;
|
||||||
options = data.options.where((element) {
|
options = data.options.where((element) {
|
||||||
if (element.notShowWhen.contains(widget.category)) {
|
if (element.notShowWhen.contains(widget.category)) {
|
||||||
@@ -40,7 +46,16 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).toList();
|
}).toList();
|
||||||
optionsValue = options.map((e) => e.options.keys.first).toList();
|
var defaultOptionsValue =
|
||||||
|
options.map((e) => e.options.keys.first).toList();
|
||||||
|
if (optionsValue.length != options.length) {
|
||||||
|
var newOptionsValue = List<String>.filled(options.length, "");
|
||||||
|
for (var i = 0; i < options.length; i++) {
|
||||||
|
newOptionsValue[i] =
|
||||||
|
optionsValue.elementAtOrNull(i) ?? defaultOptionsValue[i];
|
||||||
|
}
|
||||||
|
optionsValue = newOptionsValue;
|
||||||
|
}
|
||||||
sourceKey = source.key;
|
sourceKey = source.key;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -50,6 +65,11 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
if (widget.options != null) {
|
||||||
|
optionsValue = widget.options!;
|
||||||
|
} else {
|
||||||
|
optionsValue = [];
|
||||||
|
}
|
||||||
findData();
|
findData();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@@ -294,27 +294,9 @@ abstract mixin class _ComicPageActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onTapTag(String tag, String namespace) {
|
void onTapTag(String tag, String namespace) {
|
||||||
var config = comicSource.handleClickTagEvent?.call(namespace, tag) ??
|
var target = comicSource.handleClickTagEvent?.call(namespace, tag);
|
||||||
{
|
|
||||||
'action': 'search',
|
|
||||||
'keyword': tag,
|
|
||||||
};
|
|
||||||
var context = App.mainNavigatorKey!.currentContext!;
|
var context = App.mainNavigatorKey!.currentContext!;
|
||||||
if (config['action'] == 'search') {
|
target?.jump(context);
|
||||||
context.to(() => SearchResultPage(
|
|
||||||
text: config['keyword'] ?? '',
|
|
||||||
sourceKey: comicSource.key,
|
|
||||||
options: const [],
|
|
||||||
));
|
|
||||||
} else if (config['action'] == 'category') {
|
|
||||||
context.to(
|
|
||||||
() => CategoryComicsPage(
|
|
||||||
category: config['keyword'] ?? '',
|
|
||||||
categoryKey: comicSource.categoryData!.key,
|
|
||||||
param: config['param'],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void showMoreActions() {
|
void showMoreActions() {
|
||||||
|
@@ -105,7 +105,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
|||||||
var value = chapters[key]!;
|
var value = chapters[key]!;
|
||||||
bool visited = (history?.readEpisode ?? {}).contains(i + 1);
|
bool visited = (history?.readEpisode ?? {}).contains(i + 1);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(6, 4, 6, 4),
|
padding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: context.colorScheme.surfaceContainer,
|
color: context.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
@@ -113,7 +113,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
|||||||
onTap: () => state.read(i + 1),
|
onTap: () => state.read(i + 1),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
value,
|
value,
|
||||||
@@ -134,7 +134,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedHeight(
|
gridDelegate: const SliverGridDelegateWithFixedHeight(
|
||||||
maxCrossAxisExtent: 200,
|
maxCrossAxisExtent: 250,
|
||||||
itemHeight: 48,
|
itemHeight: 48,
|
||||||
),
|
),
|
||||||
).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)),
|
).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)),
|
||||||
@@ -300,15 +300,15 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
history!.readEpisode.contains(rawIndex);
|
history!.readEpisode.contains(rawIndex);
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(6, 4, 6, 4),
|
padding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: context.colorScheme.surfaceContainer,
|
color: context.colorScheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => state.read(chapterIndex + 1),
|
onTap: () => state.read(chapterIndex + 1),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
value,
|
value,
|
||||||
@@ -329,7 +329,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedHeight(
|
gridDelegate: const SliverGridDelegateWithFixedHeight(
|
||||||
maxCrossAxisExtent: 200,
|
maxCrossAxisExtent: 250,
|
||||||
itemHeight: 48,
|
itemHeight: 48,
|
||||||
),
|
),
|
||||||
).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)),
|
).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)),
|
||||||
|
@@ -17,10 +17,8 @@ import 'package:venera/foundation/image_provider/cached_image.dart';
|
|||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/network/download.dart';
|
import 'package:venera/network/download.dart';
|
||||||
import 'package:venera/pages/category_comics_page.dart';
|
|
||||||
import 'package:venera/pages/favorites/favorites_page.dart';
|
import 'package:venera/pages/favorites/favorites_page.dart';
|
||||||
import 'package:venera/pages/reader/reader.dart';
|
import 'package:venera/pages/reader/reader.dart';
|
||||||
import 'package:venera/pages/search_result_page.dart';
|
|
||||||
import 'package:venera/utils/app_links.dart';
|
import 'package:venera/utils/app_links.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';
|
||||||
@@ -411,7 +409,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
var group = history!.group;
|
var group = history!.group;
|
||||||
String text;
|
String text;
|
||||||
if (haveChapter) {
|
if (haveChapter) {
|
||||||
var epName = group == null
|
var epName = "E$ep";
|
||||||
|
try {
|
||||||
|
epName = group == null
|
||||||
? comic.chapters!.titles.elementAt(
|
? comic.chapters!.titles.elementAt(
|
||||||
math.min(ep - 1, comic.chapters!.length - 1),
|
math.min(ep - 1, comic.chapters!.length - 1),
|
||||||
)
|
)
|
||||||
@@ -419,6 +419,10 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
.getGroupByIndex(group - 1)
|
.getGroupByIndex(group - 1)
|
||||||
.values
|
.values
|
||||||
.elementAt(ep - 1);
|
.elementAt(ep - 1);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
text = "${"Last Reading".tl}: $epName P$page";
|
text = "${"Last Reading".tl}: $epName P$page";
|
||||||
} else {
|
} else {
|
||||||
text = "${"Last Reading".tl}: P$page";
|
text = "${"Last Reading".tl}: P$page";
|
||||||
@@ -461,7 +465,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
if (comic.tags.isEmpty &&
|
if (comic.tags.isEmpty &&
|
||||||
comic.uploader == null &&
|
comic.uploader == null &&
|
||||||
comic.uploadTime == null &&
|
comic.uploadTime == null &&
|
||||||
comic.uploadTime == null) {
|
comic.uploadTime == null &&
|
||||||
|
comic.maxPage == null) {
|
||||||
return const SliverPadding(padding: EdgeInsets.zero);
|
return const SliverPadding(padding: EdgeInsets.zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,6 +630,13 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
buildTag(text: formatTime(comic.updateTime!)),
|
buildTag(text: formatTime(comic.updateTime!)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (comic.maxPage != null)
|
||||||
|
buildWrap(
|
||||||
|
children: [
|
||||||
|
buildTag(text: 'Pages'.tl, isTitle: true),
|
||||||
|
buildTag(text: comic.maxPage.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
],
|
],
|
||||||
|
@@ -99,7 +99,11 @@ class _CommentsPageState extends State<CommentsPage> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: SmoothScrollProvider(
|
||||||
|
builder: (context, controller, physics) {
|
||||||
|
return ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
physics: physics,
|
||||||
primary: false,
|
primary: false,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
itemCount: _comments!.length + 2,
|
itemCount: _comments!.length + 2,
|
||||||
@@ -156,6 +160,8 @@ class _CommentsPageState extends State<CommentsPage> {
|
|||||||
showAvatar: showAvatar,
|
showAvatar: showAvatar,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
buildBottom(context)
|
buildBottom(context)
|
||||||
|
@@ -374,8 +374,35 @@ class _ComicSourceListState extends State<_ComicSourceList> {
|
|||||||
} else {
|
} else {
|
||||||
var currentKey = ComicSource.all().map((e) => e.key).toList();
|
var currentKey = ComicSource.all().map((e) => e.key).toList();
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: json!.length,
|
itemCount: json!.length + 1,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: context.colorScheme.primaryContainer,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.info_outline),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text("Do not report any issues related to sources to App repo.".tl),
|
||||||
|
Text("Click the setting icon to change the source list url.".tl),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
index--;
|
||||||
|
|
||||||
var key = json![index]["key"];
|
var key = json![index]["key"];
|
||||||
var action = currentKey.contains(key)
|
var action = currentKey.contains(key)
|
||||||
? const Icon(Icons.check, size: 20).paddingRight(8)
|
? const Icon(Icons.check, size: 20).paddingRight(8)
|
||||||
@@ -403,9 +430,14 @@ class _ComicSourceListState extends State<_ComicSourceList> {
|
|||||||
},
|
},
|
||||||
).fixHeight(32);
|
).fixHeight(32);
|
||||||
|
|
||||||
|
var description = json![index]["version"];
|
||||||
|
if (json![index]["description"] != null) {
|
||||||
|
description = "$description\n${json![index]["description"]}";
|
||||||
|
}
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(json![index]["name"]),
|
title: Text(json![index]["name"]),
|
||||||
subtitle: Text(json![index]["version"]),
|
subtitle: Text(description),
|
||||||
trailing: action,
|
trailing: action,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -461,6 +493,7 @@ void _addAllPagesWithComicSource(ComicSource source) {
|
|||||||
var explorePages = appdata.settings['explore_pages'];
|
var explorePages = appdata.settings['explore_pages'];
|
||||||
var categoryPages = appdata.settings['categories'];
|
var categoryPages = appdata.settings['categories'];
|
||||||
var networkFavorites = appdata.settings['favorites'];
|
var networkFavorites = appdata.settings['favorites'];
|
||||||
|
var searchPages = appdata.settings['searchSources'];
|
||||||
|
|
||||||
if (source.explorePages.isNotEmpty) {
|
if (source.explorePages.isNotEmpty) {
|
||||||
for (var page in source.explorePages) {
|
for (var page in source.explorePages) {
|
||||||
@@ -477,10 +510,15 @@ void _addAllPagesWithComicSource(ComicSource source) {
|
|||||||
!networkFavorites.contains(source.favoriteData!.key)) {
|
!networkFavorites.contains(source.favoriteData!.key)) {
|
||||||
networkFavorites.add(source.favoriteData!.key);
|
networkFavorites.add(source.favoriteData!.key);
|
||||||
}
|
}
|
||||||
|
if (source.searchPageData != null &&
|
||||||
|
!searchPages.contains(source.key)) {
|
||||||
|
searchPages.add(source.key);
|
||||||
|
}
|
||||||
|
|
||||||
appdata.settings['explore_pages'] = explorePages.toSet().toList();
|
appdata.settings['explore_pages'] = explorePages.toSet().toList();
|
||||||
appdata.settings['categories'] = categoryPages.toSet().toList();
|
appdata.settings['categories'] = categoryPages.toSet().toList();
|
||||||
appdata.settings['favorites'] = networkFavorites.toSet().toList();
|
appdata.settings['favorites'] = networkFavorites.toSet().toList();
|
||||||
|
appdata.settings['searchSources'] = searchPages.toSet().toList();
|
||||||
|
|
||||||
appdata.saveData();
|
appdata.saveData();
|
||||||
}
|
}
|
||||||
|
@@ -6,13 +6,10 @@ import 'package:venera/foundation/comic_source/comic_source.dart';
|
|||||||
import 'package:venera/foundation/global_state.dart';
|
import 'package:venera/foundation/global_state.dart';
|
||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/pages/comic_source_page.dart';
|
import 'package:venera/pages/comic_source_page.dart';
|
||||||
import 'package:venera/pages/search_result_page.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/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
import 'category_comics_page.dart';
|
|
||||||
|
|
||||||
class ExplorePage extends StatefulWidget {
|
class ExplorePage extends StatefulWidget {
|
||||||
const ExplorePage({super.key});
|
const ExplorePage({super.key});
|
||||||
|
|
||||||
@@ -445,30 +442,7 @@ Iterable<Widget> _buildExplorePagePart(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var context = App.mainNavigatorKey!.currentContext!;
|
var context = App.mainNavigatorKey!.currentContext!;
|
||||||
if (part.viewMore!.startsWith("search:")) {
|
part.viewMore!.jump(context);
|
||||||
context.to(
|
|
||||||
() => SearchResultPage(
|
|
||||||
text: part.viewMore!.replaceFirst("search:", ""),
|
|
||||||
options: const [],
|
|
||||||
sourceKey: sourceKey,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (part.viewMore!.startsWith("category:")) {
|
|
||||||
var cp = part.viewMore!.replaceFirst("category:", "");
|
|
||||||
var c = cp.split('@').first;
|
|
||||||
String? p = cp.split('@').last;
|
|
||||||
if (p == c) {
|
|
||||||
p = null;
|
|
||||||
}
|
|
||||||
context.to(
|
|
||||||
() => CategoryComicsPage(
|
|
||||||
category: c,
|
|
||||||
categoryKey:
|
|
||||||
ComicSource.find(sourceKey)!.categoryData!.key,
|
|
||||||
param: p,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Text("View more".tl),
|
child: Text("View more".tl),
|
||||||
)
|
)
|
||||||
|
@@ -52,7 +52,7 @@ class _SearchBar extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 52,
|
height: App.isMobile ? 52 : 46,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||||
child: Material(
|
child: Material(
|
||||||
@@ -942,7 +942,7 @@ class _ImageFavoritesState extends State<ImageFavorites> {
|
|||||||
displayType = type;
|
displayType = type;
|
||||||
});
|
});
|
||||||
await Future.delayed(const Duration(milliseconds: 20));
|
await Future.delayed(const Duration(milliseconds: 20));
|
||||||
var scrollController = ScrollControllerProvider.of(context);
|
var scrollController = ScrollState.of(context).controller;
|
||||||
scrollController.animateTo(
|
scrollController.animateTo(
|
||||||
scrollController.position.maxScrollExtent,
|
scrollController.position.maxScrollExtent,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
|
@@ -43,9 +43,10 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
var cp = reader.widget.chapters?.ids.elementAtOrNull(reader.chapter - 1);
|
||||||
var res = await reader.type.comicSource!.loadComicPages!(
|
var res = await reader.type.comicSource!.loadComicPages!(
|
||||||
reader.widget.cid,
|
reader.widget.cid,
|
||||||
reader.widget.chapters?.ids.elementAt(reader.chapter - 1),
|
cp,
|
||||||
);
|
);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -343,10 +344,19 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
}
|
}
|
||||||
var photoViewController = photoViewControllers[reader.page]!;
|
var photoViewController = photoViewControllers[reader.page]!;
|
||||||
double target = photoViewController.getInitialScale!.call()! * 1.75;
|
double target = photoViewController.getInitialScale!.call()! * 1.75;
|
||||||
var size = MediaQuery.of(context).size;
|
var size = reader.size;
|
||||||
|
Offset zoomPosition;
|
||||||
|
if (appdata.settings['longPressZoomPosition'] != 'center') {
|
||||||
|
zoomPosition = Offset(
|
||||||
|
size.width / 2 - location.dx,
|
||||||
|
size.height / 2 - location.dy,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
zoomPosition = Offset(0, 0);
|
||||||
|
}
|
||||||
photoViewController.animateScale?.call(
|
photoViewController.animateScale?.call(
|
||||||
target,
|
target,
|
||||||
Offset(size.width / 2 - location.dx, size.height / 2 - location.dy),
|
zoomPosition,
|
||||||
);
|
);
|
||||||
isLongPressing = true;
|
isLongPressing = true;
|
||||||
}
|
}
|
||||||
@@ -608,6 +618,13 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool onScaleUpdate([double? scale]) {
|
bool onScaleUpdate([double? scale]) {
|
||||||
|
if (prepareToNextChapter || prepareToPrevChapter) {
|
||||||
|
setState(() {
|
||||||
|
prepareToPrevChapter = false;
|
||||||
|
prepareToNextChapter = false;
|
||||||
|
});
|
||||||
|
context.readerScaffold.setFloatingButton(0);
|
||||||
|
}
|
||||||
var isZoomedIn = (scale ?? photoViewController.scale) != 1.0;
|
var isZoomedIn = (scale ?? photoViewController.scale) != 1.0;
|
||||||
if (isZoomedIn != this.isZoomedIn) {
|
if (isZoomedIn != this.isZoomedIn) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -731,7 +748,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
}
|
}
|
||||||
Offset offset;
|
Offset offset;
|
||||||
var sp = scrollController.position;
|
var sp = scrollController.position;
|
||||||
if (sp.pixels < sp.minScrollExtent || sp.pixels > sp.maxScrollExtent) {
|
if (sp.pixels <= sp.minScrollExtent ||
|
||||||
|
sp.pixels >= sp.maxScrollExtent) {
|
||||||
offset = Offset(value.dx, value.dy);
|
offset = Offset(value.dx, value.dy);
|
||||||
} else {
|
} else {
|
||||||
if (reader.mode == ReaderMode.continuousTopToBottom) {
|
if (reader.mode == ReaderMode.continuousTopToBottom) {
|
||||||
@@ -759,7 +777,10 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
delayedSetIsScrolling(false);
|
delayedSetIsScrolling(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notification is ScrollUpdateNotification) {
|
var scale = photoViewController.scale ?? 1.0;
|
||||||
|
|
||||||
|
if (notification is ScrollUpdateNotification &&
|
||||||
|
(scale - 1).abs() < 0.05) {
|
||||||
if (!scrollController.hasClients) return false;
|
if (!scrollController.hasClients) return false;
|
||||||
if (scrollController.position.pixels <=
|
if (scrollController.position.pixels <=
|
||||||
scrollController.position.minScrollExtent &&
|
scrollController.position.minScrollExtent &&
|
||||||
@@ -800,8 +821,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
},
|
},
|
||||||
child: widget,
|
child: widget,
|
||||||
);
|
);
|
||||||
var width = MediaQuery.of(context).size.width;
|
var width = reader.size.width;
|
||||||
var height = MediaQuery.of(context).size.height;
|
var height = reader.size.height;
|
||||||
if (appdata.settings['limitImageWidth'] &&
|
if (appdata.settings['limitImageWidth'] &&
|
||||||
width / height > 0.7 &&
|
width / height > 0.7 &&
|
||||||
reader.mode == ReaderMode.continuousTopToBottom) {
|
reader.mode == ReaderMode.continuousTopToBottom) {
|
||||||
@@ -882,9 +903,19 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double target = photoViewController.getInitialScale!.call()! * 1.75;
|
double target = photoViewController.getInitialScale!.call()! * 1.75;
|
||||||
|
var size = reader.size;
|
||||||
|
Offset zoomPosition;
|
||||||
|
if (appdata.settings['longPressZoomPosition'] != 'center') {
|
||||||
|
zoomPosition = Offset(
|
||||||
|
size.width / 2 - location.dx,
|
||||||
|
size.height / 2 - location.dy,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
zoomPosition = Offset(0, 0);
|
||||||
|
}
|
||||||
photoViewController.animateScale?.call(
|
photoViewController.animateScale?.call(
|
||||||
target,
|
target,
|
||||||
Offset(0, 0),
|
zoomPosition,
|
||||||
);
|
);
|
||||||
onScaleUpdate(target);
|
onScaleUpdate(target);
|
||||||
isLongPressing = true;
|
isLongPressing = true;
|
||||||
|
@@ -309,6 +309,13 @@ class _ReaderState extends State<Reader>
|
|||||||
}
|
}
|
||||||
return chapter == maxChapter;
|
return chapter == maxChapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the size of the reader.
|
||||||
|
/// The size is not always the same as the size of the screen.
|
||||||
|
Size get size {
|
||||||
|
var renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
return renderBox.size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract mixin class _ImagePerPageHandler {
|
abstract mixin class _ImagePerPageHandler {
|
||||||
@@ -363,8 +370,24 @@ abstract mixin class _VolumeListener {
|
|||||||
|
|
||||||
bool toPrevPage();
|
bool toPrevPage();
|
||||||
|
|
||||||
|
bool toNextChapter();
|
||||||
|
|
||||||
|
bool toPrevChapter();
|
||||||
|
|
||||||
VolumeListener? volumeListener;
|
VolumeListener? volumeListener;
|
||||||
|
|
||||||
|
void onDown() {
|
||||||
|
if (!toNextPage()) {
|
||||||
|
toNextChapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUp() {
|
||||||
|
if (!toPrevPage()) {
|
||||||
|
toPrevChapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handleVolumeEvent() {
|
void handleVolumeEvent() {
|
||||||
if (!App.isAndroid) {
|
if (!App.isAndroid) {
|
||||||
// Currently only support Android
|
// Currently only support Android
|
||||||
@@ -374,8 +397,8 @@ abstract mixin class _VolumeListener {
|
|||||||
volumeListener?.cancel();
|
volumeListener?.cancel();
|
||||||
}
|
}
|
||||||
volumeListener = VolumeListener(
|
volumeListener = VolumeListener(
|
||||||
onDown: toNextPage,
|
onDown: onDown,
|
||||||
onUp: toPrevPage,
|
onUp: onUp,
|
||||||
)..listen();
|
)..listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -441,6 +441,11 @@ class _SearchSettingsDialogState extends State<_SearchSettingsDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var sources = ComicSource.all();
|
||||||
|
var enabled = appdata.settings['searchSources'] as List;
|
||||||
|
sources.removeWhere((e) {
|
||||||
|
return !enabled.contains(e.key);
|
||||||
|
});
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
title: "Settings".tl,
|
title: "Settings".tl,
|
||||||
content: Column(
|
content: Column(
|
||||||
@@ -452,7 +457,7 @@ class _SearchSettingsDialogState extends State<_SearchSettingsDialog> {
|
|||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: ComicSource.all().map((e) {
|
children: sources.map((e) {
|
||||||
return OptionChip(
|
return OptionChip(
|
||||||
text: e.name.tl,
|
text: e.name.tl,
|
||||||
isSelected: searchTarget == e.key,
|
isSelected: searchTarget == e.key,
|
||||||
|
@@ -140,17 +140,6 @@ class _AppSettingsState extends State<AppSettings> {
|
|||||||
},
|
},
|
||||||
actionTitle: 'Set'.tl,
|
actionTitle: 'Set'.tl,
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SettingPartTitle(
|
|
||||||
title: "Log".tl,
|
|
||||||
icon: Icons.error_outline,
|
|
||||||
),
|
|
||||||
_CallbackSetting(
|
|
||||||
title: "Open Log".tl,
|
|
||||||
callback: () {
|
|
||||||
context.to(() => const LogsPage());
|
|
||||||
},
|
|
||||||
actionTitle: 'Open'.tl,
|
|
||||||
).toSliver(),
|
|
||||||
_SettingPartTitle(
|
_SettingPartTitle(
|
||||||
title: "User".tl,
|
title: "User".tl,
|
||||||
icon: Icons.person_outline,
|
icon: Icons.person_outline,
|
||||||
|
95
lib/pages/settings/debug.dart
Normal file
95
lib/pages/settings/debug.dart
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
part of 'settings_page.dart';
|
||||||
|
|
||||||
|
class DebugPage extends StatefulWidget {
|
||||||
|
const DebugPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DebugPage> createState() => DebugPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DebugPageState extends State<DebugPage> {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
|
||||||
|
var result = "";
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SmoothCustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverAppbar(title: Text("Debug".tl)),
|
||||||
|
_CallbackSetting(
|
||||||
|
title: "Reload Configs",
|
||||||
|
actionTitle: "Reload",
|
||||||
|
callback: () {
|
||||||
|
ComicSourceManager().reload();
|
||||||
|
},
|
||||||
|
).toSliver(),
|
||||||
|
_CallbackSetting(
|
||||||
|
title: "Open Log".tl,
|
||||||
|
callback: () {
|
||||||
|
context.to(() => const LogsPage());
|
||||||
|
},
|
||||||
|
actionTitle: 'Open'.tl,
|
||||||
|
).toSliver(),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
"JS Evaluator",
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
).toAlign(Alignment.centerLeft).paddingLeft(16),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 200,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
maxLines: null,
|
||||||
|
expands: true,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
textAlignVertical: TextAlignVertical.top,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
contentPadding: const EdgeInsets.all(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
try {
|
||||||
|
var res = JsEngine().runCode(controller.text);
|
||||||
|
setState(() {
|
||||||
|
result = res.toString();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
result = e.toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text("Run"),
|
||||||
|
).toAlign(Alignment.centerRight).paddingRight(16),
|
||||||
|
const Text(
|
||||||
|
"Result",
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
).toAlign(Alignment.centerLeft).paddingLeft(16),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 200,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: context.colorScheme.outline),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Text(result).paddingAll(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -48,6 +48,7 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
"continuousTopToBottom": "Continuous (Top to Bottom)".tl,
|
"continuousTopToBottom": "Continuous (Top to Bottom)".tl,
|
||||||
},
|
},
|
||||||
onChanged: () {
|
onChanged: () {
|
||||||
|
setState(() {});
|
||||||
var readerMode = appdata.settings['readerMode'];
|
var readerMode = appdata.settings['readerMode'];
|
||||||
if (readerMode?.toLowerCase().startsWith('continuous') ?? false) {
|
if (readerMode?.toLowerCase().startsWith('continuous') ?? false) {
|
||||||
appdata.settings['readerScreenPicNumberForLandscape'] = 1;
|
appdata.settings['readerScreenPicNumberForLandscape'] = 1;
|
||||||
@@ -68,22 +69,12 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
widget.onChanged?.call("autoPageTurningInterval");
|
widget.onChanged?.call("autoPageTurningInterval");
|
||||||
},
|
},
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SliverToBoxAdapter(
|
SliverAnimatedVisibility(
|
||||||
child: AbsorbPointer(
|
visible: appdata.settings['readerMode']!.startsWith('gallery'),
|
||||||
absorbing: (appdata.settings['readerMode']
|
|
||||||
?.toLowerCase()
|
|
||||||
.startsWith('continuous') ??
|
|
||||||
false),
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: (appdata.settings['readerMode']
|
|
||||||
?.toLowerCase()
|
|
||||||
.startsWith('continuous') ??
|
|
||||||
false)
|
|
||||||
? 0.5
|
|
||||||
: 1.0,
|
|
||||||
duration: Duration(milliseconds: 300),
|
|
||||||
child: _SliderSetting(
|
child: _SliderSetting(
|
||||||
title: "The number of pic in screen for landscape (Only Gallery Mode)".tl,
|
title:
|
||||||
|
"The number of pic in screen for landscape (Only Gallery Mode)"
|
||||||
|
.tl,
|
||||||
settingsIndex: "readerScreenPicNumberForLandscape",
|
settingsIndex: "readerScreenPicNumberForLandscape",
|
||||||
interval: 1,
|
interval: 1,
|
||||||
min: 1,
|
min: 1,
|
||||||
@@ -93,24 +84,12 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SliverAnimatedVisibility(
|
||||||
),
|
visible: appdata.settings['readerMode']!.startsWith('gallery'),
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: AbsorbPointer(
|
|
||||||
absorbing: (appdata.settings['readerMode']
|
|
||||||
?.toLowerCase()
|
|
||||||
.startsWith('continuous') ??
|
|
||||||
false),
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: (appdata.settings['readerMode']
|
|
||||||
?.toLowerCase()
|
|
||||||
.startsWith('continuous') ??
|
|
||||||
false)
|
|
||||||
? 0.5
|
|
||||||
: 1.0,
|
|
||||||
duration: Duration(milliseconds: 300),
|
|
||||||
child: _SliderSetting(
|
child: _SliderSetting(
|
||||||
title: "The number of pic in screen for portrait (Only Gallery Mode)".tl,
|
title:
|
||||||
|
"The number of pic in screen for portrait (Only Gallery Mode)"
|
||||||
|
.tl,
|
||||||
settingsIndex: "readerScreenPicNumberForPortrait",
|
settingsIndex: "readerScreenPicNumberForPortrait",
|
||||||
interval: 1,
|
interval: 1,
|
||||||
min: 1,
|
min: 1,
|
||||||
@@ -120,15 +99,25 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: 'Long press to zoom'.tl,
|
title: 'Long press to zoom'.tl,
|
||||||
settingKey: 'enableLongPressToZoom',
|
settingKey: 'enableLongPressToZoom',
|
||||||
onChanged: () {
|
onChanged: () {
|
||||||
|
setState(() {});
|
||||||
widget.onChanged?.call('enableLongPressToZoom');
|
widget.onChanged?.call('enableLongPressToZoom');
|
||||||
},
|
},
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
|
SliverAnimatedVisibility(
|
||||||
|
visible: appdata.settings['enableLongPressToZoom'] == true,
|
||||||
|
child: SelectSetting(
|
||||||
|
title: "Long press zoom position".tl,
|
||||||
|
settingKey: "longPressZoomPosition",
|
||||||
|
optionTranslation: {
|
||||||
|
"press": "Press position".tl,
|
||||||
|
"center": "Screen center".tl,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: 'Limit image width'.tl,
|
title: 'Limit image width'.tl,
|
||||||
subtitle: 'When using Continuous(Top to Bottom) mode'.tl,
|
subtitle: 'When using Continuous(Top to Bottom) mode'.tl,
|
||||||
|
@@ -30,6 +30,7 @@ part 'local_favorites.dart';
|
|||||||
part 'app.dart';
|
part 'app.dart';
|
||||||
part 'about.dart';
|
part 'about.dart';
|
||||||
part 'network.dart';
|
part 'network.dart';
|
||||||
|
part 'debug.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatefulWidget {
|
class SettingsPage extends StatefulWidget {
|
||||||
const SettingsPage({this.initialPage = -1, super.key});
|
const SettingsPage({this.initialPage = -1, super.key});
|
||||||
@@ -55,6 +56,7 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
"APP",
|
"APP",
|
||||||
"Network",
|
"Network",
|
||||||
"About",
|
"About",
|
||||||
|
"Debug"
|
||||||
];
|
];
|
||||||
|
|
||||||
final icons = <IconData>[
|
final icons = <IconData>[
|
||||||
@@ -64,7 +66,8 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
Icons.collections_bookmark_rounded,
|
Icons.collections_bookmark_rounded,
|
||||||
Icons.apps,
|
Icons.apps,
|
||||||
Icons.public,
|
Icons.public,
|
||||||
Icons.info
|
Icons.info,
|
||||||
|
Icons.bug_report,
|
||||||
];
|
];
|
||||||
|
|
||||||
double offset = 0;
|
double offset = 0;
|
||||||
@@ -246,6 +249,9 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handlePointerDown(PointerDownEvent event) {
|
void handlePointerDown(PointerDownEvent event) {
|
||||||
|
if (!App.isIOS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.position.dx < 20) {
|
if (event.position.dx < 20) {
|
||||||
gestureRecognizer.addPointer(event);
|
gestureRecognizer.addPointer(event);
|
||||||
}
|
}
|
||||||
@@ -350,6 +356,7 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
4 => const AppSettings(),
|
4 => const AppSettings(),
|
||||||
5 => const NetworkSettings(),
|
5 => const NetworkSettings(),
|
||||||
6 => const AboutSettings(),
|
6 => const AboutSettings(),
|
||||||
|
7 => const DebugPage(),
|
||||||
_ => throw UnimplementedError()
|
_ => throw UnimplementedError()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:venera/components/components.dart';
|
||||||
|
import 'package:venera/components/window_frame.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/foundation/appdata.dart';
|
import 'package:venera/foundation/appdata.dart';
|
||||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
@@ -10,6 +12,7 @@ import 'package:venera/utils/data.dart';
|
|||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
import 'package:webdav_client/webdav_client.dart' hide File;
|
import 'package:webdav_client/webdav_client.dart' hide File;
|
||||||
import 'package:rhttp/rhttp.dart' as rhttp;
|
import 'package:rhttp/rhttp.dart' as rhttp;
|
||||||
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
import 'io.dart';
|
import 'io.dart';
|
||||||
|
|
||||||
@@ -20,6 +23,10 @@ class DataSync with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
LocalFavoritesManager().addListener(onDataChanged);
|
LocalFavoritesManager().addListener(onDataChanged);
|
||||||
ComicSourceManager().addListener(onDataChanged);
|
ComicSourceManager().addListener(onDataChanged);
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
var controller = WindowFrame.of(App.rootContext);
|
||||||
|
controller.addCloseListener(_handleWindowClose);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDataChanged() {
|
void onDataChanged() {
|
||||||
@@ -28,6 +35,28 @@ class DataSync with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _handleWindowClose() {
|
||||||
|
if (_isUploading) {
|
||||||
|
_showWindowCloseDialog();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showWindowCloseDialog() async {
|
||||||
|
showLoadingDialog(
|
||||||
|
App.rootContext,
|
||||||
|
cancelButtonText: "Shut Down".tl,
|
||||||
|
onCancel: () => exit(0),
|
||||||
|
barrierDismissible: false,
|
||||||
|
message: "Uploading data...".tl,
|
||||||
|
);
|
||||||
|
while (_isUploading) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 50));
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
static DataSync? instance;
|
static DataSync? instance;
|
||||||
|
|
||||||
factory DataSync() => instance ?? (instance = DataSync._());
|
factory DataSync() => instance ?? (instance = DataSync._());
|
||||||
@@ -100,6 +129,7 @@ class DataSync with ChangeNotifier {
|
|||||||
rhttp.ClientSettings(
|
rhttp.ClientSettings(
|
||||||
proxySettings:
|
proxySettings:
|
||||||
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
|
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
|
||||||
|
userAgent: "venera v${App.version}",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -172,6 +202,7 @@ class DataSync with ChangeNotifier {
|
|||||||
rhttp.ClientSettings(
|
rhttp.ClientSettings(
|
||||||
proxySettings:
|
proxySettings:
|
||||||
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
|
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
|
||||||
|
userAgent: "venera v${App.version}",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
@@ -132,25 +131,28 @@ extension DirectoryExtension on Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sanitize the file name. Remove invalid characters and trim the file name.
|
/// Sanitize the file name. Remove invalid characters and trim the file name.
|
||||||
String sanitizeFileName(String fileName) {
|
String sanitizeFileName(String fileName, {String? dir, int? maxLength}) {
|
||||||
if (fileName.endsWith('.')) {
|
if (fileName.endsWith('.')) {
|
||||||
fileName = fileName.substring(0, fileName.length - 1);
|
fileName = fileName.substring(0, fileName.length - 1);
|
||||||
}
|
}
|
||||||
const maxLength = 255;
|
var maxLength = 255;
|
||||||
|
if (dir != null) {
|
||||||
|
if (!dir.endsWith('/') && !dir.endsWith('\\')) {
|
||||||
|
dir = "$dir/";
|
||||||
|
}
|
||||||
|
maxLength -= dir.length;
|
||||||
|
}
|
||||||
final invalidChars = RegExp(r'[<>:"/\\|?*]');
|
final invalidChars = RegExp(r'[<>:"/\\|?*]');
|
||||||
final sanitizedFileName = fileName.replaceAll(invalidChars, ' ');
|
final sanitizedFileName = fileName.replaceAll(invalidChars, ' ');
|
||||||
var trimmedFileName = sanitizedFileName.trim();
|
var trimmedFileName = sanitizedFileName.trim();
|
||||||
if (trimmedFileName.isEmpty) {
|
if (trimmedFileName.isEmpty) {
|
||||||
throw Exception('Invalid File Name: Empty length.');
|
throw Exception('Invalid File Name: Empty length.');
|
||||||
}
|
}
|
||||||
while (true) {
|
if (maxLength <= 0) {
|
||||||
final bytes = utf8.encode(trimmedFileName);
|
throw Exception('Invalid File Name: Max length is less than 0.');
|
||||||
if (bytes.length > maxLength) {
|
|
||||||
trimmedFileName =
|
|
||||||
trimmedFileName.substring(0, trimmedFileName.length - 1);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (trimmedFileName.length > maxLength) {
|
||||||
|
trimmedFileName = trimmedFileName.substring(0, maxLength);
|
||||||
}
|
}
|
||||||
return trimmedFileName;
|
return trimmedFileName;
|
||||||
}
|
}
|
||||||
|
@@ -80,6 +80,7 @@ static void my_application_activate(GApplication* application) {
|
|||||||
gtk_window_set_default_size(window, 1280, 720);
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
GdkVisual* visual;
|
GdkVisual* visual;
|
||||||
gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
|
gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
|
||||||
|
gtk_window_set_decorated(window, FALSE);
|
||||||
visual = gdk_screen_get_rgba_visual(screen);
|
visual = gdk_screen_get_rgba_visual(screen);
|
||||||
if (visual != NULL && gdk_screen_is_composited(screen)) {
|
if (visual != NULL && gdk_screen_is_composited(screen)) {
|
||||||
gtk_widget_set_visual(GTK_WIDGET(window), visual);
|
gtk_widget_set_visual(GTK_WIDGET(window), visual);
|
||||||
|
@@ -433,8 +433,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "690a03a954f1603e0149cfd479c8961b88f21336"
|
ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
|
||||||
resolved-ref: "690a03a954f1603e0149cfd479c8961b88f21336"
|
resolved-ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
|
||||||
url: "https://github.com/venera-app/flutter_saf"
|
url: "https://github.com/venera-app/flutter_saf"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
@@ -2,7 +2,7 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.3.4+134
|
version: 1.4.0+140
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.6.0 <4.0.0'
|
sdk: '>=3.6.0 <4.0.0'
|
||||||
@@ -72,7 +72,7 @@ dependencies:
|
|||||||
flutter_saf:
|
flutter_saf:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/venera-app/flutter_saf
|
url: https://github.com/venera-app/flutter_saf
|
||||||
ref: 690a03a954f1603e0149cfd479c8961b88f21336
|
ref: fe182cdf40e5fa6230f451bc1d643b860f610d13
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
shimmer_animation: ^2.1.0
|
shimmer_animation: ^2.1.0
|
||||||
flutter_memory_info: ^0.0.1
|
flutter_memory_info: ^0.0.1
|
||||||
|
@@ -2,11 +2,11 @@
|
|||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
#define MyAppName "Venera"
|
#define MyAppName "Venera"
|
||||||
#define MyAppVersion "1.3.4"
|
#define MyAppVersion "{{version}}"
|
||||||
#define MyAppPublisher "nyne"
|
#define MyAppPublisher "nyne"
|
||||||
#define MyAppURL "https://github.com/venera-app/venera"
|
#define MyAppURL "https://github.com/venera-app/venera"
|
||||||
#define MyAppExeName "venera.exe"
|
#define MyAppExeName "venera.exe"
|
||||||
#define RootPath "D:\code\venera"
|
#define RootPath "{{root_path}}"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||||
|
Reference in New Issue
Block a user