diff --git a/android/app/build.gradle b/android/app/build.gradle index 9a8ad45..f296899 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -67,7 +67,6 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.github.wgh136.venera" // 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. @@ -125,6 +124,6 @@ flutter { } dependencies { - implementation "androidx.activity:activity-ktx:1.9.2" + implementation "androidx.activity:activity-ktx:1.10.1" implementation 'androidx.documentfile:documentfile:1.0.1' } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5e6b542..efdcc4a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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 diff --git a/android/settings.gradle b/android/settings.gradle index 3a1cb3b..3d1ac22 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { 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 } diff --git a/assets/translation.json b/assets/translation.json index add7a5a..9749fc6 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -140,18 +140,18 @@ "Block": "屏蔽", "Add new favorite to": "添加新收藏到", "Move favorite after reading": "阅读后移动收藏", - "Delete folder?" : "删除文件夹?", - "Delete folder '@f' ?" : "删除文件夹 '@f' ?", + "Delete folder?": "删除文件夹?", + "Delete folder '@f' ?": "删除文件夹 '@f' ?", "Import from file": "从文件导入", "Failed to import": "导入失败", "Cache Limit": "缓存限制", "Set Cache Limit": "设置缓存限制", "Size in MB": "大小(MB)", - "Select a directory which contains the comic directories." : "选择一个包含漫画文件夹的目录", + "Select a directory which contains the comic directories.": "选择一个包含漫画文件夹的目录", "Help": "帮助", "Export as cbz": "导出为cbz", - "Select an archive file (cbz, zip, 7z, cb7)" : "选择一个归档文件 (cbz, zip, 7z, cb7)", - "An archive file" : "一个归档文件", + "Select an archive file (cbz, zip, 7z, cb7)": "选择一个归档文件 (cbz, zip, 7z, cb7)", + "An archive file": "一个归档文件", "Fullscreen": "全屏", "Exit": "退出", "View more": "查看更多", @@ -198,9 +198,9 @@ "Long press on the favorite button to quickly add to this folder": "长按收藏按钮快速添加到这个文件夹", "Added": "已添加", "Turn page by volume keys": "使用音量键翻页", - "Display time & battery info in reader":"在阅读器中显示时间和电量信息", - "EhViewer downloads":"EhViewer下载", - "Select an EhViewer database and a download folder.":"选择EhViewer的下载数据(导出的db文件)与存放下载内容的目录", + "Display time & battery info in reader": "在阅读器中显示时间和电量信息", + "EhViewer downloads": "EhViewer下载", + "Select an EhViewer database and a download folder.": "选择EhViewer的下载数据(导出的db文件)与存放下载内容的目录", "(EhViewer)Default": "(EhViewer)默认", "If you import an EhViewer's database, program will automatically create folders according to the download label in that database.": "若通过EhViewer数据库导入漫画,程序将会按其中的下载标签自动创建收藏文件夹。", "Multi-Select": "进入多选模式", @@ -241,7 +241,7 @@ "Delete all unavailable local favorite items": "删除所有无效的本地收藏", "Deleted @a favorite items.": "已删除 @a 条无效收藏", "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": "没有新版本可用", "Export as pdf": "导出为pdf", "Export as epub": "导出为epub", @@ -288,15 +288,15 @@ "Copy the title successfully": "复制标题成功", "The comic is invalid, please long press to delete, you can double click the title to copy": "该漫画已失效, 请长按删除, 可以双击标题进行复制", "No search results found": "未找到搜索结果", - "Added @c comics to download queue." : "已添加 @c 本漫画到下载队列", + "Added @c comics to download queue.": "已添加 @c 本漫画到下载队列", "Download started": "下载已开始", "Click favorite": "点击收藏", "End": "末尾", "None": "无", "View Detail": "查看详情", - "Select a directory which contains multiple archive files." : "选择一个包含多个归档文件的目录", - "Multiple archive files" : "多个归档文件", - "No valid comics found" : "未找到有效的漫画", + "Select a directory which contains multiple archive files.": "选择一个包含多个归档文件的目录", + "Multiple archive files": "多个归档文件", + "No valid comics found": "未找到有效的漫画", "Enable DNS Overrides": "启用DNS覆写", "DNS Overrides": "DNS覆写", "Custom Image Processing": "自定义图片处理", @@ -342,12 +342,12 @@ "Replies": "回复", "Follow Updates": "追更", "Not Configured": "未配置", - "Choose a folder to follow updates." : "选择一个文件夹以追更", + "Choose a folder to follow updates.": "选择一个文件夹以追更", "Choose Folder": "选择文件夹", "No folders available": "没有可用的文件夹", "Updating comics...": "更新漫画中...", - "Automatic update checking enabled." : "已启用自动更新检查", - "The app will check for updates at most once a day." : "APP将每天最多检查一次更新", + "Automatic update checking enabled.": "已启用自动更新检查", + "The app will check for updates at most once a day.": "APP将每天最多检查一次更新", "Change Folder": "更改文件夹", "Check Now": "立即检查", "Updates": "更新", @@ -360,7 +360,7 @@ "Disabled": "已禁用", "Auto Sync Data": "自动同步数据", "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 up for next chapter": "向上滑动查看下一章", "Initial Page": "初始页面", @@ -378,7 +378,16 @@ "Page": "页面", "Jump": "跳转", "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": { "Home": "首頁", @@ -520,18 +529,18 @@ "Block": "封鎖", "Add new favorite to": "添加新收藏到", "Move favorite after reading": "閱讀後移動收藏", - "Delete folder?" : "刪除資料夾?", - "Delete folder '@f' ?" : "刪除資料夾 '@f' ?", + "Delete folder?": "刪除資料夾?", + "Delete folder '@f' ?": "刪除資料夾 '@f' ?", "Import from file": "從文件匯入", "Failed to import": "匯入失敗", "Cache Limit": "快取限制", "Set Cache Limit": "設定快取限制", "Size in MB": "大小(MB)", - "Select a directory which contains the comic directories." : "選擇一個包含漫畫資料夾的目錄", + "Select a directory which contains the comic directories.": "選擇一個包含漫畫資料夾的目錄", "Help": "幫助", "Export as cbz": "匯出為cbz", - "Select an archive file (cbz, zip, 7z, cb7)" : "選擇一個歸檔文件 (cbz, zip, 7z, cb7)", - "An archive file" : "一個歸檔文件", + "Select an archive file (cbz, zip, 7z, cb7)": "選擇一個歸檔文件 (cbz, zip, 7z, cb7)", + "An archive file": "一個歸檔文件", "Fullscreen": "全螢幕", "Exit": "退出", "View more": "查看更多", @@ -622,13 +631,13 @@ "Delete all unavailable local favorite items": "刪除所有無效的本機收藏", "Deleted @a favorite items.": "已刪除 @a 條無效收藏", "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": "沒有新版本可用", "Export as pdf": "匯出為pdf", "Export as epub": "匯出為epub", "Aggregated Search": "聚合搜尋", "No search results found": "未找到搜尋結果", - "Added @c comics to download queue." : "已添加 @c 本漫畫到下載佇列", + "Added @c comics to download queue.": "已添加 @c 本漫畫到下載佇列", "Download started": "下載已開始", "Click favorite": "點擊收藏", "Local comic collection is not supported at present": "本機收藏暫不支援", @@ -675,9 +684,9 @@ "End": "末尾", "None": "無", "View Detail": "查看詳情", - "Select a directory which contains multiple archive files." : "選擇一個包含多個歸檔文件的目錄", - "Multiple archive files" : "多個歸檔文件", - "No valid comics found" : "未找到有效的漫畫", + "Select a directory which contains multiple archive files.": "選擇一個包含多個歸檔文件的目錄", + "Multiple archive files": "多個歸檔文件", + "No valid comics found": "未找到有效的漫畫", "Enable DNS Overrides": "啟用DNS覆寫", "DNS Overrides": "DNS覆寫", "Custom Image Processing": "自訂圖片處理", @@ -723,12 +732,12 @@ "Replies": "回覆", "Follow Updates": "追更", "Not Configured": "未配置", - "Choose a folder to follow updates." : "選擇一個資料夾以追更", + "Choose a folder to follow updates.": "選擇一個資料夾以追更", "Choose Folder": "選擇資料夾", "No folders available": "沒有可用的資料夾", "Updating comics...": "更新漫畫中...", - "Automatic update checking enabled." : "已啟用自動更新檢查", - "The app will check for updates at most once a day." : "APP將每天最多檢查一次更新", + "Automatic update checking enabled.": "已啟用自動更新檢查", + "The app will check for updates at most once a day.": "APP將每天最多檢查一次更新", "Change Folder": "更改資料夾", "Check Now": "立即檢查", "Updates": "更新", @@ -741,7 +750,7 @@ "Disabled": "已停用", "Auto Sync Data": "自動同步資料", "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 up for next chapter": "向上滑動查看下一章", "Initial Page": "初始頁面", @@ -759,6 +768,15 @@ "Page": "頁面", "Jump": "跳轉", "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" } } diff --git a/debian/gui/venera.png b/debian/gui/venera.png index ffd7fef..474dba4 100644 Binary files a/debian/gui/venera.png and b/debian/gui/venera.png differ diff --git a/lib/components/comic.dart b/lib/components/comic.dart index 76aaebd..3ad1f2b 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -334,7 +334,12 @@ class ComicTile extends StatelessWidget { } var children = []; - 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( margin: const EdgeInsets.fromLTRB(2, 0, 2, 2), padding: constraints.maxWidth < 80 diff --git a/lib/components/layout.dart b/lib/components/layout.dart index 0238863..e4bf0c6 100644 --- a/lib/components/layout.dart +++ b/lib/components/layout.dart @@ -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, + ), + ); + } +} diff --git a/lib/components/scroll.dart b/lib/components/scroll.dart index 3988630..741de7d 100644 --- a/lib/components/scroll.dart +++ b/lib/components/scroll.dart @@ -51,10 +51,32 @@ class _SmoothScrollProviderState extends State { static bool _isMouseScroll = App.isDesktop; + late int id; + + static int _id = 0; + + var activeChildren = {}; + + ScrollState? parent; + @override void initState() { _controller = widget.controller ?? ScrollController(); super.initState(); + id = _id; + _id++; + } + + @override + void didChangeDependencies() { + parent = ScrollState.maybeOf(context); + super.didChangeDependencies(); + } + + @override + void dispose() { + parent?.onChildInactive(id); + super.dispose(); } @override @@ -66,8 +88,7 @@ class _SmoothScrollProviderState extends State { const BouncingScrollPhysics(), ); } - return Listener( - behavior: HitTestBehavior.translucent, + var child = Listener( onPointerDown: (event) { _futurePosition = null; if (_isMouseScroll) { @@ -77,6 +98,9 @@ class _SmoothScrollProviderState extends State { } }, onPointerSignal: (pointerSignal) { + if (activeChildren.isNotEmpty) { + return; + } if (pointerSignal is PointerScrollEvent) { if (HardwareKeyboard.instance.isShiftPressed) { return; @@ -113,8 +137,14 @@ class _SmoothScrollProviderState extends State { }); } }, - child: ScrollControllerProvider._( + child: ScrollState._( controller: _controller, + onChildActive: (id) { + activeChildren.add(id); + }, + onChildInactive: (id) { + activeChildren.remove(id); + }, child: widget.builder( context, _controller, @@ -124,25 +154,49 @@ class _SmoothScrollProviderState extends State { ), ), ); + + if (parent != null) { + return MouseRegion( + onEnter: (_) { + parent!.onChildActive(id); + }, + onExit: (_) { + parent!.onChildInactive(id); + }, + child: child, + ); + } + + return child; } } -class ScrollControllerProvider extends InheritedWidget { - const ScrollControllerProvider._({ +class ScrollState extends InheritedWidget { + const ScrollState._({ required this.controller, required super.child, + required this.onChildActive, + required this.onChildInactive, }); final ScrollController controller; - static ScrollController of(BuildContext context) { - final ScrollControllerProvider? provider = - context.dependOnInheritedWidgetOfExactType(); - return provider!.controller; + final void Function(int id) onChildActive; + + final void Function(int id) onChildInactive; + + static ScrollState of(BuildContext context) { + final ScrollState? provider = + context.dependOnInheritedWidgetOfExactType(); + return provider!; + } + + static ScrollState? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); } @override - bool updateShouldNotify(ScrollControllerProvider oldWidget) { + bool updateShouldNotify(ScrollState oldWidget) { return oldWidget.controller != controller; } } diff --git a/lib/components/window_frame.dart b/lib/components/window_frame.dart index 75c6f5b..11cf0b3 100644 --- a/lib/components/window_frame.dart +++ b/lib/components/window_frame.dart @@ -82,10 +82,7 @@ class _WindowFrameState extends State { return; } } - windowManager.close().then((_) { - // Make sure the app exits when the window is closed. - exit(0); - }); + exit(0); } @override @@ -564,20 +561,19 @@ class _VirtualWindowFrameState extends State Widget _buildVirtualWindowFrame(BuildContext context) { return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(_isMaximized ? 0 : 8), - color: Colors.transparent, - boxShadow: [ - BoxShadow( - color: Colors.black.toOpacity(_isFocused ? 0.4 : 0.2), - offset: Offset(0.0, 2), - blurRadius: 4, - ) - ], - ), - clipBehavior: Clip.antiAlias, - child: widget.child, - ); + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_isMaximized ? 0 : 8), + color: Colors.transparent, + boxShadow: [ + BoxShadow( + color: Colors.black.toOpacity(_isFocused ? 0.4 : 0.2), + blurRadius: 4, + ) + ], + ), + clipBehavior: Clip.antiAlias, + child: widget.child, + ); } @override diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index d48286e..384b0ce 100644 --- a/lib/foundation/app.dart +++ b/lib/foundation/app.dart @@ -13,7 +13,7 @@ export "widget_utils.dart"; export "context.dart"; class _App { - final version = "1.3.4"; + final version = "1.4.0"; bool get isAndroid => Platform.isAndroid; @@ -47,6 +47,7 @@ class _App { late String dataPath; late String cachePath; + String? externalStoragePath; final rootNavigatorKey = GlobalKey(); @@ -77,6 +78,9 @@ class _App { Future init() async { cachePath = (await getApplicationCacheDirectory()).path; dataPath = (await getApplicationSupportDirectory()).path; + if (isAndroid) { + externalStoragePath = (await getExternalStorageDirectory())!.path; + } } Future initComponents() async { diff --git a/lib/foundation/appdata.dart b/lib/foundation/appdata.dart index b4e4073..ecb64cc 100644 --- a/lib/foundation/appdata.dart +++ b/lib/foundation/appdata.dart @@ -161,6 +161,7 @@ class Settings with ChangeNotifier { 'cacheSize': 2048, // in MB 'downloadThreads': 5, 'enableLongPressToZoom': true, + 'longPressZoomPosition': "press", // press, center 'checkUpdateOnStart': false, 'limitImageWidth': true, 'webdav': [], // empty means not configured diff --git a/lib/foundation/comic_source/category.dart b/lib/foundation/comic_source/category.dart index 7aae515..383a7ad 100644 --- a/lib/foundation/comic_source/category.dart +++ b/lib/foundation/comic_source/category.dart @@ -34,24 +34,28 @@ class CategoryButtonData { }); } +class CategoryItem { + final String label; + + final PageJumpTarget target; + + const CategoryItem(this.label, this.target); +} + abstract class BaseCategoryPart { String get title; - List get categories; - - List? get categoryParams => null; + List get categories; bool get enableRandom; - String get categoryType; - /// Data class for building a part of category page. const BaseCategoryPart(); } class FixedCategoryPart extends BaseCategoryPart { @override - final List categories; + final List categories; @override bool get enableRandom => false; @@ -59,19 +63,12 @@ class FixedCategoryPart extends BaseCategoryPart { @override final String title; - @override - final String categoryType; - - @override - final List? categoryParams; - /// A [BaseCategoryPart] that show fixed tags on category page. - const FixedCategoryPart(this.title, this.categories, this.categoryType, - [this.categoryParams]); + const FixedCategoryPart(this.title, this.categories); } class RandomCategoryPart extends BaseCategoryPart { - final List tags; + final List all; final int randomNumber; @@ -81,67 +78,59 @@ class RandomCategoryPart extends BaseCategoryPart { @override bool get enableRandom => true; - @override - final String categoryType; - - List _categories() { - if (randomNumber >= tags.length) { - return tags; + List _categories() { + if (randomNumber >= all.length) { + return all; } - var start = math.Random().nextInt(tags.length - randomNumber); - return tags.sublist(start, start + randomNumber); + var start = math.Random().nextInt(all.length - randomNumber); + return all.sublist(start, start + randomNumber); } @override - List get categories => _categories(); + List 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( - this.title, this.tags, this.randomNumber, this.categoryType); + this.title, + this.all, + this.randomNumber, + ); } -class RandomCategoryPartWithRuntimeData extends BaseCategoryPart { - final Iterable Function() loadTags; +class DynamicCategoryPart extends BaseCategoryPart { + final JSAutoFreeFunction loader; - final int randomNumber; + final String sourceKey; @override - final String title; - - @override - bool get enableRandom => true; - - @override - final String categoryType; - - static final random = math.Random(); - - List _categories() { - var tags = loadTags(); - if (randomNumber >= tags.length) { - return tags.toList(); + List get categories { + var data = loader([]); + if (data is! List) { + throw "DynamicCategoryPart loader must return a List"; } - final start = random.nextInt(tags.length - randomNumber); - var res = List.filled(randomNumber, ''); - int index = -1; - for (var s in tags) { - index++; - if (start > index) { - continue; - } else if (index == start + randomNumber) { - break; + var res = []; + for (var item in data) { + if (item is! Map) { + throw "DynamicCategoryPart loader must return a List of Map"; } - 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; } @override - List get categories => _categories(); + bool get enableRandom => false; - /// A [BaseCategoryPart] that show random tags on category page. - RandomCategoryPartWithRuntimeData( - this.title, this.loadTags, this.randomNumber, this.categoryType); + @override + final String title; + + /// A [BaseCategoryPart] that show dynamic tags on category page. + const DynamicCategoryPart(this.title, this.loader, this.sourceKey); } CategoryData getCategoryDataWithKey(String key) { diff --git a/lib/foundation/comic_source/comic_source.dart b/lib/foundation/comic_source/comic_source.dart index 1ef748c..3e4b524 100644 --- a/lib/foundation/comic_source/comic_source.dart +++ b/lib/foundation/comic_source/comic_source.dart @@ -11,6 +11,8 @@ import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/history.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/ext.dart'; import 'package:venera/utils/init.dart'; @@ -349,7 +351,7 @@ class ExplorePagePart { /// - category:categoryName /// /// End with `@`+`param` if the category has a parameter. - final String? viewMore; + final PageJumpTarget? viewMore; const ExplorePagePart(this.title, this.comics, this.viewMore); } diff --git a/lib/foundation/comic_source/models.dart b/lib/foundation/comic_source/models.dart index f17c76d..5e2bc62 100644 --- a/lib/foundation/comic_source/models.dart +++ b/lib/foundation/comic_source/models.dart @@ -169,7 +169,9 @@ class ComicDetails with HistoryMixin { static Map> _generateMap(Map map) { var res = >{}; map.forEach((key, value) { - res[key] = List.from(value); + if (value is List) { + res[key] = List.from(value); + } }); return res; } @@ -342,7 +344,8 @@ class ComicChapters { } else if (groupedChapters.isNotEmpty) { return ComicChapters.grouped(groupedChapters); } 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? 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"); + } + } +} diff --git a/lib/foundation/comic_source/parser.dart b/lib/foundation/comic_source/parser.dart index e9ed69a..0d92978 100644 --- a/lib/foundation/comic_source/parser.dart +++ b/lib/foundation/comic_source/parser.dart @@ -80,9 +80,8 @@ class ComicSourceParser { Future parse(String js, String filePath) async { js = js.replaceAll("\r\n", "\n"); - var line1 = js - .split('\n') - .firstWhereOrNull((e) => e.trim().startsWith("class ")); + var line1 = + js.split('\n').firstWhereOrNull((e) => e.trim().startsWith("class ")); if (line1 == null || !line1.startsWith("class ") || !line1.contains("extends ComicSource")) { @@ -336,7 +335,7 @@ class ComicSourceParser { (e['comics'] as List).map((e) { return Comic.fromJson(e, _key!); }).toList(), - e['viewMore'], + PageJumpTarget.parse(_key!, e['viewMore']), ); }), ), @@ -404,21 +403,91 @@ class ComicSourceParser { var categoryParts = []; for (var c in doc["parts"]) { - final String name = c["name"]; - final String type = c["type"]; - final List tags = List.from(c["categories"]); - final String itemType = c["itemType"]; - List? categoryParams = ListOrNull.from(c["categoryParams"]); - final String? groupParam = c["groupParam"]; - if (groupParam != null) { - categoryParams = List.filled(tags.length, groupParam); + if (c["categories"] != null && c["categories"] is! List) { + continue; } - if (type == "fixed") { - categoryParts - .add(FixedCategoryPart(name, tags, itemType, categoryParams)); - } else if (type == "random") { - categoryParts.add( - RandomCategoryPart(name, tags, c["randomNumber"] ?? 1, itemType)); + 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 type = c["type"]; + final List tags = List.from(c["categories"]); + final String itemType = c["itemType"]; + List? categoryParams = ListOrNull.from(c["categoryParams"]); + final String? groupParam = c["groupParam"]; + if (groupParam != null) { + categoryParams = List.filled(tags.length, groupParam); + } + var cs = []; + 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") { + categoryParts.add(FixedCategoryPart(name, cs)); + } else if (type == "random") { + categoryParts + .add(RandomCategoryPart(name, cs, c["randomNumber"] ?? 1)); + } } } @@ -620,7 +689,8 @@ class ComicSourceParser { final bool multiFolder = _getValue("favorites.multiFolder"); final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort"); - final bool? singleFolderForSingleComic = _getValue("favorites.singleFolderForSingleComic"); + final bool? singleFolderForSingleComic = + _getValue("favorites.singleFolderForSingleComic"); Future> retryZone(Future> Function() func) async { if (!ComicSource.find(_key!)!.isLogged) { @@ -978,9 +1048,12 @@ class ComicSourceParser { var res = JsEngine().runCode(""" ComicSource.sources.$_key.comic.onClickTag(${jsonEncode(namespace)}, ${jsonEncode(tag)}) """); - var r = Map.from(res); + if (res is! Map) { + return null; + } + var r = Map.from(res); r.removeWhere((key, value) => value == null); - return Map.from(r); + return PageJumpTarget.parse(_key!, r); }; } diff --git a/lib/foundation/comic_source/types.dart b/lib/foundation/comic_source/types.dart index fbacc8f..d75c3eb 100644 --- a/lib/foundation/comic_source/types.dart +++ b/lib/foundation/comic_source/types.dart @@ -41,7 +41,7 @@ typedef LikeCommentFunc = Future> Function( typedef VoteCommentFunc = Future> Function( String comicId, String? subId, String commentId, bool isUp, bool isCancel); -typedef HandleClickTagEvent = Map Function( +typedef HandleClickTagEvent = PageJumpTarget? Function( String namespace, String tag); /// [rating] is the rating value, 0-10. 1 represents 0.5 star. diff --git a/lib/foundation/local.dart b/lib/foundation/local.dart index c5f00b6..b52524f 100644 --- a/lib/foundation/local.dart +++ b/lib/foundation/local.dart @@ -461,6 +461,10 @@ class LocalManager with ChangeNotifier { if (comic != null) { return Directory(FilePath.join(path, comic.directory)); } + const comicDirectoryMaxLength = 128; + if (name.length > comicDirectoryMaxLength) { + name = name.substring(0, comicDirectoryMaxLength); + } var dir = findValidDirectoryName(path, name); return Directory(FilePath.join(path, dir)).create().then((value) => value); } diff --git a/lib/foundation/log.dart b/lib/foundation/log.dart index 54b47ed..fba4caf 100644 --- a/lib/foundation/log.dart +++ b/lib/foundation/log.dart @@ -1,7 +1,7 @@ -import 'dart:io'; - import 'package:flutter/foundation.dart'; +import 'package:venera/foundation/app.dart'; import 'package:venera/utils/ext.dart'; +import 'package:venera/utils/io.dart'; class LogItem { final LogLevel level; @@ -28,9 +28,6 @@ class Log { static bool ignoreLimitation = false; - /// only for debug - static const String? logFile = null; - static void printWarning(String text) { debugPrint('\x1B[33m$text\x1B[0m'); } @@ -39,7 +36,20 @@ class Log { debugPrint('\x1B[31m$text\x1B[0m'); } + static IOSink? _file; + 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) { content = "${content.substring(0, maxLogLength)}..."; } @@ -62,8 +72,8 @@ class Log { } _logs.add(newLog); - if(logFile != null) { - File(logFile!).writeAsString(newLog.toString(), mode: FileMode.append); + if(_file != null) { + _file!.write(newLog.toString()); } if (_logs.length > maxLogNumber) { var res = _logs.remove( diff --git a/lib/main.dart b/lib/main.dart index 463a3b0..6da09f5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,8 +35,14 @@ void main(List args) { } await windowManager.setMinimumSize(const Size(500, 600)); var placement = await WindowPlacement.loadFromFile(); - await placement.applyToWindow(); - await windowManager.show(); + if (App.isLinux) { + await windowManager.show(); + await placement.applyToWindow(); + } else { + await placement.applyToWindow(); + await windowManager.show(); + } + WindowPlacement.loop(); }); } diff --git a/lib/network/download.dart b/lib/network/download.dart index d21017c..bb8c6d3 100644 --- a/lib/network/download.dart +++ b/lib/network/download.dart @@ -482,7 +482,7 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin { chapters: comic!.chapters, cover: File(_cover!.split("file://").last).name, comicType: ComicType(source.key.hashCode), - downloadedChapters: chapters ?? [], + downloadedChapters: chapters ?? comic?.chapters?.ids.toList() ?? [], createdAt: DateTime.now(), ); } diff --git a/lib/pages/categories_page.dart b/lib/pages/categories_page.dart index f46d975..15ed457 100644 --- a/lib/pages/categories_page.dart +++ b/lib/pages/categories_page.dart @@ -4,12 +4,10 @@ import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/appdata.dart'; import 'package:venera/foundation/comic_source/comic_source.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/utils/ext.dart'; import 'package:venera/utils/translations.dart'; -import 'category_comics_page.dart'; import 'comic_source_page.dart'; class CategoriesPage extends StatefulWidget { @@ -147,43 +145,6 @@ class _CategoryPage extends StatelessWidget { 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 Widget build(BuildContext context) { var children = []; @@ -194,11 +155,11 @@ class _CategoryPage extends StatelessWidget { child: Wrap( children: [ if (data.enableRankingPage) - buildTag("Ranking".tl, (p0, p1) { + buildTag("Ranking".tl, () { context.to(() => RankingPage(categoryKey: data.key)); }), 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, children: [ buildTitleWithRefresh(part.title, () => updater(() {})), - buildTagsWithParams( - part.categories, - part.categoryParams, - part.title, - (key, param) => handleClick( - key, - param, - part.categoryType, - part.title, - category, - ), - ) + buildTags(part.categories) ], ); })); } else { children.add(buildTitle(part.title)); children.add( - buildTagsWithParams( - part.categories, - part.categoryParams, - part.title, - (tag, param) => handleClick( - tag, - param, - part.categoryType, - part.title, - data.key, - ), - ), + buildTags(part.categories), ); } } @@ -280,30 +219,28 @@ class _CategoryPage extends StatelessWidget { ); } - Widget buildTagsWithParams( - List tags, - List? params, - String? namespace, - ClickTagCallback onClick, + Widget buildTags( + List categories, ) { return Padding( padding: const EdgeInsets.fromLTRB(10, 0, 10, 16), child: Wrap( children: List.generate( - tags.length, - (index) => buildTag( - tags[index], - onClick, - namespace, - params?.elementAtOrNull(index), - ), + categories.length, + (index) => buildCategory(categories[index]), ), ), ); } - Widget buildTag(String tag, ClickTagCallback onClick, - [String? namespace, String? param]) { + Widget buildCategory(CategoryItem c) { + return buildTag(c.label, () { + var context = App.mainNavigatorKey!.currentContext!; + c.target.jump(context); + }); + } + + Widget buildTag(String label, VoidCallback onClick) { return Padding( padding: const EdgeInsets.fromLTRB(8, 6, 8, 6), child: Builder( @@ -313,10 +250,10 @@ class _CategoryPage extends StatelessWidget { color: context.colorScheme.primaryContainer.toOpacity(0.72), child: InkWell( borderRadius: const BorderRadius.all(Radius.circular(8)), - onTap: () => onClick(tag, param), + onTap: onClick, child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), - child: Text(tag), + child: Text(label), ), ), ); diff --git a/lib/pages/category_comics_page.dart b/lib/pages/category_comics_page.dart index 33b12cf..f29aaaf 100644 --- a/lib/pages/category_comics_page.dart +++ b/lib/pages/category_comics_page.dart @@ -9,6 +9,7 @@ class CategoryComicsPage extends StatefulWidget { required this.category, this.param, required this.categoryKey, + this.options, super.key, }); @@ -18,6 +19,8 @@ class CategoryComicsPage extends StatefulWidget { final String categoryKey; + final List? options; + @override State createState() => _CategoryComicsPageState(); } @@ -31,6 +34,9 @@ class _CategoryComicsPageState extends State { void findData() { for (final source in ComicSource.all()) { if (source.categoryData?.key == widget.categoryKey) { + if (source.categoryComicsData == null) { + throw "The comic source ${source.name} does not support category comics"; + } data = source.categoryComicsData!; options = data.options.where((element) { if (element.notShowWhen.contains(widget.category)) { @@ -40,7 +46,16 @@ class _CategoryComicsPageState extends State { } return true; }).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.filled(options.length, ""); + for (var i = 0; i < options.length; i++) { + newOptionsValue[i] = + optionsValue.elementAtOrNull(i) ?? defaultOptionsValue[i]; + } + optionsValue = newOptionsValue; + } sourceKey = source.key; return; } @@ -50,6 +65,11 @@ class _CategoryComicsPageState extends State { @override void initState() { + if (widget.options != null) { + optionsValue = widget.options!; + } else { + optionsValue = []; + } findData(); super.initState(); } diff --git a/lib/pages/comic_details_page/actions.dart b/lib/pages/comic_details_page/actions.dart index 90f0a35..b71a0bf 100644 --- a/lib/pages/comic_details_page/actions.dart +++ b/lib/pages/comic_details_page/actions.dart @@ -294,27 +294,9 @@ abstract mixin class _ComicPageActions { } void onTapTag(String tag, String namespace) { - var config = comicSource.handleClickTagEvent?.call(namespace, tag) ?? - { - 'action': 'search', - 'keyword': tag, - }; + var target = comicSource.handleClickTagEvent?.call(namespace, tag); var context = App.mainNavigatorKey!.currentContext!; - if (config['action'] == 'search') { - 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'], - ), - ); - } + target?.jump(context); } void showMoreActions() { diff --git a/lib/pages/comic_details_page/chapters.dart b/lib/pages/comic_details_page/chapters.dart index 10ece3d..7d86757 100644 --- a/lib/pages/comic_details_page/chapters.dart +++ b/lib/pages/comic_details_page/chapters.dart @@ -105,7 +105,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> { var value = chapters[key]!; bool visited = (history?.readEpisode ?? {}).contains(i + 1); return Padding( - padding: const EdgeInsets.fromLTRB(6, 4, 6, 4), + padding: const EdgeInsets.fromLTRB(4, 4, 4, 4), child: Material( color: context.colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(16), @@ -113,7 +113,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> { onTap: () => state.read(i + 1), borderRadius: BorderRadius.circular(16), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 8), child: Center( child: Text( value, @@ -134,7 +134,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> { }, ), gridDelegate: const SliverGridDelegateWithFixedHeight( - maxCrossAxisExtent: 200, + maxCrossAxisExtent: 250, itemHeight: 48, ), ).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)), @@ -300,15 +300,15 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters> history!.readEpisode.contains(rawIndex); } return Padding( - padding: const EdgeInsets.fromLTRB(6, 4, 6, 4), + padding: const EdgeInsets.fromLTRB(4, 4, 4, 4), child: Material( - color: context.colorScheme.surfaceContainer, - borderRadius: BorderRadius.circular(16), + color: context.colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(12), child: InkWell( onTap: () => state.read(chapterIndex + 1), - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(12), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 8), child: Center( child: Text( value, @@ -329,7 +329,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters> }, ), gridDelegate: const SliverGridDelegateWithFixedHeight( - maxCrossAxisExtent: 200, + maxCrossAxisExtent: 250, itemHeight: 48, ), ).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)), diff --git a/lib/pages/comic_details_page/comic_page.dart b/lib/pages/comic_details_page/comic_page.dart index 99745a8..aa05355 100644 --- a/lib/pages/comic_details_page/comic_page.dart +++ b/lib/pages/comic_details_page/comic_page.dart @@ -17,10 +17,8 @@ import 'package:venera/foundation/image_provider/cached_image.dart'; import 'package:venera/foundation/local.dart'; import 'package:venera/foundation/res.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/reader/reader.dart'; -import 'package:venera/pages/search_result_page.dart'; import 'package:venera/utils/app_links.dart'; import 'package:venera/utils/ext.dart'; import 'package:venera/utils/io.dart'; @@ -411,14 +409,20 @@ class _ComicPageState extends LoadingState var group = history!.group; String text; if (haveChapter) { - var epName = group == null - ? comic.chapters!.titles.elementAt( - math.min(ep - 1, comic.chapters!.length - 1), - ) - : comic.chapters! - .getGroupByIndex(group - 1) - .values - .elementAt(ep - 1); + var epName = "E$ep"; + try { + epName = group == null + ? comic.chapters!.titles.elementAt( + math.min(ep - 1, comic.chapters!.length - 1), + ) + : comic.chapters! + .getGroupByIndex(group - 1) + .values + .elementAt(ep - 1); + } + catch(e) { + // ignore + } text = "${"Last Reading".tl}: $epName P$page"; } else { text = "${"Last Reading".tl}: P$page"; @@ -461,7 +465,8 @@ class _ComicPageState extends LoadingState if (comic.tags.isEmpty && comic.uploader == null && comic.uploadTime == null && - comic.uploadTime == null) { + comic.uploadTime == null && + comic.maxPage == null) { return const SliverPadding(padding: EdgeInsets.zero); } @@ -625,6 +630,13 @@ class _ComicPageState extends LoadingState 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 Divider(), ], diff --git a/lib/pages/comic_details_page/comments_page.dart b/lib/pages/comic_details_page/comments_page.dart index 0240c46..5772929 100644 --- a/lib/pages/comic_details_page/comments_page.dart +++ b/lib/pages/comic_details_page/comments_page.dart @@ -99,61 +99,67 @@ class _CommentsPageState extends State { return Column( children: [ Expanded( - child: ListView.builder( - primary: false, - padding: EdgeInsets.zero, - itemCount: _comments!.length + 2, - itemBuilder: (context, index) { - if (index == 0) { - if (widget.replyComment != null) { - return Column( - children: [ - _CommentTile( - comment: widget.replyComment!, - source: widget.source, - comic: widget.data, - showAvatar: showAvatar, - showActions: false, - ), - const SizedBox(height: 8), - Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - color: context.colorScheme.outlineVariant, - width: 0.6, + child: SmoothScrollProvider( + builder: (context, controller, physics) { + return ListView.builder( + controller: controller, + physics: physics, + primary: false, + padding: EdgeInsets.zero, + itemCount: _comments!.length + 2, + itemBuilder: (context, index) { + if (index == 0) { + if (widget.replyComment != null) { + return Column( + children: [ + _CommentTile( + comment: widget.replyComment!, + source: widget.source, + comic: widget.data, + showAvatar: showAvatar, + showActions: false, + ), + const SizedBox(height: 8), + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: context.colorScheme.outlineVariant, + width: 0.6, + ), + ), + ), + child: Text( + "Replies".tl, + style: ts.s18, ), ), - ), - child: Text( - "Replies".tl, - style: ts.s18, - ), - ), - ], + ], + ); + } else { + return const SizedBox(); + } + } + index--; + + if (index == _comments!.length) { + if (_page < (maxPage ?? _page + 1)) { + loadMore(); + return const ListLoadingIndicator(); + } else { + return const SizedBox(); + } + } + + return _CommentTile( + comment: _comments![index], + source: widget.source, + comic: widget.data, + showAvatar: showAvatar, ); - } else { - return const SizedBox(); - } - } - index--; - - if (index == _comments!.length) { - if (_page < (maxPage ?? _page + 1)) { - loadMore(); - return const ListLoadingIndicator(); - } else { - return const SizedBox(); - } - } - - return _CommentTile( - comment: _comments![index], - source: widget.source, - comic: widget.data, - showAvatar: showAvatar, + }, ); }, ), diff --git a/lib/pages/comic_source_page.dart b/lib/pages/comic_source_page.dart index 96fd888..49dd89f 100644 --- a/lib/pages/comic_source_page.dart +++ b/lib/pages/comic_source_page.dart @@ -374,8 +374,35 @@ class _ComicSourceListState extends State<_ComicSourceList> { } else { var currentKey = ComicSource.all().map((e) => e.key).toList(); return ListView.builder( - itemCount: json!.length, + itemCount: json!.length + 1, 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 action = currentKey.contains(key) ? const Icon(Icons.check, size: 20).paddingRight(8) @@ -403,9 +430,14 @@ class _ComicSourceListState extends State<_ComicSourceList> { }, ).fixHeight(32); + var description = json![index]["version"]; + if (json![index]["description"] != null) { + description = "$description\n${json![index]["description"]}"; + } + return ListTile( title: Text(json![index]["name"]), - subtitle: Text(json![index]["version"]), + subtitle: Text(description), trailing: action, ); }, @@ -461,6 +493,7 @@ void _addAllPagesWithComicSource(ComicSource source) { var explorePages = appdata.settings['explore_pages']; var categoryPages = appdata.settings['categories']; var networkFavorites = appdata.settings['favorites']; + var searchPages = appdata.settings['searchSources']; if (source.explorePages.isNotEmpty) { for (var page in source.explorePages) { @@ -477,10 +510,15 @@ void _addAllPagesWithComicSource(ComicSource source) { !networkFavorites.contains(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['categories'] = categoryPages.toSet().toList(); appdata.settings['favorites'] = networkFavorites.toSet().toList(); + appdata.settings['searchSources'] = searchPages.toSet().toList(); appdata.saveData(); } diff --git a/lib/pages/explore_page.dart b/lib/pages/explore_page.dart index 415d7d0..d8dba9b 100644 --- a/lib/pages/explore_page.dart +++ b/lib/pages/explore_page.dart @@ -6,13 +6,10 @@ import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/global_state.dart'; import 'package:venera/foundation/res.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/utils/ext.dart'; import 'package:venera/utils/translations.dart'; -import 'category_comics_page.dart'; - class ExplorePage extends StatefulWidget { const ExplorePage({super.key}); @@ -445,30 +442,7 @@ Iterable _buildExplorePagePart( TextButton( onPressed: () { var context = App.mainNavigatorKey!.currentContext!; - if (part.viewMore!.startsWith("search:")) { - 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, - ), - ); - } + part.viewMore!.jump(context); }, child: Text("View more".tl), ) diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 579e162..4b0b2d7 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -52,7 +52,7 @@ class _SearchBar extends StatelessWidget { Widget build(BuildContext context) { return SliverToBoxAdapter( child: Container( - height: 52, + height: App.isMobile ? 52 : 46, width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Material( @@ -942,7 +942,7 @@ class _ImageFavoritesState extends State { displayType = type; }); await Future.delayed(const Duration(milliseconds: 20)); - var scrollController = ScrollControllerProvider.of(context); + var scrollController = ScrollState.of(context).controller; scrollController.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 200), diff --git a/lib/pages/reader/images.dart b/lib/pages/reader/images.dart index e2693e4..a33aeac 100644 --- a/lib/pages/reader/images.dart +++ b/lib/pages/reader/images.dart @@ -43,9 +43,10 @@ class _ReaderImagesState extends State<_ReaderImages> { }); } } else { + var cp = reader.widget.chapters?.ids.elementAtOrNull(reader.chapter - 1); var res = await reader.type.comicSource!.loadComicPages!( reader.widget.cid, - reader.widget.chapters?.ids.elementAt(reader.chapter - 1), + cp, ); if (res.error) { setState(() { @@ -343,10 +344,19 @@ class _GalleryModeState extends State<_GalleryMode> } var photoViewController = photoViewControllers[reader.page]!; 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( target, - Offset(size.width / 2 - location.dx, size.height / 2 - location.dy), + zoomPosition, ); isLongPressing = true; } @@ -608,6 +618,13 @@ class _ContinuousModeState extends State<_ContinuousMode> } bool onScaleUpdate([double? scale]) { + if (prepareToNextChapter || prepareToPrevChapter) { + setState(() { + prepareToPrevChapter = false; + prepareToNextChapter = false; + }); + context.readerScaffold.setFloatingButton(0); + } var isZoomedIn = (scale ?? photoViewController.scale) != 1.0; if (isZoomedIn != this.isZoomedIn) { setState(() { @@ -731,7 +748,8 @@ class _ContinuousModeState extends State<_ContinuousMode> } Offset offset; 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); } else { if (reader.mode == ReaderMode.continuousTopToBottom) { @@ -759,7 +777,10 @@ class _ContinuousModeState extends State<_ContinuousMode> 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.position.pixels <= scrollController.position.minScrollExtent && @@ -800,8 +821,8 @@ class _ContinuousModeState extends State<_ContinuousMode> }, child: widget, ); - var width = MediaQuery.of(context).size.width; - var height = MediaQuery.of(context).size.height; + var width = reader.size.width; + var height = reader.size.height; if (appdata.settings['limitImageWidth'] && width / height > 0.7 && reader.mode == ReaderMode.continuousTopToBottom) { @@ -882,9 +903,19 @@ class _ContinuousModeState extends State<_ContinuousMode> return; } 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( target, - Offset(0, 0), + zoomPosition, ); onScaleUpdate(target); isLongPressing = true; diff --git a/lib/pages/reader/reader.dart b/lib/pages/reader/reader.dart index 5321f10..9810285 100644 --- a/lib/pages/reader/reader.dart +++ b/lib/pages/reader/reader.dart @@ -309,6 +309,13 @@ class _ReaderState extends State } 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 { @@ -363,8 +370,24 @@ abstract mixin class _VolumeListener { bool toPrevPage(); + bool toNextChapter(); + + bool toPrevChapter(); + VolumeListener? volumeListener; + void onDown() { + if (!toNextPage()) { + toNextChapter(); + } + } + + void onUp() { + if (!toPrevPage()) { + toPrevChapter(); + } + } + void handleVolumeEvent() { if (!App.isAndroid) { // Currently only support Android @@ -374,8 +397,8 @@ abstract mixin class _VolumeListener { volumeListener?.cancel(); } volumeListener = VolumeListener( - onDown: toNextPage, - onUp: toPrevPage, + onDown: onDown, + onUp: onUp, )..listen(); } diff --git a/lib/pages/search_result_page.dart b/lib/pages/search_result_page.dart index 94f700d..c0abf46 100644 --- a/lib/pages/search_result_page.dart +++ b/lib/pages/search_result_page.dart @@ -441,6 +441,11 @@ class _SearchSettingsDialogState extends State<_SearchSettingsDialog> { @override 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( title: "Settings".tl, content: Column( @@ -452,7 +457,7 @@ class _SearchSettingsDialogState extends State<_SearchSettingsDialog> { Wrap( spacing: 8, runSpacing: 8, - children: ComicSource.all().map((e) { + children: sources.map((e) { return OptionChip( text: e.name.tl, isSelected: searchTarget == e.key, diff --git a/lib/pages/settings/app.dart b/lib/pages/settings/app.dart index e190d14..a6f91f8 100644 --- a/lib/pages/settings/app.dart +++ b/lib/pages/settings/app.dart @@ -140,17 +140,6 @@ class _AppSettingsState extends State { }, actionTitle: 'Set'.tl, ).toSliver(), - _SettingPartTitle( - title: "Log".tl, - icon: Icons.error_outline, - ), - _CallbackSetting( - title: "Open Log".tl, - callback: () { - context.to(() => const LogsPage()); - }, - actionTitle: 'Open'.tl, - ).toSliver(), _SettingPartTitle( title: "User".tl, icon: Icons.person_outline, diff --git a/lib/pages/settings/debug.dart b/lib/pages/settings/debug.dart new file mode 100644 index 0000000..232861b --- /dev/null +++ b/lib/pages/settings/debug.dart @@ -0,0 +1,95 @@ +part of 'settings_page.dart'; + +class DebugPage extends StatefulWidget { + const DebugPage({super.key}); + + @override + State createState() => DebugPageState(); +} + +class DebugPageState extends State { + 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), + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages/settings/reader.dart b/lib/pages/settings/reader.dart index fc282ed..ea04a78 100644 --- a/lib/pages/settings/reader.dart +++ b/lib/pages/settings/reader.dart @@ -48,6 +48,7 @@ class _ReaderSettingsState extends State { "continuousTopToBottom": "Continuous (Top to Bottom)".tl, }, onChanged: () { + setState(() {}); var readerMode = appdata.settings['readerMode']; if (readerMode?.toLowerCase().startsWith('continuous') ?? false) { appdata.settings['readerScreenPicNumberForLandscape'] = 1; @@ -68,67 +69,55 @@ class _ReaderSettingsState extends State { widget.onChanged?.call("autoPageTurningInterval"); }, ).toSliver(), - 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( - title: "The number of pic in screen for landscape (Only Gallery Mode)".tl, - settingsIndex: "readerScreenPicNumberForLandscape", - interval: 1, - min: 1, - max: 5, - onChanged: () { - widget.onChanged?.call("readerScreenPicNumberForLandscape"); - }, - ), - ), + SliverAnimatedVisibility( + visible: appdata.settings['readerMode']!.startsWith('gallery'), + child: _SliderSetting( + title: + "The number of pic in screen for landscape (Only Gallery Mode)" + .tl, + settingsIndex: "readerScreenPicNumberForLandscape", + interval: 1, + min: 1, + max: 5, + onChanged: () { + widget.onChanged?.call("readerScreenPicNumberForLandscape"); + }, ), ), - 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( - title: "The number of pic in screen for portrait (Only Gallery Mode)".tl, - settingsIndex: "readerScreenPicNumberForPortrait", - interval: 1, - min: 1, - max: 5, - onChanged: () { - widget.onChanged?.call("readerScreenPicNumberForPortrait"); - }, - ), - ), + SliverAnimatedVisibility( + visible: appdata.settings['readerMode']!.startsWith('gallery'), + child: _SliderSetting( + title: + "The number of pic in screen for portrait (Only Gallery Mode)" + .tl, + settingsIndex: "readerScreenPicNumberForPortrait", + interval: 1, + min: 1, + max: 5, + onChanged: () { + widget.onChanged?.call("readerScreenPicNumberForPortrait"); + }, ), ), _SwitchSetting( title: 'Long press to zoom'.tl, settingKey: 'enableLongPressToZoom', onChanged: () { + setState(() {}); widget.onChanged?.call('enableLongPressToZoom'); }, ).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( title: 'Limit image width'.tl, subtitle: 'When using Continuous(Top to Bottom) mode'.tl, diff --git a/lib/pages/settings/settings_page.dart b/lib/pages/settings/settings_page.dart index cb79750..6adbfca 100644 --- a/lib/pages/settings/settings_page.dart +++ b/lib/pages/settings/settings_page.dart @@ -30,6 +30,7 @@ part 'local_favorites.dart'; part 'app.dart'; part 'about.dart'; part 'network.dart'; +part 'debug.dart'; class SettingsPage extends StatefulWidget { const SettingsPage({this.initialPage = -1, super.key}); @@ -55,6 +56,7 @@ class _SettingsPageState extends State implements PopEntry { "APP", "Network", "About", + "Debug" ]; final icons = [ @@ -64,7 +66,8 @@ class _SettingsPageState extends State implements PopEntry { Icons.collections_bookmark_rounded, Icons.apps, Icons.public, - Icons.info + Icons.info, + Icons.bug_report, ]; double offset = 0; @@ -246,6 +249,9 @@ class _SettingsPageState extends State implements PopEntry { } void handlePointerDown(PointerDownEvent event) { + if (!App.isIOS) { + return; + } if (event.position.dx < 20) { gestureRecognizer.addPointer(event); } @@ -350,6 +356,7 @@ class _SettingsPageState extends State implements PopEntry { 4 => const AppSettings(), 5 => const NetworkSettings(), 6 => const AboutSettings(), + 7 => const DebugPage(), _ => throw UnimplementedError() }; } diff --git a/lib/utils/data_sync.dart b/lib/utils/data_sync.dart index 04cf28b..12bd7cc 100644 --- a/lib/utils/data_sync.dart +++ b/lib/utils/data_sync.dart @@ -1,4 +1,6 @@ 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/appdata.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:webdav_client/webdav_client.dart' hide File; import 'package:rhttp/rhttp.dart' as rhttp; +import 'package:venera/utils/translations.dart'; import 'io.dart'; @@ -20,6 +23,10 @@ class DataSync with ChangeNotifier { } LocalFavoritesManager().addListener(onDataChanged); ComicSourceManager().addListener(onDataChanged); + Future.delayed(const Duration(seconds: 1), () { + var controller = WindowFrame.of(App.rootContext); + controller.addCloseListener(_handleWindowClose); + }); } 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; factory DataSync() => instance ?? (instance = DataSync._()); @@ -100,6 +129,7 @@ class DataSync with ChangeNotifier { rhttp.ClientSettings( proxySettings: proxy == null ? null : rhttp.ProxySettings.proxy(proxy), + userAgent: "venera v${App.version}", ), ), ); @@ -172,6 +202,7 @@ class DataSync with ChangeNotifier { rhttp.ClientSettings( proxySettings: proxy == null ? null : rhttp.ProxySettings.proxy(proxy), + userAgent: "venera v${App.version}", ), ), ); diff --git a/lib/utils/io.dart b/lib/utils/io.dart index 98b39dc..288b5a1 100644 --- a/lib/utils/io.dart +++ b/lib/utils/io.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; @@ -132,25 +131,28 @@ extension DirectoryExtension on Directory { } /// 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('.')) { 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 sanitizedFileName = fileName.replaceAll(invalidChars, ' '); var trimmedFileName = sanitizedFileName.trim(); if (trimmedFileName.isEmpty) { throw Exception('Invalid File Name: Empty length.'); } - while (true) { - final bytes = utf8.encode(trimmedFileName); - if (bytes.length > maxLength) { - trimmedFileName = - trimmedFileName.substring(0, trimmedFileName.length - 1); - } else { - break; - } + if (maxLength <= 0) { + throw Exception('Invalid File Name: Max length is less than 0.'); + } + if (trimmedFileName.length > maxLength) { + trimmedFileName = trimmedFileName.substring(0, maxLength); } return trimmedFileName; } diff --git a/linux/my_application.cc b/linux/my_application.cc index 274826b..8d3f6d5 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -80,6 +80,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_default_size(window, 1280, 720); GdkVisual* visual; gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE); + gtk_window_set_decorated(window, FALSE); visual = gdk_screen_get_rgba_visual(screen); if (visual != NULL && gdk_screen_is_composited(screen)) { gtk_widget_set_visual(GTK_WIDGET(window), visual); diff --git a/pubspec.lock b/pubspec.lock index ecbe6fc..8fa2170 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,8 +433,8 @@ packages: dependency: "direct main" description: path: "." - ref: "690a03a954f1603e0149cfd479c8961b88f21336" - resolved-ref: "690a03a954f1603e0149cfd479c8961b88f21336" + ref: fe182cdf40e5fa6230f451bc1d643b860f610d13 + resolved-ref: fe182cdf40e5fa6230f451bc1d643b860f610d13 url: "https://github.com/venera-app/flutter_saf" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index d7ee5af..9d9ff98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: venera description: "A comic app." publish_to: 'none' -version: 1.3.4+134 +version: 1.4.0+140 environment: sdk: '>=3.6.0 <4.0.0' @@ -72,7 +72,7 @@ dependencies: flutter_saf: git: url: https://github.com/venera-app/flutter_saf - ref: 690a03a954f1603e0149cfd479c8961b88f21336 + ref: fe182cdf40e5fa6230f451bc1d643b860f610d13 dynamic_color: ^1.7.0 shimmer_animation: ^2.1.0 flutter_memory_info: ^0.0.1 diff --git a/windows/build_arm64.iss b/windows/build_arm64.iss index db4fc70..4e1d1dc 100644 --- a/windows/build_arm64.iss +++ b/windows/build_arm64.iss @@ -2,11 +2,11 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Venera" -#define MyAppVersion "1.3.4" +#define MyAppVersion "{{version}}" #define MyAppPublisher "nyne" #define MyAppURL "https://github.com/venera-app/venera" #define MyAppExeName "venera.exe" -#define RootPath "D:\code\venera" +#define RootPath "{{root_path}}" [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.