Feat 为画廊模式添加每页显示图片数量的配置 (#82)

* Feat: Add dynamic image-per-page configuration for gallery mode

- Implemented a slider to configure the number of images displayed per page (1-5) in gallery mode.
- Updated the reader to dynamically reflect changes in the `imagesPerPage` setting without requiring a mode switch or reopening.
- Ensured compatibility with existing continuous reading mode.

* fix currentImagesPerPage

* fix Continuous mode

* improve readerScreenPicNumber setting disable view

* improve PhotoViewController
This commit is contained in:
buste
2024-12-01 19:56:38 +08:00
committed by GitHub
parent 60f7b4d3b0
commit 95c98eeaed
5 changed files with 130 additions and 27 deletions

View File

@@ -105,6 +105,7 @@
"Continuous (Right to Left)": "连续(从右到左)", "Continuous (Right to Left)": "连续(从右到左)",
"Continuous (Top to Bottom)": "连续(从上到下)", "Continuous (Top to Bottom)": "连续(从上到下)",
"Auto page turning interval": "自动翻页间隔", "Auto page turning interval": "自动翻页间隔",
"The number of pic in screen (Only Gallery Mode)": "同屏幕图片数量(仅画廊模式)",
"Theme Mode": "主题模式", "Theme Mode": "主题模式",
"System": "系统", "System": "系统",
"Light": "浅色", "Light": "浅色",
@@ -353,6 +354,7 @@
"Continuous (Right to Left)": "連續(從右到左)", "Continuous (Right to Left)": "連續(從右到左)",
"Continuous (Top to Bottom)": "連續(從上到下)", "Continuous (Top to Bottom)": "連續(從上到下)",
"Auto page turning interval": "自動翻頁間隔", "Auto page turning interval": "自動翻頁間隔",
"The number of pic in screen (Only Gallery Mode)": "同螢幕圖片數量(僅畫廊模式)",
"Theme Mode": "主題模式", "Theme Mode": "主題模式",
"System": "系統", "System": "系統",
"Light": "浅色", "Light": "浅色",

View File

@@ -106,6 +106,7 @@ class _Settings with ChangeNotifier {
'defaultSearchTarget': null, 'defaultSearchTarget': null,
'autoPageTurningInterval': 5, // in seconds 'autoPageTurningInterval': 5, // in seconds
'readerMode': 'galleryLeftToRight', // values of [ReaderMode] 'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
'readerScreenPicNumber': 1, // 1 - 5
'enableTapToTurnPages': true, 'enableTapToTurnPages': true,
'enablePageAnimation': true, 'enablePageAnimation': true,
'language': 'system', // system, zh-CN, zh-TW, en-US 'language': 'system', // system, zh-CN, zh-TW, en-US

View File

@@ -83,7 +83,8 @@ class _ReaderImagesState extends State<_ReaderImages> {
); );
} else { } else {
if (reader.mode.isGallery) { if (reader.mode.isGallery) {
return _GalleryMode(key: Key(reader.mode.key)); return _GalleryMode(
key: Key('${reader.mode.key}_${reader.imagesPerPage}'));
} else { } else {
return _ContinuousMode(key: Key(reader.mode.key)); return _ContinuousMode(key: Key(reader.mode.key));
} }
@@ -110,6 +111,10 @@ class _GalleryModeState extends State<_GalleryMode>
late _ReaderState reader; late _ReaderState reader;
int get totalPages => ((reader.images!.length + reader.imagesPerPage - 1) /
reader.imagesPerPage)
.ceil();
@override @override
void initState() { void initState() {
reader = context.reader; reader = context.reader;
@@ -124,8 +129,14 @@ class _GalleryModeState extends State<_GalleryMode>
void cache(int current) { void cache(int current) {
for (int i = current + 1; i <= current + preCacheCount; i++) { for (int i = current + 1; i <= current + preCacheCount; i++) {
if (i <= reader.maxPage && !cached[i]) { if (i <= totalPages && !cached[i]) {
_precacheImage(i, context); int startIndex = (i - 1) * reader.imagesPerPage;
int endIndex =
math.min(startIndex + reader.imagesPerPage, reader.images!.length);
for (int i = startIndex; i < endIndex; i++) {
precacheImage(
_createImageProviderFromKey(reader.images![i], context), context);
}
cached[i] = true; cached[i] = true;
} }
} }
@@ -141,32 +152,34 @@ class _GalleryModeState extends State<_GalleryMode>
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
? Axis.vertical ? Axis.vertical
: Axis.horizontal, : Axis.horizontal,
itemCount: reader.images!.length + 2, itemCount: totalPages + 2,
builder: (BuildContext context, int index) { builder: (BuildContext context, int index) {
ImageProvider? imageProvider; if (index == 0 || index == totalPages + 1) {
if (index != 0 && index != reader.images!.length + 1) {
imageProvider = _createImageProvider(index, context);
} else {
return PhotoViewGalleryPageOptions.customChild( return PhotoViewGalleryPageOptions.customChild(
scaleStateController: PhotoViewScaleStateController(), scaleStateController: PhotoViewScaleStateController(),
child: const SizedBox(), child: const SizedBox(),
); );
} } else {
int pageIndex = index - 1;
int startIndex = pageIndex * reader.imagesPerPage;
int endIndex = math.min(startIndex + reader.imagesPerPage, reader.images!.length);
List<String> pageImages = reader.images!.sublist(startIndex, endIndex);
cached[index] = true; cached[index] = true;
cache(index); cache(index);
photoViewControllers[index] ??= PhotoViewController(); photoViewControllers[index] = PhotoViewController();
return PhotoViewGalleryPageOptions( return PhotoViewGalleryPageOptions.customChild(
filterQuality: FilterQuality.medium, child: PhotoView.customChild(
key: ValueKey('photo_view_$index'),
controller: photoViewControllers[index], controller: photoViewControllers[index],
imageProvider: imageProvider, minScale: PhotoViewComputedScale.contained * 1.0,
fit: BoxFit.contain, maxScale: PhotoViewComputedScale.covered * 10.0,
errorBuilder: (_, error, s, retry) { child: buildPageImages(pageImages),
return NetworkError(message: error.toString(), retry: retry); ),
},
); );
}
}, },
pageController: controller, pageController: controller,
loadingBuilder: (context, event) => Center( loadingBuilder: (context, event) => Center(
@@ -186,9 +199,9 @@ class _GalleryModeState extends State<_GalleryMode>
if (!reader.toPrevChapter()) { if (!reader.toPrevChapter()) {
reader.toPage(1); reader.toPage(1);
} }
} else if (i == reader.maxPage + 1) { } else if (i == totalPages + 1) {
if (!reader.toNextChapter()) { if (!reader.toNextChapter()) {
reader.toPage(reader.maxPage); reader.toPage(totalPages);
} }
} else { } else {
reader.setPage(i); reader.setPage(i);
@@ -198,9 +211,30 @@ class _GalleryModeState extends State<_GalleryMode>
); );
} }
Widget buildPageImages(List<String> images) {
Axis axis = (reader.mode == ReaderMode.galleryTopToBottom)
? Axis.vertical
: Axis.horizontal;
List<Widget> imageWidgets = images.map((imageKey) {
ImageProvider imageProvider =
_createImageProviderFromKey(imageKey, context);
return Expanded(
child: Image(
image: imageProvider,
fit: BoxFit.contain,
),
);
}).toList();
return axis == Axis.vertical
? Column(children: imageWidgets)
: Row(children: imageWidgets);
}
@override @override
Future<void> animateToPage(int page) { Future<void> animateToPage(int page) {
if ((page - controller.page!).abs() > 1) { if ((page - controller.page!.round()).abs() > 1) {
controller.jumpToPage(page > controller.page! ? page - 1 : page + 1); controller.jumpToPage(page > controller.page! ? page - 1 : page + 1);
} }
return controller.animateToPage( return controller.animateToPage(
@@ -600,6 +634,21 @@ class _ContinuousModeState extends State<_ContinuousMode>
} }
} }
ImageProvider _createImageProviderFromKey(
String imageKey, BuildContext context) {
if (imageKey.startsWith('file://')) {
return FileImage(File(imageKey.replaceFirst("file://", '')));
} else {
var reader = context.reader;
return ReaderImageProvider(
imageKey,
reader.type.comicSource!.key,
reader.cid,
reader.eid,
);
}
}
ImageProvider _createImageProvider(int page, BuildContext context) { ImageProvider _createImageProvider(int page, BuildContext context) {
var reader = context.reader; var reader = context.reader;
var imageKey = reader.images![page - 1]; var imageKey = reader.images![page - 1];

View File

@@ -1,6 +1,7 @@
library venera_reader; library venera_reader;
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@@ -82,7 +83,8 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
} }
@override @override
int get maxPage => images?.length ?? 1; int get maxPage =>
((images?.length ?? 1) + imagesPerPage - 1) ~/ imagesPerPage;
ComicType get type => widget.type; ComicType get type => widget.type;
@@ -94,6 +96,30 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
late ReaderMode mode; late ReaderMode mode;
int get imagesPerPage => appdata.settings['readerScreenPicNumber'] ?? 1;
int _lastImagesPerPage = appdata.settings['readerScreenPicNumber'] ?? 1;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_checkImagesPerPageChange();
}
void _checkImagesPerPageChange() {
int currentImagesPerPage = imagesPerPage;
if (_lastImagesPerPage != currentImagesPerPage) {
_adjustPageForImagesPerPageChange(_lastImagesPerPage, currentImagesPerPage);
_lastImagesPerPage = currentImagesPerPage;
}
}
void _adjustPageForImagesPerPageChange(int oldImagesPerPage, int newImagesPerPage) {
int previousImageIndex = (page - 1) * oldImagesPerPage;
int newPage = (previousImageIndex ~/ newImagesPerPage) + 1;
page = newPage;
}
History? history; History? history;
@override @override
@@ -133,6 +159,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_checkImagesPerPageChange();
return KeyboardListener( return KeyboardListener(
focusNode: focusNode, focusNode: focusNode,
autofocus: true, autofocus: true,

View File

@@ -41,6 +41,11 @@ class _ReaderSettingsState extends State<ReaderSettings> {
"continuousTopToBottom": "Continuous (Top to Bottom)".tl, "continuousTopToBottom": "Continuous (Top to Bottom)".tl,
}, },
onChanged: () { onChanged: () {
var readerMode = appdata.settings['readerMode'];
if (readerMode?.toLowerCase().startsWith('continuous') ?? false) {
appdata.settings['readerScreenPicNumber'] = 1;
widget.onChanged?.call('readerScreenPicNumber');
}
widget.onChanged?.call("readerMode"); widget.onChanged?.call("readerMode");
}, },
).toSliver(), ).toSliver(),
@@ -54,6 +59,25 @@ class _ReaderSettingsState extends State<ReaderSettings> {
widget.onChanged?.call("autoPageTurningInterval"); widget.onChanged?.call("autoPageTurningInterval");
}, },
).toSliver(), ).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 (Only Gallery Mode)".tl,
settingsIndex: "readerScreenPicNumber",
interval: 1,
min: 1,
max: 5,
onChanged: () {
widget.onChanged?.call("readerScreenPicNumber");
},
),
),
),
),
_SwitchSetting( _SwitchSetting(
title: 'Long press to zoom'.tl, title: 'Long press to zoom'.tl,
settingKey: 'enableLongPressToZoom', settingKey: 'enableLongPressToZoom',