From dfd15ed34ac76dd00459cf1c01110de1b7be87c1 Mon Sep 17 00:00:00 2001 From: nyne Date: Sat, 26 Apr 2025 10:23:18 +0800 Subject: [PATCH 1/7] Fix an issue where folders were not fully displayed on the favorites page. --- lib/pages/favorites/side_bar.dart | 158 ++++++++++++++++-------------- 1 file changed, 83 insertions(+), 75 deletions(-) diff --git a/lib/pages/favorites/side_bar.dart b/lib/pages/favorites/side_bar.dart index 71f2c9b..024edc1 100644 --- a/lib/pages/favorites/side_bar.dart +++ b/lib/pages/favorites/side_bar.dart @@ -86,51 +86,10 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { padding: widget.withAppbar ? EdgeInsets.zero : EdgeInsets.only(top: context.padding.top), - itemCount: folders.length + networkFolders.length + 2, + itemCount: folders.length + networkFolders.length + 3, itemBuilder: (context, index) { if (index == 0) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - Icon( - Icons.local_activity, - color: context.colorScheme.secondary, - ), - const SizedBox(width: 12), - Text("Local".tl), - const Spacer(), - MenuButton( - entries: [ - MenuEntry( - icon: Icons.add, - text: 'Create Folder'.tl, - onClick: () { - newFolder().then((value) { - setState(() { - folders = - LocalFavoritesManager().folderNames; - }); - }); - }, - ), - MenuEntry( - icon: Icons.reorder, - text: 'Sort'.tl, - onClick: () { - sortFolders().then((value) { - setState(() { - folders = - LocalFavoritesManager().folderNames; - }); - }); - }, - ), - ], - ), - ], - ).paddingHorizontal(16), - ); + return buildLocalTitle(); } index--; if (index == 0) { @@ -142,38 +101,7 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { } index -= folders.length; if (index == 0) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 12), - margin: const EdgeInsets.only(top: 8), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - color: context.colorScheme.outlineVariant, - width: 0.6, - ), - ), - ), - child: Row( - children: [ - Icon( - Icons.cloud, - color: context.colorScheme.secondary, - ), - const SizedBox(width: 12), - Text("Network".tl), - const Spacer(), - IconButton( - icon: const Icon(Icons.settings), - onPressed: () { - showPopUpWidget( - App.rootContext, - setFavoritesPagesWidget(), - ); - }, - ), - ], - ).paddingHorizontal(16), - ); + return buildNetworkTitle(); } index--; return buildNetworkFolder(networkFolders[index]); @@ -185,6 +113,86 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { ); } + Widget buildLocalTitle() { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + Icon( + Icons.local_activity, + color: context.colorScheme.secondary, + ), + const SizedBox(width: 12), + Text("Local".tl), + const Spacer(), + MenuButton( + entries: [ + MenuEntry( + icon: Icons.add, + text: 'Create Folder'.tl, + onClick: () { + newFolder().then((value) { + setState(() { + folders = + LocalFavoritesManager().folderNames; + }); + }); + }, + ), + MenuEntry( + icon: Icons.reorder, + text: 'Sort'.tl, + onClick: () { + sortFolders().then((value) { + setState(() { + folders = + LocalFavoritesManager().folderNames; + }); + }); + }, + ), + ], + ), + ], + ).paddingHorizontal(16), + ); + } + + Widget buildNetworkTitle() { + return Container( + padding: const EdgeInsets.symmetric(vertical: 12), + margin: const EdgeInsets.only(top: 8), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: context.colorScheme.outlineVariant, + width: 0.6, + ), + ), + ), + child: Row( + children: [ + Icon( + Icons.cloud, + color: context.colorScheme.secondary, + ), + const SizedBox(width: 12), + Text("Network".tl), + const Spacer(), + IconButton( + icon: const Icon(Icons.settings), + onPressed: () { + showPopUpWidget( + App.rootContext, + setFavoritesPagesWidget(), + ); + }, + ), + ], + ).paddingHorizontal(16), + ); + } + Widget buildLocalFolder(String name) { bool isSelected = name == favPage.folder && !favPage.isNetwork; return InkWell( From 9ff68d07011e0e7a66a565c3b1076b1eb13d3c73 Mon Sep 17 00:00:00 2001 From: nyne Date: Mon, 28 Apr 2025 19:40:12 +0800 Subject: [PATCH 2/7] Improve local favorites performance. --- lib/foundation/favorites.dart | 73 ++++ lib/pages/favorites/favorites_page.dart | 2 + lib/pages/favorites/local_favorites_page.dart | 346 +++++++++++------- lib/utils/ext.dart | 11 + 4 files changed, 302 insertions(+), 130 deletions(-) diff --git a/lib/foundation/favorites.dart b/lib/foundation/favorites.dart index ad2baf6..2fce8f5 100644 --- a/lib/foundation/favorites.dart +++ b/lib/foundation/favorites.dart @@ -1,4 +1,6 @@ import 'dart:convert'; +import 'dart:ffi'; +import 'dart:isolate'; import 'package:flutter/foundation.dart'; import 'package:sqlite3/sqlite3.dart'; @@ -209,7 +211,22 @@ class LocalFavoritesManager with ChangeNotifier { late Database _db; + late Map counts; + + int get totalComics { + int total = 0; + for (var t in counts.values) { + total += t; + } + return total; + } + + int folderComics(String folder) { + return counts[folder] ?? 0; + } + Future init() async { + counts = {}; _db = sqlite3.open("${App.dataPath}/local_favorite.db"); _db.execute(""" create table if not exists folder_order ( @@ -256,6 +273,13 @@ class LocalFavoritesManager with ChangeNotifier { } else { appdata.settings['followUpdatesFolder'] = null; } + initCounts(); + } + + void initCounts() { + for (var folder in folderNames) { + counts[folder] = count(folder); + } } List find(String id, ComicType type) { @@ -357,6 +381,23 @@ class LocalFavoritesManager with ChangeNotifier { return rows.map((element) => FavoriteItem.fromRow(element)).toList(); } + static Future> _getFolderComicsAsync( + String folder, Pointer p) { + return Isolate.run(() { + var db = sqlite3.fromPointer(p); + var rows = db.select(""" + select * from "$folder" + ORDER BY display_order; + """); + return rows.map((element) => FavoriteItem.fromRow(element)).toList(); + }); + } + + /// Start a new isolate to get the comics in the folder + Future> getFolderComicsAsync(String folder) { + return _getFolderComicsAsync(folder, _db.handle); + } + List getAllComics() { var res = {}; for (final folder in folderNames) { @@ -368,6 +409,26 @@ class LocalFavoritesManager with ChangeNotifier { return res.toList(); } + static Future> _getAllComicsAsync( + List folders, Pointer p) { + return Isolate.run(() { + var db = sqlite3.fromPointer(p); + var res = {}; + for (final folder in folders) { + var comics = db.select(""" + select * from "$folder"; + """); + res.addAll(comics.map((element) => FavoriteItem.fromRow(element))); + } + return res.toList(); + }); + } + + /// Start a new isolate to get all the comics + Future> getAllComicsAsync() { + return _getAllComicsAsync(folderNames, _db.handle); + } + void addTagTo(String folder, String id, String tag) { _db.execute(""" update "$folder" @@ -433,6 +494,7 @@ class LocalFavoritesManager with ChangeNotifier { ); """); notifyListeners(); + counts[name] = 0; return name; } @@ -547,6 +609,11 @@ class LocalFavoritesManager with ChangeNotifier { """, [updateTime, comic.id, comic.type.value]); } } + if (counts[folder] == null) { + counts[folder] = count(folder); + } else { + counts[folder] = counts[folder]! + 1; + } notifyListeners(); return true; } @@ -596,6 +663,7 @@ class LocalFavoritesManager with ChangeNotifier { delete from folder_order where folder_name == ?; """, [name]); + counts.remove(name); notifyListeners(); } @@ -611,6 +679,11 @@ class LocalFavoritesManager with ChangeNotifier { delete from "$folder" where id == ? and type == ?; """, [id, type.value]); + if (counts[folder] != null) { + counts[folder] = counts[folder]! - 1; + } else { + counts[folder] = count(folder); + } notifyListeners(); } diff --git a/lib/pages/favorites/favorites_page.dart b/lib/pages/favorites/favorites_page.dart index aa14a64..664ec46 100644 --- a/lib/pages/favorites/favorites_page.dart +++ b/lib/pages/favorites/favorites_page.dart @@ -18,7 +18,9 @@ import 'package:venera/network/download.dart'; import 'package:venera/pages/comic_details_page/comic_page.dart'; import 'package:venera/pages/reader/reader.dart'; import 'package:venera/pages/settings/settings_page.dart'; +import 'package:venera/utils/ext.dart'; import 'package:venera/utils/io.dart'; +import 'package:venera/utils/tags_translation.dart'; import 'package:venera/utils/translations.dart'; part 'favorite_actions.dart'; diff --git a/lib/pages/favorites/local_favorites_page.dart b/lib/pages/favorites/local_favorites_page.dart index a807b08..7acd560 100644 --- a/lib/pages/favorites/local_favorites_page.dart +++ b/lib/pages/favorites/local_favorites_page.dart @@ -2,6 +2,10 @@ part of 'favorites_page.dart'; const _localAllFolderLabel = '^_^[%local_all%]^_^'; +/// If the number of comics in a folder exceeds this limit, it will be +/// fetched asynchronously. +const _asyncDataFetchLimit = 200; + class _LocalFavoritesPage extends StatefulWidget { const _LocalFavoritesPage({required this.folder, super.key}); @@ -35,40 +39,110 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { bool get isAllFolder => widget.folder == _localAllFolderLabel; + LocalFavoritesManager get manager => LocalFavoritesManager(); + + bool isLoading = false; + + var searchResults = []; + + void updateSearchResult() { + setState(() { + if (keyword.trim().isEmpty) { + searchResults = comics; + } else { + searchResults = []; + for (var comic in comics) { + if (matchKeyword(keyword, comic)) { + searchResults.add(comic); + } + } + } + }); + } + void updateComics() { - if (keyword.isEmpty) { - setState(() { - if (isAllFolder) { - comics = LocalFavoritesManager().getAllComics(); - } else { - comics = LocalFavoritesManager().getFolderComics(widget.folder); - } - }); + if (isLoading) return; + if (isAllFolder) { + var totalComics = manager.totalComics; + if (totalComics < _asyncDataFetchLimit) { + comics = manager.getAllComics(); + } else { + isLoading = true; + manager + .getAllComicsAsync() + .minTime(const Duration(milliseconds: 200)) + .then((value) { + if (mounted) { + setState(() { + isLoading = false; + comics = value; + }); + } + }); + } } else { - setState(() { - if (isAllFolder) { - comics = LocalFavoritesManager().search(keyword); - } else { - comics = - LocalFavoritesManager().searchInFolder(widget.folder, keyword); - } - }); + var folderComics = manager.folderComics(widget.folder); + if (folderComics < _asyncDataFetchLimit) { + comics = manager.getFolderComics(widget.folder); + } else { + isLoading = true; + manager + .getFolderComicsAsync(widget.folder) + .minTime(const Duration(milliseconds: 200)) + .then((value) { + if (mounted) { + setState(() { + isLoading = false; + comics = value; + }); + } + }); + } } + setState(() {}); + } + + bool matchKeyword(String keyword, FavoriteItem comic) { + var list = keyword.split(" "); + for (var k in list) { + if (k.isEmpty) continue; + if (comic.title.contains(k)) { + continue; + } else if (comic.subtitle != null && comic.subtitle!.contains(k)) { + continue; + } else if (comic.tags.any((tag) { + if (tag == k) { + return true; + } else if (tag.contains(':') && tag.split(':')[1] == k) { + return true; + } else if (App.locale.languageCode != 'en' && + tag.translateTagsToCN == k) { + return true; + } + return false; + })) { + continue; + } else if (comic.author == k) { + continue; + } + return false; + } + return true; } @override void initState() { favPage = context.findAncestorStateOfType<_FavoritesPageState>()!; if (!isAllFolder) { - comics = LocalFavoritesManager().getFolderComics(widget.folder); var (a, b) = LocalFavoritesManager().findLinked(widget.folder); networkSource = a; networkFolder = b; } else { - comics = LocalFavoritesManager().getAllComics(); networkSource = null; networkFolder = null; } + comics = []; + updateComics(); LocalFavoritesManager().addListener(updateComics); super.initState(); } @@ -215,7 +289,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { icon: const Icon(Icons.search), onPressed: () { setState(() { + keyword = ""; searchMode = true; + updateSearchResult(); }); }, ), @@ -411,9 +487,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { icon: const Icon(Icons.close), onPressed: () { setState(() { - searchMode = false; - keyword = ""; - updateComics(); + setState(() { + searchMode = false; + }); }); }, ), @@ -422,132 +498,142 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { autofocus: true, decoration: InputDecoration( hintText: "Search".tl, - border: InputBorder.none, + border: UnderlineInputBorder(), ), onChanged: (v) { keyword = v; - updateComics(); + updateSearchResult(); }, - ), + ).paddingBottom(8).paddingRight(8), ), - SliverGridComics( - comics: comics, - selections: selectedComics, - menuBuilder: (c) { - return [ - if (!isAllFolder) + if (isLoading) + SliverToBoxAdapter( + child: SizedBox( + height: 200, + child: const Center( + child: CircularProgressIndicator(), + ), + ), + ) + else + SliverGridComics( + comics: searchMode ? searchResults : comics, + selections: selectedComics, + menuBuilder: (c) { + return [ + if (!isAllFolder) + MenuEntry( + icon: Icons.delete, + text: "Delete".tl, + onClick: () { + LocalFavoritesManager().deleteComicWithId( + widget.folder, + c.id, + (c as FavoriteItem).type, + ); + }, + ), MenuEntry( - icon: Icons.delete, - text: "Delete".tl, + icon: Icons.check, + text: "Select".tl, onClick: () { - LocalFavoritesManager().deleteComicWithId( - widget.folder, - c.id, - (c as FavoriteItem).type, + setState(() { + if (!multiSelectMode) { + multiSelectMode = true; + } + if (selectedComics.containsKey(c as FavoriteItem)) { + selectedComics.remove(c); + _checkExitSelectMode(); + } else { + selectedComics[c] = true; + } + lastSelectedIndex = comics.indexOf(c); + }); + }, + ), + MenuEntry( + icon: Icons.download, + text: "Download".tl, + onClick: () { + downloadComic(c as FavoriteItem); + context.showMessage( + message: "Download started".tl, ); }, ), - MenuEntry( - icon: Icons.check, - text: "Select".tl, - onClick: () { - setState(() { - if (!multiSelectMode) { - multiSelectMode = true; - } - if (selectedComics.containsKey(c as FavoriteItem)) { - selectedComics.remove(c); - _checkExitSelectMode(); - } else { - selectedComics[c] = true; - } - lastSelectedIndex = comics.indexOf(c); - }); - }, - ), - MenuEntry( - icon: Icons.download, - text: "Download".tl, - onClick: () { - downloadComic(c as FavoriteItem); - context.showMessage( - message: "Download started".tl, - ); - }, - ), - if (appdata.settings["onClickFavorite"] == "viewDetail") - MenuEntry( - icon: Icons.menu_book_outlined, - text: "Read".tl, - onClick: () { - App.mainNavigatorKey?.currentContext?.to( - () => ReaderWithLoading( - id: c.id, - sourceKey: c.sourceKey, - ), - ); - }, - ), - ]; - }, - onTap: (c) { - if (multiSelectMode) { - setState(() { - if (selectedComics.containsKey(c as FavoriteItem)) { - selectedComics.remove(c); - _checkExitSelectMode(); - } else { - selectedComics[c] = true; - } - lastSelectedIndex = comics.indexOf(c); - }); - } else if (appdata.settings["onClickFavorite"] == "viewDetail") { - App.mainNavigatorKey?.currentContext - ?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey)); - } else { - App.mainNavigatorKey?.currentContext?.to( - () => ReaderWithLoading( - id: c.id, - sourceKey: c.sourceKey, - ), - ); - } - }, - onLongPressed: (c) { - setState(() { - if (!multiSelectMode) { - multiSelectMode = true; - if (!selectedComics.containsKey(c as FavoriteItem)) { - selectedComics[c] = true; - } - lastSelectedIndex = comics.indexOf(c); + if (appdata.settings["onClickFavorite"] == "viewDetail") + MenuEntry( + icon: Icons.menu_book_outlined, + text: "Read".tl, + onClick: () { + App.mainNavigatorKey?.currentContext?.to( + () => ReaderWithLoading( + id: c.id, + sourceKey: c.sourceKey, + ), + ); + }, + ), + ]; + }, + onTap: (c) { + if (multiSelectMode) { + setState(() { + if (selectedComics.containsKey(c as FavoriteItem)) { + selectedComics.remove(c); + _checkExitSelectMode(); + } else { + selectedComics[c] = true; + } + lastSelectedIndex = comics.indexOf(c); + }); + } else if (appdata.settings["onClickFavorite"] == "viewDetail") { + App.mainNavigatorKey?.currentContext + ?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey)); } else { - if (lastSelectedIndex != null) { - int start = lastSelectedIndex!; - int end = comics.indexOf(c as FavoriteItem); - if (start > end) { - int temp = start; - start = end; - end = temp; + App.mainNavigatorKey?.currentContext?.to( + () => ReaderWithLoading( + id: c.id, + sourceKey: c.sourceKey, + ), + ); + } + }, + onLongPressed: (c) { + setState(() { + if (!multiSelectMode) { + multiSelectMode = true; + if (!selectedComics.containsKey(c as FavoriteItem)) { + selectedComics[c] = true; } + lastSelectedIndex = comics.indexOf(c); + } else { + if (lastSelectedIndex != null) { + int start = lastSelectedIndex!; + int end = comics.indexOf(c as FavoriteItem); + if (start > end) { + int temp = start; + start = end; + end = temp; + } - for (int i = start; i <= end; i++) { - if (i == lastSelectedIndex) continue; + for (int i = start; i <= end; i++) { + if (i == lastSelectedIndex) continue; - var comic = comics[i]; - if (selectedComics.containsKey(comic)) { - selectedComics.remove(comic); - } else { - selectedComics[comic] = true; + var comic = comics[i]; + if (selectedComics.containsKey(comic)) { + selectedComics.remove(comic); + } else { + selectedComics[comic] = true; + } } } + lastSelectedIndex = comics.indexOf(c as FavoriteItem); } - lastSelectedIndex = comics.indexOf(c as FavoriteItem); - } - _checkExitSelectMode(); - }); - }, - ), + _checkExitSelectMode(); + }); + }, + ), ], ); body = AppScrollBar( diff --git a/lib/utils/ext.dart b/lib/utils/ext.dart index c4fa3ed..404e179 100644 --- a/lib/utils/ext.dart +++ b/lib/utils/ext.dart @@ -107,4 +107,15 @@ abstract class MapOrNull{ static Map? from(Map? i){ return i == null ? null : Map.from(i); } +} + +extension FutureExt on Future{ + /// Wrap the future to make sure it will return at least the duration. + Future minTime(Duration duration) async { + var res = await Future.wait([ + this, + Future.delayed(duration), + ]); + return res[0]; + } } \ No newline at end of file From 929c1a9d91008b0781c93886a42594014322dab0 Mon Sep 17 00:00:00 2001 From: nyne Date: Mon, 28 Apr 2025 19:46:29 +0800 Subject: [PATCH 3/7] Show comics count of a folder on sidebar. --- lib/pages/favorites/local_favorites_page.dart | 2 +- lib/pages/favorites/side_bar.dart | 37 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/pages/favorites/local_favorites_page.dart b/lib/pages/favorites/local_favorites_page.dart index 7acd560..7dfe925 100644 --- a/lib/pages/favorites/local_favorites_page.dart +++ b/lib/pages/favorites/local_favorites_page.dart @@ -4,7 +4,7 @@ const _localAllFolderLabel = '^_^[%local_all%]^_^'; /// If the number of comics in a folder exceeds this limit, it will be /// fetched asynchronously. -const _asyncDataFetchLimit = 200; +const _asyncDataFetchLimit = 500; class _LocalFavoritesPage extends StatefulWidget { const _LocalFavoritesPage({required this.folder, super.key}); diff --git a/lib/pages/favorites/side_bar.dart b/lib/pages/favorites/side_bar.dart index 024edc1..464ad75 100644 --- a/lib/pages/favorites/side_bar.dart +++ b/lib/pages/favorites/side_bar.dart @@ -133,8 +133,7 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { onClick: () { newFolder().then((value) { setState(() { - folders = - LocalFavoritesManager().folderNames; + folders = LocalFavoritesManager().folderNames; }); }); }, @@ -145,8 +144,7 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { onClick: () { sortFolders().then((value) { setState(() { - folders = - LocalFavoritesManager().folderNames; + folders = LocalFavoritesManager().folderNames; }); }); }, @@ -195,6 +193,15 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { Widget buildLocalFolder(String name) { bool isSelected = name == favPage.folder && !favPage.isNetwork; + int count = 0; + if (name == _localAllFolderLabel) { + count = LocalFavoritesManager().totalComics; + } else { + count = LocalFavoritesManager().folderComics(name); + } + var folderName = name == _localAllFolderLabel + ? "All".tl + : getFavoriteDataOrNull(name)?.title ?? name; return InkWell( onTap: () { if (isSelected) { @@ -219,9 +226,25 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { ), ), padding: const EdgeInsets.only(left: 16), - child: Text(name == _localAllFolderLabel - ? "All".tl - : getFavoriteDataOrNull(name)?.title ?? name), + child: Row( + children: [ + Expanded( + child: Text(folderName), + ), + Container( + margin: EdgeInsets.only(right: 8), + padding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: context.colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Text(count.toString()), + ), + ], + ), ), ); } From bf7b90313a305a72c0e759ce9d194718fcddf176 Mon Sep 17 00:00:00 2001 From: nyne Date: Mon, 28 Apr 2025 20:18:29 +0800 Subject: [PATCH 4/7] Fix invalid total page count. Close #348 --- lib/pages/reader/images.dart | 6 ++---- lib/pages/reader/reader.dart | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/pages/reader/images.dart b/lib/pages/reader/images.dart index 752606a..ba7e54f 100644 --- a/lib/pages/reader/images.dart +++ b/lib/pages/reader/images.dart @@ -119,7 +119,7 @@ class _GalleryModeState extends State<_GalleryMode> /// [totalPages] is the total number of pages in the current chapter. /// More than one images can be displayed on one page. int get totalPages { - if (!showSingleImageOnFirstPage) { + if (!reader.showSingleImageOnFirstPage) { return (reader.images!.length / reader.imagesPerPage).ceil(); } else { return 1 + @@ -144,11 +144,9 @@ class _GalleryModeState extends State<_GalleryMode> super.initState(); } - bool get showSingleImageOnFirstPage => appdata.settings["showSingleImageOnFirstPage"]; - /// Get the range of images for the given page. [page] is 1-based. (int start, int end) getPageImagesRange(int page) { - if (showSingleImageOnFirstPage) { + if (reader.showSingleImageOnFirstPage) { if (page == 1) { return (0, 1); } else { diff --git a/lib/pages/reader/reader.dart b/lib/pages/reader/reader.dart index 0686bf3..a199948 100644 --- a/lib/pages/reader/reader.dart +++ b/lib/pages/reader/reader.dart @@ -111,7 +111,13 @@ class _ReaderState extends State } @override - int get maxPage => ((images?.length ?? 1) / imagesPerPage).ceil(); + int get maxPage { + if (!showSingleImageOnFirstPage) { + return (images!.length / imagesPerPage).ceil(); + } else { + return 1 + ((images!.length - 1) / imagesPerPage).ceil(); + } + } ComicType get type => widget.type; @@ -125,7 +131,8 @@ class _ReaderState extends State late ReaderMode mode; @override - bool get isPortrait => MediaQuery.of(context).orientation == Orientation.portrait; + bool get isPortrait => + MediaQuery.of(context).orientation == Orientation.portrait; History? history; @@ -343,6 +350,9 @@ abstract mixin class _ImagePerPageHandler { } } + bool get showSingleImageOnFirstPage => + appdata.settings["showSingleImageOnFirstPage"]; + /// The number of images displayed on one screen int get imagesPerPage { if (mode.isContinuous) return 1; From b37ea01acadaa10a994ac1df6efbe7e86d7acf74 Mon Sep 17 00:00:00 2001 From: nyne Date: Tue, 29 Apr 2025 11:18:59 +0800 Subject: [PATCH 5/7] Add an option to disable double tap to zoom. --- lib/foundation/appdata.dart | 1 + lib/pages/reader/gesture.dart | 6 ++++++ lib/pages/reader/images.dart | 1 + lib/pages/reader/reader.dart | 3 +++ lib/pages/settings/reader.dart | 8 ++++++++ 5 files changed, 19 insertions(+) diff --git a/lib/foundation/appdata.dart b/lib/foundation/appdata.dart index 7b1a898..9c1f210 100644 --- a/lib/foundation/appdata.dart +++ b/lib/foundation/appdata.dart @@ -185,6 +185,7 @@ class Settings with ChangeNotifier { 'comicListDisplayMode': 'paging', // paging, continuous 'showPageNumberInReader': true, 'showSingleImageOnFirstPage': false, + 'enableDoubleTapToZoom': true, }; operator [](String key) { diff --git a/lib/pages/reader/gesture.dart b/lib/pages/reader/gesture.dart index 751af23..04a68e9 100644 --- a/lib/pages/reader/gesture.dart +++ b/lib/pages/reader/gesture.dart @@ -152,12 +152,18 @@ class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDet bool _dragInProgress = false; + bool get _enableDoubleTapToZoom => appdata.settings["enableDoubleTapToZoom"]; + void onTapUp(TapUpDetails event) { if (_longPressInProgress) { _longPressInProgress = false; return; } final location = event.globalPosition; + if (!_enableDoubleTapToZoom) { + onTap(location); + return; + } final previousLocation = _previousEvent?.globalPosition; if (previousLocation != null) { if ((location - previousLocation).distanceSquared < diff --git a/lib/pages/reader/images.dart b/lib/pages/reader/images.dart index ba7e54f..fe4be2b 100644 --- a/lib/pages/reader/images.dart +++ b/lib/pages/reader/images.dart @@ -250,6 +250,7 @@ class _GalleryModeState extends State<_GalleryMode> } return PhotoViewGalleryPageOptions.customChild( + childSize: reader.size * 2, controller: photoViewControllers[index], minScale: PhotoViewComputedScale.contained * 1.0, maxScale: PhotoViewComputedScale.covered * 10.0, diff --git a/lib/pages/reader/reader.dart b/lib/pages/reader/reader.dart index a199948..6b732ba 100644 --- a/lib/pages/reader/reader.dart +++ b/lib/pages/reader/reader.dart @@ -112,6 +112,9 @@ class _ReaderState extends State @override int get maxPage { + if (images == null) { + return 1; + } if (!showSingleImageOnFirstPage) { return (images!.length / imagesPerPage).ceil(); } else { diff --git a/lib/pages/settings/reader.dart b/lib/pages/settings/reader.dart index cce23e6..3b5744d 100644 --- a/lib/pages/settings/reader.dart +++ b/lib/pages/settings/reader.dart @@ -113,6 +113,14 @@ class _ReaderSettingsState extends State { }, ), ), + _SwitchSetting( + title: 'Double tap to zoom'.tl, + settingKey: 'enableDoubleTapToZoom', + onChanged: () { + setState(() {}); + widget.onChanged?.call('enableDoubleTapToZoom'); + }, + ).toSliver(), _SwitchSetting( title: 'Long press to zoom'.tl, settingKey: 'enableLongPressToZoom', From 146fc701433a3ab0b3baaf7c3e0290070b2b1a15 Mon Sep 17 00:00:00 2001 From: nyne Date: Tue, 29 Apr 2025 11:19:59 +0800 Subject: [PATCH 6/7] Update version code --- lib/foundation/app.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index 67c6ad7..87cc8a1 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.4.1"; + final version = "1.4.2"; bool get isAndroid => Platform.isAndroid; diff --git a/pubspec.yaml b/pubspec.yaml index 953bf25..708f5b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: venera description: "A comic app." publish_to: 'none' -version: 1.4.1+141 +version: 1.4.2+142 environment: sdk: '>=3.6.0 <4.0.0' From 6e14942dab51eea6d24bf88ec8d703dba9b114fc Mon Sep 17 00:00:00 2001 From: nyne Date: Tue, 29 Apr 2025 11:29:30 +0800 Subject: [PATCH 7/7] Add application category type to Info.plist --- ios/Runner/Info.plist | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index be8d5d6..bf6fc29 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -46,12 +46,14 @@ UIApplicationSupportsIndirectInputEvents NSPhotoLibraryUsageDescription - Choose images + Choose images UIFileSharingEnabled LSSupportsOpeningDocumentsInPlace NSFaceIDUsageDescription - Ensure that the operation is being performed by the user themselves. + Ensure that the operation is being performed by the user themselves. + LSApplicationCategoryType + public.app-category.books