mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
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:
@@ -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": "浅色",
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
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,
|
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];
|
||||||
|
@@ -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,
|
||||||
|
@@ -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',
|
||||||
|
Reference in New Issue
Block a user