mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
implement saving image, sharing image, reading settings and chapters view
This commit is contained in:
@@ -12,49 +12,51 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
||||
|
||||
bool inProgress = false;
|
||||
|
||||
late _ReaderState reader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
context.reader.isLoading = true;
|
||||
reader = context.reader;
|
||||
reader.isLoading = true;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void load() async {
|
||||
if (inProgress) return;
|
||||
inProgress = true;
|
||||
if (context.reader.type == ComicType.local ||
|
||||
(await LocalManager().isDownloaded(
|
||||
context.reader.cid, context.reader.type, context.reader.chapter))) {
|
||||
if (reader.type == ComicType.local ||
|
||||
(await LocalManager()
|
||||
.isDownloaded(reader.cid, reader.type, reader.chapter))) {
|
||||
try {
|
||||
var images = await LocalManager().getImages(
|
||||
context.reader.cid, context.reader.type, context.reader.chapter);
|
||||
var images = await LocalManager()
|
||||
.getImages(reader.cid, reader.type, reader.chapter);
|
||||
setState(() {
|
||||
context.reader.images = images;
|
||||
context.reader.isLoading = false;
|
||||
reader.images = images;
|
||||
reader.isLoading = false;
|
||||
inProgress = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
error = e.toString();
|
||||
context.reader.isLoading = false;
|
||||
reader.isLoading = false;
|
||||
inProgress = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
var res = await context.reader.type.comicSource!.loadComicPages!(
|
||||
context.reader.widget.cid,
|
||||
context.reader.widget.chapters?.keys
|
||||
.elementAt(context.reader.chapter - 1),
|
||||
var res = await reader.type.comicSource!.loadComicPages!(
|
||||
reader.widget.cid,
|
||||
reader.widget.chapters?.keys.elementAt(reader.chapter - 1),
|
||||
);
|
||||
if (res.error) {
|
||||
setState(() {
|
||||
error = res.errorMessage;
|
||||
context.reader.isLoading = false;
|
||||
reader.isLoading = false;
|
||||
inProgress = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
context.reader.images = res.data;
|
||||
context.reader.isLoading = false;
|
||||
reader.images = res.data;
|
||||
reader.isLoading = false;
|
||||
inProgress = false;
|
||||
});
|
||||
}
|
||||
@@ -64,7 +66,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (context.reader.isLoading) {
|
||||
if (reader.isLoading) {
|
||||
load();
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
@@ -74,14 +76,14 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
||||
message: error!,
|
||||
retry: () {
|
||||
setState(() {
|
||||
context.reader.isLoading = true;
|
||||
reader.isLoading = true;
|
||||
error = null;
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (context.reader.mode.isGallery) {
|
||||
return _GalleryMode(key: Key(context.reader.mode.key));
|
||||
if (reader.mode.isGallery) {
|
||||
return _GalleryMode(key: Key(reader.mode.key));
|
||||
} else {
|
||||
// TODO: Implement other modes
|
||||
throw UnimplementedError();
|
||||
@@ -107,17 +109,20 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
|
||||
var photoViewControllers = <int, PhotoViewController>{};
|
||||
|
||||
late _ReaderState reader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
controller = PageController(initialPage: context.reader.page);
|
||||
context.reader._imageViewController = this;
|
||||
cached = List.filled(context.reader.maxPage + 2, false);
|
||||
reader = context.reader;
|
||||
controller = PageController(initialPage: reader.page);
|
||||
reader._imageViewController = this;
|
||||
cached = List.filled(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]) {
|
||||
if (i <= reader.maxPage && !cached[i]) {
|
||||
_precacheImage(i, context);
|
||||
cached[i] = true;
|
||||
}
|
||||
@@ -130,14 +135,14 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: context.colorScheme.surface,
|
||||
),
|
||||
reverse: context.reader.mode == ReaderMode.galleryRightToLeft,
|
||||
scrollDirection: context.reader.mode == ReaderMode.galleryTopToBottom
|
||||
reverse: reader.mode == ReaderMode.galleryRightToLeft,
|
||||
scrollDirection: reader.mode == ReaderMode.galleryTopToBottom
|
||||
? Axis.vertical
|
||||
: Axis.horizontal,
|
||||
itemCount: context.reader.images!.length + 2,
|
||||
itemCount: reader.images!.length + 2,
|
||||
builder: (BuildContext context, int index) {
|
||||
ImageProvider? imageProvider;
|
||||
if (index != 0 && index != context.reader.images!.length + 1) {
|
||||
if (index != 0 && index != reader.images!.length + 1) {
|
||||
imageProvider = _createImageProvider(index, context);
|
||||
} else {
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
@@ -176,15 +181,15 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
),
|
||||
onPageChanged: (i) {
|
||||
if (i == 0) {
|
||||
if (!context.reader.toNextChapter()) {
|
||||
context.reader.toPage(1);
|
||||
if (!reader.toNextChapter()) {
|
||||
reader.toPage(1);
|
||||
}
|
||||
} else if (i == context.reader.maxPage + 1) {
|
||||
if (!context.reader.toPrevChapter()) {
|
||||
context.reader.toPage(context.reader.maxPage);
|
||||
} else if (i == reader.maxPage + 1) {
|
||||
if (!reader.toPrevChapter()) {
|
||||
reader.toPage(reader.maxPage);
|
||||
}
|
||||
} else {
|
||||
context.reader.setPage(i);
|
||||
reader.setPage(i);
|
||||
context.readerScaffold.update();
|
||||
}
|
||||
},
|
||||
@@ -210,21 +215,22 @@ class _GalleryModeState extends State<_GalleryMode>
|
||||
|
||||
@override
|
||||
void handleDoubleTap(Offset location) {
|
||||
var controller = photoViewControllers[context.reader.page]!;
|
||||
var controller = photoViewControllers[reader.page]!;
|
||||
controller.onDoubleClick?.call();
|
||||
}
|
||||
}
|
||||
|
||||
ImageProvider _createImageProvider(int page, BuildContext context) {
|
||||
var imageKey = context.reader.images![page-1];
|
||||
if(imageKey.startsWith('file://')) {
|
||||
var reader = context.reader;
|
||||
var imageKey = reader.images![page - 1];
|
||||
if (imageKey.startsWith('file://')) {
|
||||
return FileImage(File(imageKey.replaceFirst("file://", '')));
|
||||
} else {
|
||||
return ReaderImageProvider(
|
||||
imageKey,
|
||||
context.reader.type.comicSource!.key,
|
||||
context.reader.cid,
|
||||
context.reader.eid,
|
||||
reader.type.comicSource!.key,
|
||||
reader.cid,
|
||||
reader.eid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -10,10 +10,13 @@ import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:venera/components/components.dart';
|
||||
import 'package:venera/foundation/app.dart';
|
||||
import 'package:venera/foundation/appdata.dart';
|
||||
import 'package:venera/foundation/cache_manager.dart';
|
||||
import 'package:venera/foundation/comic_type.dart';
|
||||
import 'package:venera/foundation/history.dart';
|
||||
import 'package:venera/foundation/image_provider/reader_image.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
import 'package:venera/pages/settings/settings_page.dart';
|
||||
import 'package:venera/utils/file_type.dart';
|
||||
import 'package:venera/utils/io.dart';
|
||||
import 'package:venera/utils/translations.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
@@ -63,6 +63,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: context.padding.top),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surface.withOpacity(0.82),
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
@@ -73,16 +74,20 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
const BackButton(),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(context.reader.widget.name, style: ts.s18),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: "Settings".tl,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: openSetting,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -191,7 +196,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
icon: context.reader.autoPageTurningTimer != null
|
||||
? const Icon(Icons.timer)
|
||||
: const Icon(Icons.timer_sharp),
|
||||
onPressed: context.reader.autoPageTurning,
|
||||
onPressed: () {
|
||||
context.reader.autoPageTurning();
|
||||
update();
|
||||
},
|
||||
),
|
||||
),
|
||||
if (context.reader.widget.chapters != null)
|
||||
@@ -226,6 +234,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
return BlurEffect(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surface.withOpacity(0.82),
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
@@ -243,7 +252,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
return Slider(
|
||||
value: context.reader.page.toDouble(),
|
||||
min: 1,
|
||||
max: context.reader.maxPage.clamp(context.reader.page, 1 << 16).toDouble(),
|
||||
max:
|
||||
context.reader.maxPage.clamp(context.reader.page, 1 << 16).toDouble(),
|
||||
divisions: (context.reader.maxPage - 1).clamp(2, 1 << 16),
|
||||
onChanged: (i) {
|
||||
context.reader.toPage(i.toInt());
|
||||
@@ -285,18 +295,131 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
||||
}
|
||||
|
||||
void openChapterDrawer() {
|
||||
// TODO
|
||||
showSideBar(
|
||||
context,
|
||||
_ChaptersView(context.reader),
|
||||
width: 400,
|
||||
);
|
||||
}
|
||||
|
||||
void saveCurrentImage() {
|
||||
// TODO
|
||||
Future<Uint8List> _getCurrentImageData() async {
|
||||
var imageKey = context.reader.images![context.reader.page - 1];
|
||||
if (imageKey.startsWith("file://")) {
|
||||
return await File(imageKey.substring(7)).readAsBytes();
|
||||
} else {
|
||||
return (await CacheManager()
|
||||
.findCache("$imageKey@${context.reader.type.comicSource!.key}"))!
|
||||
.readAsBytes();
|
||||
}
|
||||
}
|
||||
|
||||
void share() {
|
||||
// TODO
|
||||
void saveCurrentImage() async {
|
||||
var data = await _getCurrentImageData();
|
||||
var fileType = detectFileType(data);
|
||||
var filename = "${context.reader.page}${fileType.ext}";
|
||||
saveFile(data: data, filename: filename);
|
||||
}
|
||||
|
||||
void share() async {
|
||||
var data = await _getCurrentImageData();
|
||||
var fileType = detectFileType(data);
|
||||
var filename = "${context.reader.page}${fileType.ext}";
|
||||
Share.shareFile(
|
||||
data: data,
|
||||
filename: filename,
|
||||
mime: fileType.mime,
|
||||
);
|
||||
}
|
||||
|
||||
void openSetting() {
|
||||
// TODO
|
||||
showSideBar(
|
||||
context,
|
||||
ReaderSettings(
|
||||
onChanged: (key) {
|
||||
if(key == "readerMode") {
|
||||
context.reader.mode = ReaderMode.fromKey(appdata.settings[key]);
|
||||
App.rootContext.pop();
|
||||
}
|
||||
context.reader.update();
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChaptersView extends StatefulWidget {
|
||||
const _ChaptersView(this.reader);
|
||||
|
||||
final _ReaderState reader;
|
||||
|
||||
@override
|
||||
State<_ChaptersView> createState() => _ChaptersViewState();
|
||||
}
|
||||
|
||||
class _ChaptersViewState extends State<_ChaptersView> {
|
||||
bool desc = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chapters = widget.reader.widget.chapters!;
|
||||
var current = widget.reader.chapter - 1;
|
||||
return Scaffold(
|
||||
body: SmoothCustomScrollView(
|
||||
slivers: [
|
||||
SliverAppbar(
|
||||
title: Text("Chapters".tl),
|
||||
actions: [
|
||||
Tooltip(
|
||||
message: "Click to change the order".tl,
|
||||
child: TextButton.icon(
|
||||
icon: Icon(
|
||||
!desc ? Icons.arrow_upward : Icons.arrow_downward,
|
||||
size: 18,
|
||||
),
|
||||
label: Text(!desc ? "Ascending".tl : "Descending".tl),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
desc = !desc;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (desc) {
|
||||
index = chapters.length - 1 - index;
|
||||
}
|
||||
var chapter = chapters.values.elementAt(index);
|
||||
return ListTile(
|
||||
shape: Border(
|
||||
left: BorderSide(
|
||||
color: current == index
|
||||
? context.colorScheme.primary
|
||||
: Colors.transparent,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
chapter,
|
||||
style: current == index
|
||||
? ts.withColor(context.colorScheme.primary).bold
|
||||
: null,
|
||||
),
|
||||
onTap: () {
|
||||
widget.reader.toChapter(index + 1);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: chapters.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user