Add a feature to allow saving custom reader settings for each comic. (#459)

* Add a feature to allow saving custom reader settings for each  comic.

* Comic-specific settings disabled by default
This commit is contained in:
enximi
2025-08-10 16:02:44 +08:00
committed by GitHub
parent 0ac857ef9a
commit ce0d10aeb2
8 changed files with 658 additions and 354 deletions

View File

@@ -32,10 +32,17 @@ class _ReaderImagesState extends State<_ReaderImages> {
inProgress = true;
if (reader.type == ComicType.local ||
(LocalManager().isDownloaded(
reader.cid, reader.type, reader.chapter, reader.widget.chapters))) {
reader.cid,
reader.type,
reader.chapter,
reader.widget.chapters,
))) {
try {
var images = await LocalManager()
.getImages(reader.cid, reader.type, reader.chapter);
var images = await LocalManager().getImages(
reader.cid,
reader.type,
reader.chapter,
);
setState(() {
reader.images = images;
reader.isLoading = false;
@@ -81,9 +88,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
Widget build(BuildContext context) {
if (reader.isLoading) {
load();
return const Center(
child: CircularProgressIndicator(),
);
return const Center(child: CircularProgressIndicator());
} else if (error != null) {
return GestureDetector(
onTap: () {
@@ -104,7 +109,8 @@ class _ReaderImagesState extends State<_ReaderImages> {
} else {
if (reader.mode.isGallery) {
return _GalleryMode(
key: Key('${reader.mode.key}_${reader.imagesPerPage}'));
key: Key('${reader.mode.key}_${reader.imagesPerPage}'),
);
} else {
return _ContinuousMode(key: Key(reader.mode.key));
}
@@ -132,11 +138,15 @@ 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 (!reader.showSingleImageOnFirstPage) {
return (reader.images!.length / reader.imagesPerPage).ceil();
if (!reader.showSingleImageOnFirstPage(reader.cid, reader.type)) {
return (reader.images!.length /
reader.imagesPerPage(reader.cid, reader.type))
.ceil();
} else {
return 1 +
((reader.images!.length - 1) / reader.imagesPerPage).ceil();
((reader.images!.length - 1) /
reader.imagesPerPage(reader.cid, reader.type))
.ceil();
}
}
@@ -159,19 +169,24 @@ class _GalleryModeState extends State<_GalleryMode>
/// Get the range of images for the given page. [page] is 1-based.
(int start, int end) getPageImagesRange(int page) {
if (reader.showSingleImageOnFirstPage) {
var imagesPerPage = reader.imagesPerPage(reader.cid, reader.type);
if (reader.showSingleImageOnFirstPage(reader.cid, reader.type)) {
if (page == 1) {
return (0, 1);
} else {
int startIndex = (page - 2) * reader.imagesPerPage + 1;
int startIndex = (page - 2) * imagesPerPage + 1;
int endIndex = math.min(
startIndex + reader.imagesPerPage, reader.images!.length);
startIndex + imagesPerPage,
reader.images!.length,
);
return (startIndex, endIndex);
}
} else {
int startIndex = (page - 1) * reader.imagesPerPage;
int startIndex = (page - 1) * imagesPerPage;
int endIndex = math.min(
startIndex + reader.imagesPerPage, reader.images!.length);
startIndex + imagesPerPage,
reader.images!.length,
);
return (startIndex, endIndex);
}
}
@@ -193,9 +208,9 @@ class _GalleryModeState extends State<_GalleryMode>
var (startIndex, endIndex) = getPageImagesRange(page);
for (int i = startIndex; i < endIndex; i++) {
if (shouldPreCache) {
_precacheImage(i+1, context);
_precacheImage(i + 1, context);
} else {
_preDownloadImage(i+1, context);
_preDownloadImage(i + 1, context);
}
}
}
@@ -217,16 +232,12 @@ class _GalleryModeState extends State<_GalleryMode>
var controller = photoViewControllers[reader.page]!;
Offset value = event.delta;
if (isLongPressing) {
controller.updateMultiple(
position: controller.position + value,
);
controller.updateMultiple(position: controller.position + value);
}
}
},
child: PhotoViewGallery.builder(
backgroundDecoration: BoxDecoration(
color: context.colorScheme.surface,
),
backgroundDecoration: BoxDecoration(color: context.colorScheme.surface),
reverse: reader.mode == ReaderMode.galleryRightToLeft,
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
? Axis.vertical
@@ -239,14 +250,17 @@ class _GalleryModeState extends State<_GalleryMode>
);
} else {
var (startIndex, endIndex) = getPageImagesRange(index);
List<String> pageImages =
reader.images!.sublist(startIndex, endIndex);
List<String> pageImages = reader.images!.sublist(
startIndex,
endIndex,
);
cache(index);
photoViewControllers[index] ??= PhotoViewController();
if (reader.imagesPerPage == 1 || pageImages.length == 1) {
if (reader.imagesPerPage(reader.cid, reader.type) == 1 ||
pageImages.length == 1) {
return PhotoViewGalleryPageOptions(
filterQuality: FilterQuality.medium,
controller: photoViewControllers[index],
@@ -287,11 +301,11 @@ class _GalleryModeState extends State<_GalleryMode>
onPageChanged: (i) {
if (i == 0) {
if (reader.isFirstChapterOfGroup || !reader.toPrevChapter()) {
reader.toPage(1);
reader.toPage(reader.cid, reader.type, 1);
}
} else if (i == totalPages + 1) {
if (reader.isLastChapterOfGroup || !reader.toNextChapter()) {
reader.toPage(totalPages);
reader.toPage(reader.cid, reader.type, totalPages);
}
} else {
reader.setPage(i);
@@ -356,13 +370,16 @@ class _GalleryModeState extends State<_GalleryMode>
onInit: (state) => imageStates.add(state),
onDispose: (state) => imageStates.remove(state),
),
)
),
];
} else {
imageWidgets = images.map((imageKey) {
startIndex++;
ImageProvider imageProvider =
_createImageProviderFromKey(imageKey, context, startIndex);
ImageProvider imageProvider = _createImageProviderFromKey(
imageKey,
context,
startIndex,
);
return Expanded(
child: ComicImage(
image: imageProvider,
@@ -423,10 +440,7 @@ class _GalleryModeState extends State<_GalleryMode>
} else {
zoomPosition = Offset(0, 0);
}
photoViewController.animateScale?.call(
target,
zoomPosition,
);
photoViewController.animateScale?.call(target, zoomPosition);
isLongPressing = true;
}
@@ -471,14 +485,14 @@ class _GalleryModeState extends State<_GalleryMode>
keyRepeatTimer = null;
}
if (forward == true) {
reader.toPage(reader.page+1);
reader.toPage(reader.cid, reader.type, reader.page + 1);
} else if (forward == false) {
reader.toPage(reader.page-1);
reader.toPage(reader.cid, reader.type, reader.page - 1);
}
}
if (event is KeyRepeatEvent && keyRepeatTimer == null) {
keyRepeatTimer = Timer.periodic(
reader.enablePageAnimation
reader.enablePageAnimation(reader.cid, reader.type)
? const Duration(milliseconds: 200)
: const Duration(milliseconds: 50),
(timer) {
@@ -486,9 +500,9 @@ class _GalleryModeState extends State<_GalleryMode>
timer.cancel();
return;
} else if (forward == true) {
reader.toPage(reader.page+1);
reader.toPage(reader.cid, reader.type, reader.page + 1);
} else if (forward == false) {
reader.toPage(reader.page-1);
reader.toPage(reader.cid, reader.type, reader.page - 1);
}
},
);
@@ -512,15 +526,15 @@ class _GalleryModeState extends State<_GalleryMode>
return await File(imageKey.substring(7)).readAsBytes();
} else {
return (await CacheManager().findCache(
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
.readAsBytes();
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
))!.readAsBytes();
}
}
@override
String? getImageKeyByOffset(Offset offset) {
String? imageKey;
if (reader.imagesPerPage == 1) {
if (reader.imagesPerPage(reader.cid, reader.type) == 1) {
imageKey = reader.images![reader.page - 1];
} else {
for (var imageState in imageStates) {
@@ -538,7 +552,7 @@ const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
PointerDeviceKind.mouse,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.unknown
PointerDeviceKind.unknown,
};
const double _kChangeChapterOffset = 160;
@@ -673,10 +687,12 @@ class _ContinuousModeState extends State<_ContinuousMode>
void onScroll() {
if (prepareToPrevChapter) {
jumpToNextChapter = false;
jumpToPrevChapter = scrollController.offset <
jumpToPrevChapter =
scrollController.offset <
scrollController.position.minScrollExtent - _kChangeChapterOffset;
} else if (prepareToNextChapter) {
jumpToNextChapter = scrollController.offset >
jumpToNextChapter =
scrollController.offset >
scrollController.position.maxScrollExtent + _kChangeChapterOffset;
jumpToPrevChapter = false;
}
@@ -721,8 +737,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
physics: isCTRLPressed || _isMouseScrolling || disableScroll
? const NeverScrollableScrollPhysics()
: isZoomedIn
? const ClampingScrollPhysics()
: const BouncingScrollPhysics(),
? const ClampingScrollPhysics()
: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
if (index == 0 || index == reader.maxPage + 1) {
return const SizedBox();
@@ -750,8 +766,10 @@ class _ContinuousModeState extends State<_ContinuousMode>
),
);
},
scrollBehavior: const MaterialScrollBehavior()
.copyWith(scrollbars: false, dragDevices: _kTouchLikeDeviceTypes),
scrollBehavior: const MaterialScrollBehavior().copyWith(
scrollbars: false,
dragDevices: _kTouchLikeDeviceTypes,
),
);
widget = Stack(
@@ -895,20 +913,14 @@ class _ContinuousModeState extends State<_ContinuousMode>
}
return PhotoView.customChild(
backgroundDecoration: BoxDecoration(
color: context.colorScheme.surface,
),
backgroundDecoration: BoxDecoration(color: context.colorScheme.surface),
childSize: Size(width, height),
minScale: 1.0,
maxScale: 2.5,
strictScale: true,
controller: photoViewController,
onScaleUpdate: onScaleUpdate,
child: SizedBox(
width: width,
height: height,
child: widget,
),
child: SizedBox(width: width, height: height, child: widget),
);
}
@@ -978,10 +990,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
} else {
zoomPosition = Offset(0, 0);
}
photoViewController.animateScale?.call(
target,
zoomPosition,
);
photoViewController.animateScale?.call(target, zoomPosition);
onScaleUpdate(target);
isLongPressing = true;
}
@@ -1069,8 +1078,8 @@ class _ContinuousModeState extends State<_ContinuousMode>
return await File(imageKey.substring(7)).readAsBytes();
} else {
return (await CacheManager().findCache(
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
.readAsBytes();
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}",
))!.readAsBytes();
}
}
@@ -1114,10 +1123,7 @@ void _precacheImage(int page, BuildContext context) {
if (page <= 0 || page > context.reader.images!.length) {
return;
}
precacheImage(
_createImageProvider(page, context),
context,
);
precacheImage(_createImageProvider(page, context), context);
}
/// [_preDownloadImage] is used to download the image for the given page.
@@ -1138,10 +1144,7 @@ void _preDownloadImage(int page, BuildContext context) {
}
class _SwipeChangeChapterProgress extends StatefulWidget {
const _SwipeChangeChapterProgress({
this.controller,
required this.isPrev,
});
const _SwipeChangeChapterProgress({this.controller, required this.isPrev});
final ScrollController? controller;
@@ -1258,7 +1261,12 @@ class _ProgressPainter extends CustomPainter {
paint.color = color;
canvas.drawRRect(
RRect.fromLTRBR(
0, 0, size.width * value, size.height, Radius.circular(16)),
0,
0,
size.width * value,
size.height,
Radius.circular(16),
),
paint,
);
}