comic reading

This commit is contained in:
nyne
2024-10-07 22:33:07 +08:00
parent 5ccd0af2d8
commit 601a7cd796
16 changed files with 1127 additions and 278 deletions

View File

@@ -0,0 +1,212 @@
part of 'reader.dart';
class _ReaderImages extends StatefulWidget {
const _ReaderImages({super.key});
@override
State<_ReaderImages> createState() => _ReaderImagesState();
}
class _ReaderImagesState extends State<_ReaderImages> {
String? error;
bool inProgress = false;
@override
void initState() {
context.reader.isLoading = true;
super.initState();
}
void load() async {
if (inProgress) return;
inProgress = true;
var res = await context.reader.widget.source.loadComicPages!(
context.reader.widget.cid,
context.reader.widget.chapters?.keys
.elementAt(context.reader.chapter - 1),
);
if (res.error) {
setState(() {
error = res.errorMessage;
context.reader.isLoading = false;
inProgress = false;
});
} else {
setState(() {
context.reader.images = res.data;
context.reader.isLoading = false;
inProgress = false;
});
}
context.readerScaffold.update();
}
@override
Widget build(BuildContext context) {
if (context.reader.isLoading) {
load();
return const Center(
child: CircularProgressIndicator(),
);
} else if (error != null) {
return NetworkError(
message: error!,
retry: () {
setState(() {
context.reader.isLoading = true;
error = null;
});
},
);
} else {
if (context.reader.mode.isGallery) {
return _GalleryMode(key: Key(context.reader.mode.key));
} else {
// TODO: Implement other modes
throw UnimplementedError();
}
}
}
}
class _GalleryMode extends StatefulWidget {
const _GalleryMode({super.key});
@override
State<_GalleryMode> createState() => _GalleryModeState();
}
class _GalleryModeState extends State<_GalleryMode>
implements _ImageViewController {
late PageController controller;
late List<bool> cached;
int get preCacheCount => 4;
var photoViewControllers = <int, PhotoViewController>{};
@override
void initState() {
controller = PageController(initialPage: context.reader.page);
context.reader._imageViewController = this;
cached = List.filled(context.reader.maxPage + 2, false);
super.initState();
}
void cache(int current) {
for (int i = current + 1; i <= current + preCacheCount; i++) {
if (i <= context.reader.maxPage && !cached[i]) {
_precacheImage(i, context);
cached[i] = true;
}
}
}
@override
Widget build(BuildContext context) {
return PhotoViewGallery.builder(
backgroundDecoration: BoxDecoration(
color: context.colorScheme.surface,
),
reverse: context.reader.mode == ReaderMode.galleryRightToLeft,
scrollDirection: context.reader.mode == ReaderMode.galleryTopToBottom
? Axis.vertical
: Axis.horizontal,
itemCount: context.reader.images!.length + 2,
builder: (BuildContext context, int index) {
ImageProvider? imageProvider;
if (index != 0 && index != context.reader.images!.length + 1) {
imageProvider = _createImageProvider(index, context);
} else {
return PhotoViewGalleryPageOptions.customChild(
scaleStateController: PhotoViewScaleStateController(),
child: const SizedBox(),
);
}
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(
child: SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(
backgroundColor: context.colorScheme.surfaceContainerHigh,
value: event == null || event.expectedTotalBytes == null
? null
: event.cumulativeBytesLoaded / event.expectedTotalBytes!,
),
),
),
onPageChanged: (i) {
if (i == 0) {
if (!context.reader.toNextChapter()) {
context.reader.toPage(1);
}
} else if (i == context.reader.maxPage + 1) {
if (!context.reader.toPrevChapter()) {
context.reader.toPage(context.reader.maxPage);
}
} else {
context.reader.setPage(i);
context.readerScaffold.update();
}
},
);
}
@override
Future<void> animateToPage(int page) {
if ((page - controller.page!).abs() > 1) {
controller.jumpToPage(page > controller.page! ? page - 1 : page + 1);
}
return controller.animateToPage(
page,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}
@override
void toPage(int page) {
controller.jumpToPage(page);
}
@override
void handleDoubleTap(Offset location) {
var controller = photoViewControllers[context.reader.page]!;
controller.onDoubleClick?.call();
}
}
ImageProvider _createImageProvider(int page, BuildContext context) {
return ReaderImageProvider(
context.reader.images![page - 1],
context.reader.widget.source.key,
context.reader.cid,
context.reader.eid,
);
}
void _precacheImage(int page, BuildContext context) {
precacheImage(
_createImageProvider(page, context),
context,
);
}