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/