From a2430c254175264b82350609aedf4aa6322cfd86 Mon Sep 17 00:00:00 2001 From: niuhuan Date: Thu, 30 Mar 2023 22:38:28 +0800 Subject: [PATCH] :recycle: Import/export ui style --- .../opensource/pikapika/MainActivity.kt | 24 + ci/version.code.txt | 2 +- ci/version.info.txt | 7 + lib/basic/Method.dart | 16 + lib/basic/config/Address.dart | 39 +- lib/basic/config/ChooserRoot.dart | 34 +- lib/basic/config/ExportPath.dart | 120 +++ lib/basic/config/ImportNotice.dart | 22 + lib/screens/DownloadExportToFileScreen.dart | 936 ++++++------------ lib/screens/DownloadExportingGroupScreen.dart | 242 +++-- lib/screens/DownloadImportScreen.dart | 69 +- lib/screens/DownloadListScreen.dart | 8 +- lib/screens/InitScreen.dart | 2 + lib/screens/components/ImageReader.dart | 2 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- 16 files changed, 733 insertions(+), 796 deletions(-) create mode 100644 lib/basic/config/ExportPath.dart create mode 100644 lib/basic/config/ImportNotice.dart diff --git a/android/app/src/main/kotlin/opensource/pikapika/MainActivity.kt b/android/app/src/main/kotlin/opensource/pikapika/MainActivity.kt index 97d2965..d6493b9 100644 --- a/android/app/src/main/kotlin/opensource/pikapika/MainActivity.kt +++ b/android/app/src/main/kotlin/opensource/pikapika/MainActivity.kt @@ -103,6 +103,10 @@ class MainActivity : FlutterActivity() { "androidSecureFlag" -> androidSecureFlag(call.argument("flag")!!) "verifyAuthentication" -> auth() "androidStorageRoot" -> storageRoot() + "androidDefaultExportsDir" -> androidDefaultExportsDir().absolutePath + "androidMkdirs" -> androidMkdirs( + call.arguments() ?: throw Exception("need arg") + ) else -> { notImplementedToken } @@ -408,4 +412,24 @@ class MainActivity : FlutterActivity() { fun storageRoot(): String { return Environment.getExternalStorageDirectory().absolutePath } + + private fun downloadsDir(): File { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + ?: throw java.lang.IllegalStateException() + } + + private fun defaultPikapikaDir(): File { + return File(downloadsDir(), "pikapika") + } + + private fun androidDefaultExportsDir(): File { + return File(defaultPikapikaDir(), "exports") + } + + private fun androidMkdirs(path: String) { + val dir = File(path) + if (!dir.exists()) { + dir.mkdirs() + } + } } diff --git a/ci/version.code.txt b/ci/version.code.txt index 1a6afed..b7c8e16 100644 --- a/ci/version.code.txt +++ b/ci/version.code.txt @@ -1 +1 @@ -v1.6.8 \ No newline at end of file +v1.7.0 \ No newline at end of file diff --git a/ci/version.info.txt b/ci/version.info.txt index 8c073eb..7b36c08 100644 --- a/ci/version.info.txt +++ b/ci/version.info.txt @@ -1,3 +1,10 @@ +v1.7.0 +- [x] ✨固定导出路径 +- [x] ✨ISO可以直接导出漫画到文件浏览器 +- [x] ♻️在首页选择分流亦可测速 +- [x] ♻️导入导出文案以及UI的优化 + + v1.6.8 - [x] ✨选择API分流以及图片分流时进行测速 diff --git a/lib/basic/Method.dart b/lib/basic/Method.dart index 29261ee..d2f5d8c 100644 --- a/lib/basic/Method.dart +++ b/lib/basic/Method.dart @@ -993,4 +993,20 @@ class Method { Future stopWebServer() { return _flatInvoke("stopWebServer", ""); } + + Future androidDefaultExportsDir() async { + return await _channel.invokeMethod("androidDefaultExportsDir"); + } + + Future getHomeDir() { + return _flatInvoke("getHomeDir", ""); + } + + Future mkdirs(String path) { + return _flatInvoke("mkdirs", path); + } + + Future androidMkdirs(String path) async { + return await _channel.invokeMethod("androidMkdirs", path); + } } diff --git a/lib/basic/config/Address.dart b/lib/basic/config/Address.dart index 87b3cc9..4f8236b 100644 --- a/lib/basic/config/Address.dart +++ b/lib/basic/config/Address.dart @@ -31,35 +31,6 @@ String currentAddress() => _currentAddress; String currentAddressName() => _addresses[_currentAddress] ?? ""; -Future chooseAddress(BuildContext context) async { - String? choose = await showDialog( - context: context, - builder: (BuildContext context) { - return SimpleDialog( - title: const Text('选择分流'), - children: [ - ..._addresses.entries.map( - (e) => SimpleDialogOption( - child: ApiOptionRow( - e.value, - e.key, - key: Key("API:${e.key}"), - ), - onPressed: () { - Navigator.of(context).pop(e.key); - }, - ), - ), - ], - ); - }, - ); - if (choose != null) { - await method.setSwitchAddress(choose); - _currentAddress = choose; - } -} - Widget switchAddressSetting() { return StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { @@ -67,7 +38,7 @@ Widget switchAddressSetting() { title: const Text("分流"), subtitle: Text(currentAddressName()), onTap: () async { - await chooseAddress(context); + await chooseAddressAndSwitch(context); setState(() {}); }, ); @@ -118,8 +89,12 @@ Future chooseAddressAndSwitch(BuildContext context) async { title: const Text('选择分流'), children: [ ..._addresses.entries.map( - (e) => SimpleDialogOption( - child: Text(e.value), + (e) => SimpleDialogOption( + child: ApiOptionRow( + e.value, + e.key, + key: Key("API:${e.key}"), + ), onPressed: () { Navigator.of(context).pop(e.key); }, diff --git a/lib/basic/config/ChooserRoot.dart b/lib/basic/config/ChooserRoot.dart index 1843276..c7b75e4 100644 --- a/lib/basic/config/ChooserRoot.dart +++ b/lib/basic/config/ChooserRoot.dart @@ -10,30 +10,22 @@ import '../Method.dart'; const _propertyName = "chooserRoot"; late String _chooserRoot; -late String _androidDefaultRoot; Future initChooserRoot() async { _chooserRoot = await method.loadProperty(_propertyName, ""); - if (Platform.isAndroid) { - _androidDefaultRoot = await method.androidStorageRoot(); - } -} - -String _currentChooserRoot() { - if (_chooserRoot == "") { - if (Platform.isWindows) { - return '/'; - } else if (Platform.isMacOS) { - return '/Users'; - } else if (Platform.isLinux) { - return '/'; - } else if (Platform.isAndroid) { - return _androidDefaultRoot; - } else { - return ''; + if (_chooserRoot.isEmpty) { + if (Platform.isAndroid) { + try { + _chooserRoot = await method.androidStorageRoot(); + } catch (e) { + _chooserRoot = "/sdcard"; + } + } else if (Platform.isMacOS || Platform.isLinux) { + _chooserRoot = await method.getHomeDir(); + } else if (Platform.isWindows) { + _chooserRoot = "/"; } } - return _chooserRoot; } Future currentChooserRoot() async { @@ -42,7 +34,7 @@ Future currentChooserRoot() async { throw Exception("申请权限被拒绝"); } } - return _currentChooserRoot(); + return _chooserRoot; } Future _inputChooserRoot(BuildContext context) async { @@ -67,7 +59,7 @@ Widget chooserRootSetting() { builder: (BuildContext context, void Function(void Function()) setState) { return ListTile( title: const Text("文件夹选择器默认路径"), - subtitle: Text(_currentChooserRoot()), + subtitle: Text(_chooserRoot), onTap: () async { await _inputChooserRoot(context); setState(() {}); diff --git a/lib/basic/config/ExportPath.dart b/lib/basic/config/ExportPath.dart new file mode 100644 index 0000000..c0c546a --- /dev/null +++ b/lib/basic/config/ExportPath.dart @@ -0,0 +1,120 @@ +/// 文件夹选择器的根路径 + +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; +import '../Cross.dart'; +import '../Method.dart'; + +const _propertyName = "exportPath"; +late String _exportPath; + +Future initExportPath() async { + _exportPath = await method.loadProperty(_propertyName, ""); + if (_exportPath.isEmpty) { + if (Platform.isAndroid) { + try { + _exportPath = await method.androidDefaultExportsDir(); + } catch (e) { + _exportPath = "/sdcard/Download/pikapika/exports"; + } + } else if (Platform.isMacOS || Platform.isLinux) { + _exportPath = await method.getHomeDir(); + if (Platform.isMacOS) { + _exportPath = _exportPath + "/Downloads"; + } + } else if (Platform.isWindows) { + _exportPath = "exports"; + } + } +} + +Future attachExportPath() async { + late String path; + if (Platform.isIOS) { + path = await method.iosGetDocumentDir(); + } else { + if (Platform.isAndroid) { + if (!(await Permission.storage.request()).isGranted) { + throw Exception("申请权限被拒绝"); + } + } + path = _exportPath; + } + if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) { + await method.mkdirs(path); + } else if (Platform.isAndroid) { + await method.androidMkdirs(path); + } + return path; +} + +String showExportPath() { + if (Platform.isIOS) { + return "\n\n随后可在文件管理中找到导出的内容"; + } + return "\n\n$_exportPath"; +} + +Future _setExportPath(String folder) async { + await method.saveProperty(_propertyName, folder); + _exportPath = folder; +} + +Widget displayExportPathInfo() { + return StatefulBuilder( + builder: (BuildContext context, void Function(void Function()) setState) { + if (Platform.isIOS) { + return Container( + margin: const EdgeInsets.all(15), + padding: const EdgeInsets.all(15), + color: (Theme.of(context).textTheme.bodyText1?.color ?? Colors.black) + .withOpacity(.01), + child: const Text("您正在使用iOS设备:\n导出到文件的内容请打开系统自带文件管理进行浏览"), + ); + } + return Column(children: [ + MaterialButton( + onPressed: () async { + String? choose = await chooseFolder(context); + if (choose != null) { + _setExportPath(choose); + } + setState(() {}); + }, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + width: constraints.maxWidth, + padding: const EdgeInsets.only(top: 15, bottom: 15), + color: (Theme.of(context).textTheme.bodyText1?.color ?? + Colors.black) + .withOpacity(.05), + child: Text( + "导出路径 (点击可修改):\n" + "$_exportPath", + textAlign: TextAlign.center, + ), + ); + }, + ), + ), + ...Platform.isAndroid + ? [ + Container(height: 15), + Container( + margin: const EdgeInsets.all(15), + padding: const EdgeInsets.all(15), + color: (Theme.of(context).textTheme.bodyText1?.color ?? + Colors.black) + .withOpacity(.01), + child: const Text( + "您正在使用安卓设备:\n如果不能成功导出并且提示权限不足, 可以尝试在Download或Document下建立子目录进行导出", + ), + ), + ] + : [], + ]); + }, + ); +} diff --git a/lib/basic/config/ImportNotice.dart b/lib/basic/config/ImportNotice.dart new file mode 100644 index 0000000..993a95d --- /dev/null +++ b/lib/basic/config/ImportNotice.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +Widget importNotice(BuildContext context) { + if (Platform.isAndroid) { + return Container( + margin: const EdgeInsets.all(15), + padding: const EdgeInsets.all(15), + color: (Theme + .of(context) + .textTheme + .bodyText1 + ?.color ?? Colors.black) + .withOpacity(.01), + child: const Text( + "您正在使用安卓设备:\n如果不能导入导出并且提示权限不足, 可以尝试在Download或Document下建立子目录进行导入", + ), + ); + } + return Container(); +} diff --git a/lib/screens/DownloadExportToFileScreen.dart b/lib/screens/DownloadExportToFileScreen.dart index 3cd9ff1..8387072 100644 --- a/lib/screens/DownloadExportToFileScreen.dart +++ b/lib/screens/DownloadExportToFileScreen.dart @@ -1,17 +1,14 @@ import 'dart:async'; -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:pikapika/basic/Channels.dart'; import 'package:pikapika/basic/Common.dart'; -import 'package:pikapika/basic/Cross.dart'; import 'package:pikapika/basic/Entities.dart'; import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/config/ExportRename.dart'; import 'package:pikapika/screens/DownloadExportToSocketScreen.dart'; - +import '../basic/config/ExportPath.dart'; import '../basic/config/IconLoading.dart'; +import '../basic/config/IsPro.dart'; import 'components/ContentError.dart'; import 'components/ContentLoading.dart'; import 'components/DownloadInfoCard.dart'; @@ -104,11 +101,20 @@ class _DownloadExportToFileScreenState padding: const EdgeInsets.all(8), child: exportResult != "" ? Text(exportResult) : Container(), ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('TIPS : 选择一个目录'), - ), - ..._buildExportToFileButtons(), + displayExportPathInfo(), + Container(height: 15), + _exportPkzButton(), + Container(height: 10), + _exportPkiButton(), + Container(height: 10), + _exportHtmlZipButton(), + Container(height: 10), + _exportToHtmlJPEGButton(), + Container(height: 10), + _exportToJPEGSZIPButton(), + Container(height: 10), + _exportToHtmlJPEGNotDownOverButton(), + Container(height: 10), MaterialButton( onPressed: () async { Navigator.of(context).push( @@ -123,7 +129,7 @@ class _DownloadExportToFileScreenState }, child: _buildButtonInner('传输到其他设备'), ), - Container(height: 10), + Container(height: 40), ], ); }, @@ -131,648 +137,312 @@ class _DownloadExportToFileScreenState ); } - List _buildExportToFileButtons() { - List widgets = []; - if (Platform.isWindows || - Platform.isMacOS || - Platform.isLinux || - Platform.isAndroid) { - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + Widget _exportPkzButton() { + return MaterialButton( + onPressed: () async { + var name = ""; + if (currentExportRename()) { + var rename = await inputString( + context, + "请输入保存后的名称", + defaultValue: _task.title, + ); + if (rename != null && rename.isNotEmpty) { + name = rename; + } else { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadToJPG( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'), - )); - widgets.add(Container(height: 10)); - ///////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + } else { + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画导出PKZ${showExportPath()}")) { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadToPkz( - [widget.comicId], - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: - _buildButtonInner('导出到xxx.pkz\n(可直接打开观看的格式,不支持导入)\n(可以躲避网盘或者聊天软件的扫描)'), - )); - widgets.add(Container(height: 10)); - ///////////////////// - ///////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); - return; - } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadToPki( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: - _buildButtonInner('导出到xxx.pki\n(只支持导入, 不支持直接阅读)\n(可以躲避网盘或者聊天软件的扫描)\n(后期版本可能支持直接阅读)'), - )); - widgets.add(Container(height: 10)); - ///////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); - return; - } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownload( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: _buildButtonInner('导出到HTML.zip\n(可从其他设备导入 / 解压后可阅读)'), - )); - widgets.add(Container(height: 10)); - ////////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); - return; - } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicJpegsEvenNotFinish( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: _buildButtonInner('导出到HTML+JPG\n(即使没有下载成功)'), - )); - widgets.add(Container(height: 10)); - } - if (Platform.isIOS || Platform.isAndroid) { - widgets.add(MaterialButton( - onPressed: () async { - if (!(await confirmDialog(context, "导出确认", "将本漫画所有图片到相册?"))) { - return; - } - if (!(await Permission.storage.request()).isGranted) { - return; - } - try { - setState(() { - exporting = true; - }); - // 导出所有图片数据 - var count = 0; - List eps = await method.downloadEpList(widget.comicId); - for (var i = 0; i < eps.length; i++) { - var pics = await method.downloadPicturesByEpId(eps[i].id); - for (var j = 0; j < pics.length; j++) { - setState(() { - exportMessage = "导出图片 ${count++} 张"; - }); - await saveImageQuiet( - await method.downloadImagePath(pics[j].localPath), - context, - ); - } - } - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - }, - child: _buildButtonInner('将所有图片导出到手机相册'), - )); - widgets.add(Container(height: 10)); - } - return widgets; + } + try { + setState(() { + exporting = true; + }); + await method.exportComicDownloadToPkz( + [widget.comicId], + await attachExportPath(), + name, + ); + setState(() { + exportResult = "导出成功"; + }); + } catch (e) { + setState(() { + exportResult = "导出失败 $e"; + }); + } finally { + setState(() { + exporting = false; + }); + } + }, + child: _buildButtonInner( + '导出到xxx.pkz\n(可直接打开观看的格式,不支持导入)\n(可以躲避网盘或者聊天软件的扫描)', + ), + ); } - List _buildExportToJpegZipButtons() { - List widgets = []; - if (Platform.isWindows || - Platform.isMacOS || - Platform.isLinux || - Platform.isAndroid) { - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + Widget _exportPkiButton() { + return MaterialButton( + onPressed: () async { + var name = ""; + if (currentExportRename()) { + var rename = await inputString( + context, + "请输入保存后的名称", + defaultValue: _task.title, + ); + if (rename != null && rename.isNotEmpty) { + name = rename; + } else { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadJpegZip( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: _buildButtonInner('导出阅读器用JPGS.zip\n(不可再导入)'), - )); - widgets.add(Container(height: 10)); - ///////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + } else { + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画导出PKI${showExportPath()}")) { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadToPkz( - [widget.comicId], - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: - _buildButtonInner('导出到xxx.pkz\n(可直接打开观看的格式,不支持导入)\n(可以躲避网盘或者聊天软件的扫描)'), - )); - widgets.add(Container(height: 10)); - ///////////////////// - ///////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + } + try { + setState(() { + exporting = true; + }); + await method.exportComicDownloadToPki( + widget.comicId, + await attachExportPath(), + name, + ); + setState(() { + exportResult = "导出成功"; + }); + } catch (e) { + setState(() { + exportResult = "导出失败 $e"; + }); + } finally { + setState(() { + exporting = false; + }); + } + }, + child: _buildButtonInner( + '导出到xxx.pki\n(只支持导入, 不支持直接阅读)\n(可以躲避网盘或者聊天软件的扫描)\n(后期版本可能支持直接阅读)', + ), + ); + } + + Widget _exportHtmlZipButton() { + return MaterialButton( + onPressed: () async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + var name = ""; + if (currentExportRename()) { + var rename = await inputString( + context, + "请输入保存后的名称", + defaultValue: _task.title, + ); + if (rename != null && rename.isNotEmpty) { + name = rename; + } else { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadToPki( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: - _buildButtonInner('导出到xxx.pki\n(只支持导入, 不支持直接阅读)\n(可以躲避网盘或者聊天软件的扫描)\n(后期版本可能支持直接阅读)'), - )); - widgets.add(Container(height: 10)); - ///////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + } else { + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画导出HTML+ZIP${showExportPath()}")) { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownload( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: _buildButtonInner('导出到HTML.zip\n(可从其他设备导入 / 解压后可阅读)'), - )); - widgets.add(Container(height: 10)); - ////////////////////// - widgets.add(MaterialButton( - onPressed: () async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + } + try { + setState(() { + exporting = true; + }); + await method.exportComicDownload( + widget.comicId, + await attachExportPath(), + name, + ); + setState(() { + exportResult = "导出成功"; + }); + } catch (e) { + setState(() { + exportResult = "导出失败 $e"; + }); + } finally { + setState(() { + exporting = false; + }); + } + }, + child: _buildButtonInner( + '导出到HTML.zip\n(可从其他设备导入 / 解压后可阅读)' + (!isPro ? "\n(发电后使用)" : ""), + ), + ); + } + + Widget _exportToHtmlJPEGButton() { + return MaterialButton( + onPressed: () async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + var name = ""; + if (currentExportRename()) { + var rename = await inputString( + context, + "请输入保存后的名称", + defaultValue: _task.title, + ); + if (rename != null && rename.isNotEmpty) { + name = rename; + } else { return; } - var name = ""; - if (currentExportRename()) { - var rename = await inputString( - context, - "请输入保存后的名称", - defaultValue: _task.title, - ); - if (rename != null && rename.isNotEmpty) { - name = rename; - } else { - return; - } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicJpegsEvenNotFinish( - widget.comicId, - path, - name, - ); - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); - } - } - }, - child: _buildButtonInner('导出到HTML+JPG\n(即使没有下载成功)'), - )); - widgets.add(Container(height: 10)); - } - if (Platform.isIOS || Platform.isAndroid) { - widgets.add(MaterialButton( - onPressed: () async { - if (!(await confirmDialog(context, "导出确认", "将本漫画所有图片到相册?"))) { + } else { + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画导出HTML+JPEG${showExportPath()}")) { return; } - if (!(await Permission.storage.request()).isGranted) { + } + try { + setState(() { + exporting = true; + }); + await method.exportComicDownloadToJPG( + widget.comicId, + await attachExportPath(), + name, + ); + setState(() { + exportResult = "导出成功"; + }); + } catch (e) { + setState(() { + exportResult = "导出失败 $e"; + }); + } finally { + setState(() { + exporting = false; + }); + } + }, + child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'), + ); + } + + Widget _exportToJPEGSZIPButton() { + return MaterialButton( + onPressed: () async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + var name = ""; + if (currentExportRename()) { + var rename = await inputString( + context, + "请输入保存后的名称", + defaultValue: _task.title, + ); + if (rename != null && rename.isNotEmpty) { + name = rename; + } else { return; } - try { - setState(() { - exporting = true; - }); - // 导出所有图片数据 - var count = 0; - List eps = await method.downloadEpList(widget.comicId); - for (var i = 0; i < eps.length; i++) { - var pics = await method.downloadPicturesByEpId(eps[i].id); - for (var j = 0; j < pics.length; j++) { - setState(() { - exportMessage = "导出图片 ${count++} 张"; - }); - await saveImageQuiet( - await method.downloadImagePath(pics[j].localPath), - context, - ); - } - } - setState(() { - exportResult = "导出成功"; - }); - } catch (e) { - setState(() { - exportResult = "导出失败 $e"; - }); - } finally { - setState(() { - exporting = false; - }); + } else { + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画导出JPEGS.zip${showExportPath()}")) { + return; } - }, - child: _buildButtonInner('将所有图片导出到手机相册'), - )); - widgets.add(Container(height: 10)); - } - return widgets; + } + try { + setState(() { + exporting = true; + }); + await method.exportComicDownloadJpegZip( + widget.comicId, + await attachExportPath(), + name, + ); + setState(() { + exportResult = "导出成功"; + }); + } catch (e) { + setState(() { + exportResult = "导出失败 $e"; + }); + } finally { + setState(() { + exporting = false; + }); + } + }, + child: _buildButtonInner( + '导出阅读器用JPGS.zip\n(不可再导入)' + (!isPro ? "\n(发电后使用)" : ""), + ), + ); + } + + Widget _exportToHtmlJPEGNotDownOverButton() { + return MaterialButton( + onPressed: () async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + var name = ""; + if (currentExportRename()) { + var rename = await inputString( + context, + "请输入保存后的名称", + defaultValue: _task.title, + ); + if (rename != null && rename.isNotEmpty) { + name = rename; + } else { + return; + } + } else { + if (!await confirmDialog(context, "导出确认", + "将您所选的漫画导出HTML+JPEGS(即使没有下载完成)${showExportPath()}")) { + return; + } + } + try { + setState(() { + exporting = true; + }); + await method.exportComicJpegsEvenNotFinish( + widget.comicId, + await attachExportPath(), + name, + ); + setState(() { + exportResult = "导出成功"; + }); + } catch (e) { + setState(() { + exportResult = "导出失败 $e"; + }); + } finally { + setState(() { + exporting = false; + }); + } + }, + child: _buildButtonInner( + '导出到HTML+JPG\n(即使没有下载成功)' + (!isPro ? "\n(发电后使用)" : ""), + ), + ); } Widget _buildButtonInner(String text) { @@ -780,7 +450,7 @@ class _DownloadExportToFileScreenState builder: (BuildContext context, BoxConstraints constraints) { return Container( width: constraints.maxWidth, - padding: const EdgeInsets.only(top: 15, bottom: 15), + padding: const EdgeInsets.all(15), color: (Theme.of(context).textTheme.bodyText1?.color ?? Colors.black) .withOpacity(.05), child: Text( diff --git a/lib/screens/DownloadExportingGroupScreen.dart b/lib/screens/DownloadExportingGroupScreen.dart index fd2e43c..1ec68bf 100644 --- a/lib/screens/DownloadExportingGroupScreen.dart +++ b/lib/screens/DownloadExportingGroupScreen.dart @@ -6,6 +6,7 @@ import 'package:pikapika/basic/Common.dart'; import '../basic/Channels.dart'; import '../basic/Cross.dart'; import '../basic/Method.dart'; +import '../basic/config/ExportPath.dart'; import '../basic/config/ExportRename.dart'; import '../basic/config/IsPro.dart'; import 'components/ContentLoading.dart'; @@ -58,20 +59,39 @@ class _DownloadExportingGroupScreenState } return ListView( children: [ + Container(height: 20), + displayExportPathInfo(), Container(height: 20), MaterialButton( onPressed: _exportPkz, - child: const Text("导出PKZ"), + child: _buildButtonInner("导出成一个PKZ\n(加密模式,防止网盘检测,能用pikapika打开观看)"), ), Container(height: 20), MaterialButton( onPressed: _exportPkis, - child: Text("分别导出PKI" + (!isPro ? "\n(发电后使用)" : "")), + child: _buildButtonInner("每部漫画都打包一个PKI\n(加密模式,防止网盘检测,能用pikapika导入)"), ), Container(height: 20), MaterialButton( onPressed: _exportZips, - child: Text("分别导出ZIP" + (!isPro ? "\n(发电后使用)" : "")), + child: _buildButtonInner( + "每部漫画都打包一个ZIP\n(不加密模式,能用pikapika导入或网页浏览器观看)" + + (!isPro ? "\n(发电后使用)" : "")), + ), + Container(height: 20), + MaterialButton( + onPressed: _exportToJPEGSZips, + child: _buildButtonInner( + "每部漫画都打包一个ZIP+JPEG\n(能直接使用其他阅读器看,不可再导入)" + + (!isPro ? "\n(发电后使用)" : ""), + ), + ), + Container(height: 20), + MaterialButton( + onPressed: _exportToJPEGSFolders, + child: _buildButtonInner( + "每部漫画都导出成文件夹+JPEG" + (!isPro ? "\n(发电后使用)" : ""), + ), ), Container(height: 20), ], @@ -79,15 +99,6 @@ class _DownloadExportingGroupScreenState } _exportPkz() async { - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); - return; - } var name = ""; if (currentExportRename()) { var rename = await inputString( @@ -100,63 +111,53 @@ class _DownloadExportingGroupScreenState } else { return; } - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportComicDownloadToPkz( - widget.idList, - path, - name, - ); - exported = true; - } catch (err) { - e = err; - exportFail = true; - } finally { - setState(() { - exporting = false; - }); + } else { + if (!await confirmDialog( + context, "导出确认", "将导出您所选的漫画为一个PKZ${showExportPath()}")) { + return; } } + try { + setState(() { + exporting = true; + }); + await method.exportComicDownloadToPkz( + widget.idList, + await attachExportPath(), + name, + ); + exported = true; + } catch (err) { + e = err; + exportFail = true; + } finally { + setState(() { + exporting = false; + }); + } } _exportPkis() async { - if (!isPro) { - defaultToast(context, "请先发电鸭"); + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画分别导出成单独的PKI${showExportPath()}")) { return; } - late String? path; try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); - return; - } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportAnyComicDownloadsToPki( - widget.idList, - path, - ); - exported = true; - } catch (err) { - e = err; - exportFail = true; - } finally { - setState(() { - exporting = false; - }); - } + setState(() { + exporting = true; + }); + await method.exportAnyComicDownloadsToPki( + widget.idList, + await attachExportPath(), + ); + exported = true; + } catch (err) { + e = err; + exportFail = true; + } finally { + setState(() { + exporting = false; + }); } } @@ -165,34 +166,90 @@ class _DownloadExportingGroupScreenState defaultToast(context, "请先发电鸭"); return; } - late String? path; - try { - path = Platform.isIOS - ? await method.iosGetDocumentDir() - : await chooseFolder(context); - } catch (e) { - defaultToast(context, "$e"); + if (!await confirmDialog( + context, "导出确认", "将导出您所选的漫画分别导出ZIP${showExportPath()}")) { return; } - print("path $path"); - if (path != null) { - try { - setState(() { - exporting = true; - }); - await method.exportAnyComicDownloadsToZip( - widget.idList, + try { + setState(() { + exporting = true; + }); + await method.exportAnyComicDownloadsToZip( + widget.idList, + await attachExportPath(), + ); + exported = true; + } catch (err) { + e = err; + exportFail = true; + } finally { + setState(() { + exporting = false; + }); + } + } + + _exportToJPEGSZips() async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画分别导出ZIP+JPEG${showExportPath()}")) { + return; + } + try { + setState(() { + exporting = true; + }); + final path = await attachExportPath(); + for (var id in widget.idList) { + await method.exportComicDownloadJpegZip( + id, path, + "", ); - exported = true; - } catch (err) { - e = err; - exportFail = true; - } finally { - setState(() { - exporting = false; - }); } + exported = true; + } catch (err) { + e = err; + exportFail = true; + } finally { + setState(() { + exporting = false; + }); + } + } + + _exportToJPEGSFolders() async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + if (!await confirmDialog( + context, "导出确认", "将您所选的漫画分别导出文件夹+JPEG${showExportPath()}")) { + return; + } + try { + setState(() { + exporting = true; + }); + final path = await attachExportPath(); + for (var id in widget.idList) { + await method.exportComicDownloadToJPG( + id, + path, + "", + ); + } + exported = true; + } catch (err) { + e = err; + exportFail = true; + } finally { + setState(() { + exporting = false; + }); } } @@ -214,4 +271,21 @@ class _DownloadExportingGroupScreenState }, ); } + + Widget _buildButtonInner(String text) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + width: constraints.maxWidth, + padding: const EdgeInsets.all(15), + color: (Theme.of(context).textTheme.bodyText1?.color ?? Colors.black) + .withOpacity(.05), + child: Text( + text, + textAlign: TextAlign.center, + ), + ); + }, + ); + } } diff --git a/lib/screens/DownloadImportScreen.dart b/lib/screens/DownloadImportScreen.dart index f39c516..2209a15 100644 --- a/lib/screens/DownloadImportScreen.dart +++ b/lib/screens/DownloadImportScreen.dart @@ -10,6 +10,7 @@ import 'package:pikapika/basic/config/ChooserRoot.dart'; import '../basic/Cross.dart'; import '../basic/config/IconLoading.dart'; +import '../basic/config/ImportNotice.dart'; import '../basic/config/IsPro.dart'; import 'PkzArchiveScreen.dart'; import 'components/ContentLoading.dart'; @@ -63,12 +64,6 @@ class _DownloadImportScreenState extends State { ); } - List actions = []; - - actions.add(_fileImportButton()); - actions.add(_networkImportButton()); - actions.add(_importDirFilesZipButton()); - return Scaffold( appBar: AppBar( title: const Text('导入'), @@ -79,7 +74,15 @@ class _DownloadImportScreenState extends State { padding: const EdgeInsets.all(10), child: Text(_importMessage), ), - ...actions, + Container(height: 20), + importNotice(context), + Container(height: 20), + _fileImportButton(), + Container(height: 20), + _networkImportButton(), + Container(height: 20), + _importDirFilesZipButton(), + Container(height: 40), ], ), ); @@ -151,10 +154,20 @@ class _DownloadImportScreenState extends State { } } }, - child: const Text( - '选择zip文件进行导入\n选择pki文件进行导入\n选择pkz文件进行阅读', - style: TextStyle(), - textAlign: TextAlign.center, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + width: constraints.maxWidth, + padding: const EdgeInsets.only(top: 15, bottom: 15), + color: + (Theme.of(context).textTheme.bodyText1?.color ?? Colors.black) + .withOpacity(.05), + child: const Text( + '选择zip文件进行导入\n选择pki文件进行导入\n选择pkz文件进行阅读', + textAlign: TextAlign.center, + ), + ); + }, ), ); } @@ -185,7 +198,21 @@ class _DownloadImportScreenState extends State { } } }, - child: const Text('从其他设备导入'), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + width: constraints.maxWidth, + padding: const EdgeInsets.only(top: 15, bottom: 15), + color: + (Theme.of(context).textTheme.bodyText1?.color ?? Colors.black) + .withOpacity(.05), + child: const Text( + '从其他设备导入', + textAlign: TextAlign.center, + ), + ); + }, + ), ); } @@ -220,10 +247,20 @@ class _DownloadImportScreenState extends State { } } }, - child: Text( - '选择文件夹\n(导入里面所有的zip/pki)' + (!isPro ? "\n(发电后使用)" : ""), - style: TextStyle(), - textAlign: TextAlign.center, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + width: constraints.maxWidth, + padding: const EdgeInsets.only(top: 15, bottom: 15), + color: + (Theme.of(context).textTheme.bodyText1?.color ?? Colors.black) + .withOpacity(.05), + child: Text( + '选择文件夹\n(导入里面所有的zip/pki)' + (!isPro ? "\n(发电后使用)" : ""), + textAlign: TextAlign.center, + ), + ); + }, ), ); } diff --git a/lib/screens/DownloadListScreen.dart b/lib/screens/DownloadListScreen.dart index 0a3f75b..acbb1b8 100644 --- a/lib/screens/DownloadListScreen.dart +++ b/lib/screens/DownloadListScreen.dart @@ -30,11 +30,9 @@ class _DownloadListScreenState extends State { inBar: false, setState: setState, onSubmitted: (value) { - if (value.isNotEmpty) { - _search = value; - _f = method.allDownloads(_search); - _searchBar.controller.text = value; - } + _search = value; + _f = method.allDownloads(_search); + _searchBar.controller.text = value; }, buildDefaultAppBar: (BuildContext context) { return AppBar( diff --git a/lib/screens/InitScreen.dart b/lib/screens/InitScreen.dart index bf89b80..ad0fced 100644 --- a/lib/screens/InitScreen.dart +++ b/lib/screens/InitScreen.dart @@ -42,6 +42,7 @@ import 'package:pikapika/screens/PkzArchiveScreen.dart'; import 'package:uni_links/uni_links.dart'; import 'package:uri_to_file/uri_to_file.dart'; import '../basic/config/DownloadCachePath.dart'; +import '../basic/config/ExportPath.dart'; import '../basic/config/ExportRename.dart'; import '../basic/config/IconLoading.dart'; import '../basic/config/IsPro.dart'; @@ -94,6 +95,7 @@ class _InitScreenState extends State { await initKeyboardController(); await initAndroidDisplayMode(); await initChooserRoot(); + await initExportPath(); await initTimeZone(); await initDownloadAndExportPath(); await initAndroidSecureFlag(); diff --git a/lib/screens/components/ImageReader.dart b/lib/screens/components/ImageReader.dart index 3a402af..e22fe98 100644 --- a/lib/screens/components/ImageReader.dart +++ b/lib/screens/components/ImageReader.dart @@ -906,7 +906,7 @@ class _SettingPanelState extends State<_SettingPanel> { icon: Icons.shuffle, title: currentAddressName(), onPressed: () async { - await chooseAddress(context); + await chooseAddressAndSwitch(context); setState(() {}); }, ), diff --git a/pubspec.lock b/pubspec.lock index 070d137..551bc2e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -273,10 +273,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "50e882fe0a06bf0c8f7f5bce78d30975f279213293afc9471dc35f05617c50ff" + sha256: d4cb8ab04f770dab9d04c7959e5f6d22e8c5280343d425f9344f93832cf58445 url: "https://pub.dev" source: hosted - version: "0.8.7+1" + version: "0.8.7+2" image_picker_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6a6fc73..c6cedab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.6.8+15 +version: 1.7.0+16 environment: sdk: ">=2.12.0 <3.0.0"