diff --git a/assets/templates/epub/META-INF/container.xml b/assets/templates/epub/META-INF/container.xml
new file mode 100644
index 000000000..5ab7bad8c
--- /dev/null
+++ b/assets/templates/epub/META-INF/container.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/assets/templates/epub/OEBPS/content/index.html.tpl b/assets/templates/epub/OEBPS/content/index.html.tpl
new file mode 100644
index 000000000..59e0e4ed2
--- /dev/null
+++ b/assets/templates/epub/OEBPS/content/index.html.tpl
@@ -0,0 +1,13 @@
+
+
+
+ {{ title }}
+
+
+
+
+ {#
#}
+
+
+
+
\ No newline at end of file
diff --git a/assets/templates/epub/OEBPS/content/resources/index_0.css b/assets/templates/epub/OEBPS/content/resources/index_0.css
new file mode 100644
index 000000000..6525a81d2
--- /dev/null
+++ b/assets/templates/epub/OEBPS/content/resources/index_0.css
@@ -0,0 +1,36 @@
+body {
+ text-align: left;
+ text-indent: 0em;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 0;
+ margin-right: 0;
+ Padding-top: 0;
+ Padding-bottom: 0;
+ Padding-left: 0;
+ Padding-right: 0;
+}
+p {
+ text-align: left;
+ text-indent: 0em;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 0;
+ margin-right: 0;
+ Padding-top: 0;
+ Padding-bottom: 0;
+ Padding-left: 0;
+ Padding-right: 0;
+}
+div {
+ text-align: left;
+ text-indent: 0em;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 0;
+ margin-right: 0;
+ Padding-top: 0;
+ Padding-bottom: 0;
+ Padding-left: 0;
+ Padding-right: 0;
+}
diff --git a/assets/templates/epub/OEBPS/metadata.xml.tpl b/assets/templates/epub/OEBPS/metadata.xml.tpl
new file mode 100644
index 000000000..eb1457aa4
--- /dev/null
+++ b/assets/templates/epub/OEBPS/metadata.xml.tpl
@@ -0,0 +1,33 @@
+
+
+
+ {{ title }}
+ e-hentai.org
+ {# ChainLP (0.0.40.17) [http://no722.cocolog-nifty.com/blog/] #}
+ {# 2021-07-30T05:37:00+00:00 #}
+ e-hentai.org
+ {{ language }}
+ {# 254ce15d-dfdf-4fa0-bba0-9dbd232c7854 #}
+
+
+
+
+
+
+
+ {%- for fileName in fileNameList %}
+
+ {%- endfor %}
+ {%- for fileName in fileNameList %}
+
+ {%- endfor %}
+
+
+
+
+
+ {%- for fileName in fileNameList %}
+
+ {%- endfor %}
+
+
diff --git a/assets/templates/epub/OEBPS/toc.ncx.tpl b/assets/templates/epub/OEBPS/toc.ncx.tpl
new file mode 100644
index 000000000..bb3d9903f
--- /dev/null
+++ b/assets/templates/epub/OEBPS/toc.ncx.tpl
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+Table of Contents
+
+
+
diff --git a/assets/templates/epub/mimetype b/assets/templates/epub/mimetype
new file mode 100644
index 000000000..403c4f02d
--- /dev/null
+++ b/assets/templates/epub/mimetype
@@ -0,0 +1 @@
+application/epub+zip
diff --git a/lib/common/epub/epub_builder.dart b/lib/common/epub/epub_builder.dart
new file mode 100644
index 000000000..d8931f96e
--- /dev/null
+++ b/lib/common/epub/epub_builder.dart
@@ -0,0 +1,127 @@
+import 'dart:io';
+
+import 'package:fehviewer/store/floor/entity/gallery_task.dart';
+import 'package:fehviewer/utils/logger.dart';
+import 'package:flutter/services.dart' show rootBundle;
+import 'package:jinja/jinja.dart';
+import 'package:path/path.dart' as path;
+
+import '../global.dart';
+
+Future buildEpub(GalleryTask task, {String? tempPath}) async {
+ final _tempPath = tempPath ?? path.join(Global.tempPath, 'export_epub_temp');
+
+ Directory _tempDir = Directory(_tempPath);
+ if (_tempDir.existsSync()) {
+ _tempDir.deleteSync(recursive: true);
+ }
+ _tempDir.createSync(recursive: true);
+
+ final metaPath = path.join(_tempPath, 'META-INF');
+ _creatDir(metaPath);
+
+ // OEBPS 目录创建
+ final oebpsPath = path.join(_tempPath, 'OEBPS');
+ _creatDir(oebpsPath);
+
+ final contentPath = path.join(oebpsPath, 'content');
+ _creatDir(contentPath);
+
+ final resourcesPath = path.join(contentPath, 'resources');
+ _creatDir(resourcesPath);
+
+ // mimetype
+ final fileMimetype = File(path.join(_tempPath, 'mimetype'));
+ fileMimetype.writeAsStringSync('application/epub+zip\n');
+
+ // css 创建
+ final cssText = await rootBundle
+ .loadString('assets/templates/epub/OEBPS/content/resources/index_0.css');
+ final fileCss = File(path.join(resourcesPath, 'index_0.css'));
+ fileCss.writeAsStringSync(cssText);
+
+ // container
+ final containerText = await rootBundle
+ .loadString('assets/templates/epub/META-INF/container.xml');
+ final fileContainer = File(path.join(metaPath, 'container.xml'));
+ fileContainer.writeAsStringSync(containerText);
+
+ // 图片复制到 resourcesPath
+ final _galleryDir = Directory(task.dirPath!);
+ final _fileList = _galleryDir.listSync();
+ for (final _file in _fileList) {
+ if ((await FileSystemEntity.type(_file.path)) ==
+ FileSystemEntityType.file) {
+ final srcFile = File(_file.path);
+ srcFile.copySync(path.join(resourcesPath, path.basename(srcFile.path)));
+ }
+ }
+
+ // 模板操作
+ final environment = Environment();
+
+ // index 模板内容读取
+ final indexTemplateText = await rootBundle
+ .loadString('assets/templates/epub/OEBPS/content/index.html.tpl');
+ final indexTemplate = environment.fromString(indexTemplateText);
+
+ // metadata 模板内容读取
+ final metadataTemplateText = await rootBundle
+ .loadString('assets/templates/epub/OEBPS/metadata.xml.tpl');
+ final metadataTemplate = environment.fromString(metadataTemplateText);
+
+ // toc 模板内容读取
+ final tocTemplateText =
+ await rootBundle.loadString('assets/templates/epub/OEBPS/toc.ncx.tpl');
+ final tocTemplate = environment.fromString(tocTemplateText);
+
+ final _fileNameList = _fileList
+ .map(
+ (e) => {
+ 'withoutExtension': path.basenameWithoutExtension(e.path),
+ 'name': path.basename(e.path),
+ 'extension': path.extension(e.path) == '.jpg'
+ ? 'jpeg'
+ : path.extension(e.path).split('.').last.toLowerCase(),
+ },
+ )
+ .toList()
+ ..sort((a, b) => a['name']!.compareTo(b['name']!));
+
+ // 写入index
+ for (final _imgFile in _fileNameList) {
+ final _xhtml = indexTemplate.render(
+ fileName: _imgFile['name'],
+ );
+ final _xhtmlFile = File(
+ path.join(contentPath, 'index_${_imgFile['withoutExtension']}.xhtml'));
+ _xhtmlFile.createSync(recursive: true);
+ _xhtmlFile.writeAsStringSync('$_xhtml');
+ }
+
+ // 写入 metadata.opf
+ final metadata = metadataTemplate.render(
+ title: task.title,
+ fileNameList: _fileNameList,
+ coverName: _fileNameList.first['name'],
+ coverExtension: _fileNameList.first['extension'],
+ );
+ final metadataFile = File(path.join(oebpsPath, 'metadata.opf'));
+ metadataFile.createSync();
+ metadataFile.writeAsStringSync('$metadata');
+
+ // 写入 toc.ncx
+ final toc = tocTemplate.render();
+ final tocFile = File(path.join(oebpsPath, 'toc.ncx'));
+ tocFile.createSync();
+ tocFile.writeAsStringSync('$toc');
+
+ return _tempPath;
+}
+
+void _creatDir(String path) {
+ final _dir = Directory(path);
+ if (!_dir.existsSync()) {
+ _dir.createSync(recursive: true);
+ }
+}
diff --git a/lib/pages/item/download_gallery_item.dart b/lib/pages/item/download_gallery_item.dart
index 2c84210f4..df4ae6f79 100644
--- a/lib/pages/item/download_gallery_item.dart
+++ b/lib/pages/item/download_gallery_item.dart
@@ -43,7 +43,7 @@ Future syncReadProgress(
Future _showSyncDialog() async {
return await showCupertinoDialog(
context: context,
- barrierDismissible: true,
+ // barrierDismissible: true,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text('${L10n.of(context).sync_read_progress}...'),
diff --git a/lib/pages/tab/controller/download_view_controller.dart b/lib/pages/tab/controller/download_view_controller.dart
index f3f3f402a..345544043 100644
--- a/lib/pages/tab/controller/download_view_controller.dart
+++ b/lib/pages/tab/controller/download_view_controller.dart
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:fehviewer/common/controller/archiver_download_controller.dart';
import 'package:fehviewer/common/controller/download_controller.dart';
+import 'package:fehviewer/common/epub/epub_builder.dart';
import 'package:fehviewer/common/global.dart';
import 'package:fehviewer/common/isolate_download/download_manager.dart';
import 'package:fehviewer/generated/l10n.dart';
@@ -24,6 +25,8 @@ enum DownloadType {
archiver,
}
+typedef FunctionExport = Future Function();
+
const String idDownloadGalleryView = 'DownloadGalleryView';
const String idDownloadArchiverView = 'DownloadArchiverView';
const String idDownloadGalleryItem = 'DownloadGalleryItem';
@@ -242,7 +245,8 @@ class DownloadViewController extends GetxController {
onPressed: () {
logger.d('导出zip');
Get.back();
- _exportZip(task);
+
+ _exportZip(context, task);
},
child: const Text(
'ZIP',
@@ -253,7 +257,7 @@ class DownloadViewController extends GetxController {
onPressed: () {
logger.d('导出epub');
Get.back();
- _exportEpub(task);
+ _exportEpub(context, task);
},
child: const Text(
'EPUB',
@@ -264,13 +268,13 @@ class DownloadViewController extends GetxController {
});
}
- Future _exportZip(GalleryTask task) async {
+ Future _exportZip(BuildContext context, GalleryTask task) async {
logger.d('export zip , dir path ${task.dirPath}');
if (task.dirPath == null) {
return null;
}
- final _zipPath = await _compZip(task);
+ final _zipPath = await exportGallery(context, () => _compZip(task));
if (_zipPath != null) {
Share.shareFiles([_zipPath]);
@@ -307,7 +311,33 @@ class DownloadViewController extends GetxController {
return _zipPath;
}
- Future _exportEpub(GalleryTask task) async {}
+ Future _exportEpub(BuildContext context, GalleryTask task) async {
+ logger.d('export epub , dir path ${task.dirPath}');
+ if (task.dirPath == null) {
+ return null;
+ }
+
+ final _exportFilePath =
+ await exportGallery(context, () => _buildEpub(task));
+
+ if (_exportFilePath != null) {
+ Share.shareFiles([_exportFilePath]);
+ }
+ }
+
+ Future _buildEpub(GalleryTask task) async {
+ final _tempPath = await buildEpub(task);
+
+ // 打包epub文件
+ final encoder = ZipFileEncoder();
+ final _epubPath =
+ path.join(Global.tempPath, 'epub', '${task.gid}_${task.title}.epub');
+ encoder.create(_epubPath);
+ encoder.addDirectory(Directory(_tempPath), includeDirName: false);
+ encoder.close();
+
+ return _epubPath;
+ }
// gallery 暂停任务
void pauseGalleryDownload(int? gid) {
@@ -321,3 +351,45 @@ class DownloadViewController extends GetxController {
update(['${idDownloadGalleryItem}_$gid']);
}
}
+
+Future exportGallery(
+ BuildContext context,
+ FunctionExport funExport, {
+ FunctionExport? funcCancel,
+}) async {
+ Future _showExportDialog() async {
+ return await showCupertinoDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return CupertinoAlertDialog(
+ title: Text(L10n.of(context).processing),
+ content: GetBuilder(
+ initState: (state) async {
+ final _path = await funExport();
+ Get.back(result: _path);
+ },
+ builder: (logic) {
+ return const CupertinoActivityIndicator(radius: 16);
+ },
+ ).paddingOnly(top: 10.0),
+ actions: [
+ CupertinoDialogAction(
+ child: Text(
+ L10n.of(context).cancel,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ color: CupertinoColors.destructiveRed),
+ ),
+ onPressed: () {
+ funcCancel?.call();
+ Get.back();
+ },
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ return await _showExportDialog();
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index a2d4352fd..32d8128b1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -189,3 +189,7 @@ flutter:
assets:
- assets/
+ - assets/templates/epub/META-INF/
+ - assets/templates/epub/OEBPS/
+ - assets/templates/epub/OEBPS/content/
+ - assets/templates/epub/OEBPS/content/resources/