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 nyne
parent df2ba6efd1
commit 66ebdb03b1
5 changed files with 130 additions and 27 deletions

View File

@@ -83,7 +83,8 @@ class _ReaderImagesState extends State<_ReaderImages> {
);
} else {
if (reader.mode.isGallery) {
return _GalleryMode(key: Key(reader.mode.key));
return _GalleryMode(
key: Key('${reader.mode.key}_${reader.imagesPerPage}'));
} else {
return _ContinuousMode(key: Key(reader.mode.key));
}
@@ -110,6 +111,10 @@ class _GalleryModeState extends State<_GalleryMode>
late _ReaderState reader;
int get totalPages => ((reader.images!.length + reader.imagesPerPage - 1) /
reader.imagesPerPage)
.ceil();
@override
void initState() {
reader = context.reader;
@@ -124,8 +129,14 @@ class _GalleryModeState extends State<_GalleryMode>
void cache(int current) {
for (int i = current + 1; i <= current + preCacheCount; i++) {
if (i <= reader.maxPage && !cached[i]) {
_precacheImage(i, context);
if (i <= totalPages && !cached[i]) {
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;
}
}
@@ -141,32 +152,34 @@ class _GalleryModeState extends State<_GalleryMode>
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
? Axis.vertical
: Axis.horizontal,
itemCount: reader.images!.length + 2,
itemCount: totalPages + 2,
builder: (BuildContext context, int index) {
ImageProvider? imageProvider;
if (index != 0 && index != reader.images!.length + 1) {
imageProvider = _createImageProvider(index, context);
} else {
if (index == 0 || index == totalPages + 1) {
return PhotoViewGalleryPageOptions.customChild(
scaleStateController: PhotoViewScaleStateController(),
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;
cache(index);
photoViewControllers[index] = PhotoViewController();
return PhotoViewGalleryPageOptions.customChild(
child: PhotoView.customChild(
key: ValueKey('photo_view_$index'),
controller: photoViewControllers[index],
minScale: PhotoViewComputedScale.contained * 1.0,
maxScale: PhotoViewComputedScale.covered * 10.0,
child: buildPageImages(pageImages),
),
);
}
cached[index] = true;
cache(index);
photoViewControllers[index] ??= PhotoViewController();
return PhotoViewGalleryPageOptions(
filterQuality: FilterQuality.medium,
controller: photoViewControllers[index],
imageProvider: imageProvider,
fit: BoxFit.contain,
errorBuilder: (_, error, s, retry) {
return NetworkError(message: error.toString(), retry: retry);
},
);
},
pageController: controller,
loadingBuilder: (context, event) => Center(
@@ -186,9 +199,9 @@ class _GalleryModeState extends State<_GalleryMode>
if (!reader.toPrevChapter()) {
reader.toPage(1);
}
} else if (i == reader.maxPage + 1) {
} else if (i == totalPages + 1) {
if (!reader.toNextChapter()) {
reader.toPage(reader.maxPage);
reader.toPage(totalPages);
}
} else {
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
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);
}
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) {
var reader = context.reader;
var imageKey = reader.images![page - 1];

View File

@@ -1,6 +1,7 @@
library venera_reader;
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
@@ -82,7 +83,8 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
}
@override
int get maxPage => images?.length ?? 1;
int get maxPage =>
((images?.length ?? 1) + imagesPerPage - 1) ~/ imagesPerPage;
ComicType get type => widget.type;
@@ -94,6 +96,30 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
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;
@override
@@ -133,6 +159,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
@override
Widget build(BuildContext context) {
_checkImagesPerPageChange();
return KeyboardListener(
focusNode: focusNode,
autofocus: true,

View File

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