diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 4655071..35b1a55 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: env: - go_version: '1.17' + go_version: '1.18' flutter_channel: 'stable' GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/android/app/build.gradle b/android/app/build.gradle index 79e2e9e..85b3153 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -13,12 +13,12 @@ if (flutterRoot == null) { def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { - flutterVersionCode = '1' + flutterVersionCode = '2' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { - flutterVersionName = '1.0' + flutterVersionName = '1.0.1' } apply plugin: 'com.android.application' diff --git a/ci/version.code.txt b/ci/version.code.txt index b4e1736..c5c4119 100644 --- a/ci/version.code.txt +++ b/ci/version.code.txt @@ -1 +1 @@ -v1.5.4 \ No newline at end of file +v1.5.5 \ No newline at end of file diff --git a/ci/version.info.txt b/ci/version.info.txt index 7217012..74076b5 100644 --- a/ci/version.info.txt +++ b/ci/version.info.txt @@ -1,11 +1,13 @@ 更新 -- [x] 苹果手机支持导出导出了 -- [x] 修复了安卓导入时不能选择文件 (AOSP个别机型的问题) -- [x] 增加了导出 PKI/PKZ 等格式 -- [x] 苹果/安卓 在文件管理器中可以直接打开 PKI/PKZ 导入或观看 -- [x] 增加了骑士榜 +- [x] 对历史记录页面进行优化 +- [x] 对导入进行优化 +- [x] 增加了批量导出ZIP/PKI到文件夹 +- [x] 增加了从一个文件夹中导入所有ZIP/PKI的功能 +- [x] 增加了发电页面, 对作者发过电的用户会展示发电特权图标 计划 - [ ] 本地骑士书签 (漫画书签?/快捷搜索书签?) +- [ ] 将Jasmine导出的PKI/JMI/导入, 阅读Jasmine导出的PKI + diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 08b3866..b88282e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -359,7 +359,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -368,10 +368,12 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -491,7 +493,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -500,11 +502,13 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -515,7 +519,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -524,10 +528,12 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 656fdbe..6d271d5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,15 +2,6 @@ - CFBundleURLTypes - - - CFBundleURLSchemes - - pika - - - CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -31,36 +22,6 @@ - UTExportedTypeDeclarations - - - UTTypeIdentifier - niuhuan.pkz - UTTypeConformsTo - - public.data - public.content - com.apple.package - - UTTypeDescription - PKZ Archive - UTTypeTagSpecification - - public.filename-extension - - pkz - pki - zip - - public.mime-type - - text/vnd.niuhuan.pkz - text/vnd.niuhuan.pki - text/vnd.niuhuan.zip - - - - CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -72,11 +33,20 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) + $(MARKETING_VERSION) CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + pika + + + CFBundleVersion - $(FLUTTER_BUILD_NUMBER) + $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType public.app-category.entertainment LSRequiresIPhoneOS @@ -104,5 +74,35 @@ UIViewControllerBasedStatusBarAppearance + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.data + public.content + com.apple.package + + UTTypeDescription + PKZ Archive + UTTypeIdentifier + niuhuan.pkz + UTTypeTagSpecification + + public.filename-extension + + pkz + pki + zip + + public.mime-type + + text/vnd.niuhuan.pkz + text/vnd.niuhuan.pki + text/vnd.niuhuan.zip + + + + diff --git a/lib/basic/Entities.dart b/lib/basic/Entities.dart index 9045938..179d1c5 100644 --- a/lib/basic/Entities.dart +++ b/lib/basic/Entities.dart @@ -1012,3 +1012,13 @@ class PkzComicViewLog { return _data; } } + +class IsPro { + late bool isPro; + late int expire; + + IsPro.fromJson(Map json) { + this.isPro = json["isPro"]; + this.expire = json["expire"]; + } +} diff --git a/lib/basic/Method.dart b/lib/basic/Method.dart index 7c03416..9858865 100644 --- a/lib/basic/Method.dart +++ b/lib/basic/Method.dart @@ -548,7 +548,8 @@ class Method { } /// 导出下载的漫画到pki - Future exportComicDownloadToPki(String comicId, String dir, String name) { + Future exportComicDownloadToPki( + String comicId, String dir, String name) { return _flatInvoke("exportComicDownloadToPki", { "comicId": comicId, "dir": dir, @@ -582,6 +583,35 @@ class Method { }); } + /// 导出zip + Future exportAnyComicDownloadsToZip( + List comicIds, + String dir, + ) { + return _flatInvoke("exportAnyComicDownloadsToZip", { + "comicIds": comicIds, + "dir": dir, + }); + } + + /// 导出pki + Future exportAnyComicDownloadsToPki( + List comicIds, + String dir, + ) { + return _flatInvoke("exportAnyComicDownloadsToPki", { + "comicIds": comicIds, + "dir": dir, + }); + } + + /// 导入文件夹所有的文件 + Future importComicDownloadDir( + String dir, + ) { + return _flatInvoke("importComicDownloadDir", dir); + } + /// 使用网络将下载传输到其他设备 Future exportComicUsingSocket(String comicId) async { return int.parse(await _flatInvoke("exportComicUsingSocket", comicId)); @@ -833,4 +863,16 @@ class Method { .map((e) => Knight.fromJson(e)) .toList(); } + + Future isPro() async { + return IsPro.fromJson(jsonDecode(await _flatInvoke("isPro", ""))); + } + + Future reloadPro() { + return _flatInvoke("reloadPro", ""); + } + + Future inputCdKey(String cdKey) { + return _flatInvoke("inputCdKey", cdKey); + } } diff --git a/lib/basic/config/AndroidDisplayMode.dart b/lib/basic/config/AndroidDisplayMode.dart index ca84311..5a455ba 100644 --- a/lib/basic/config/AndroidDisplayMode.dart +++ b/lib/basic/config/AndroidDisplayMode.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:pikapika/basic/Method.dart'; import '../Common.dart'; +import 'IsPro.dart'; const _propertyName = "androidDisplayMode"; List _modes = []; @@ -27,7 +28,11 @@ Future _chooseAndroidDisplayMode(BuildContext context) async { if (Platform.isAndroid) { List list = [""]; list.addAll(_modes); - String? result = await chooseListDialog(context, "安卓屏幕刷新率", list); + String? result = await chooseListDialog( + context, + "安卓屏幕刷新率 \n(省电模式下不会高刷)", + list, + ); if (result != null) { await method.saveProperty(_propertyName, result); _androidDisplayMode = result; @@ -41,9 +46,18 @@ Widget androidDisplayModeSetting() { return StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { return ListTile( - title: const Text("屏幕刷新率(安卓)"), + title: Text( + "屏幕刷新率(安卓)" + (!isPro ? "(发电)" : ""), + style: TextStyle( + color: !isPro ? Colors.grey : null, + ), + ), subtitle: Text(_androidDisplayMode), onTap: () async { + if (!isPro) { + defaultToast(context, "请先发电再使用"); + return; + } await _chooseAndroidDisplayMode(context); setState(() {}); }, diff --git a/lib/basic/config/AndroidSecureFlag.dart b/lib/basic/config/AndroidSecureFlag.dart index 5a31e0d..fe6f588 100644 --- a/lib/basic/config/AndroidSecureFlag.dart +++ b/lib/basic/config/AndroidSecureFlag.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import '../Common.dart'; import '../Method.dart'; +import 'IsPro.dart'; const _propertyName = "androidSecureFlag"; @@ -37,9 +38,20 @@ Widget androidSecureFlagSetting() { return StatefulBuilder(builder: (BuildContext context, void Function(void Function()) setState) { return ListTile( - title: const Text("禁止截图/禁止显示在任务视图"), - subtitle: Text(_androidSecureFlag ? "是" : "否"), + title: Text( + "禁止截图/禁止显示在任务视图" + (!isPro ? "(发电)" : ""), + style: TextStyle( + color: !isPro ? Colors.grey : null, + ), + ), + subtitle: Text( + _androidSecureFlag ? "是" : "否", + ), onTap: () async { + if (!isPro) { + defaultToast(context, "请先发电再使用"); + return; + } await _chooseAndroidSecureFlag(context); setState(() {}); }); diff --git a/lib/basic/config/DownloadThreadCount.dart b/lib/basic/config/DownloadThreadCount.dart index 7642c96..039b522 100644 --- a/lib/basic/config/DownloadThreadCount.dart +++ b/lib/basic/config/DownloadThreadCount.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Method.dart'; +import 'IsPro.dart'; + late int _downloadThreadCount; const _values = [1, 2, 3, 4, 5]; @@ -15,9 +17,18 @@ Widget downloadThreadCountSetting() { return StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { return ListTile( - title: const Text("下载线程数"), + title: Text( + "下载线程数" + (!isPro ? "(发电)" : ""), + style: TextStyle( + color: !isPro ? Colors.grey : null, + ), + ), subtitle: Text("$_downloadThreadCount"), onTap: () async { + if (!isPro) { + defaultToast(context, "请先发电再使用"); + return; + } int? value = await chooseListDialog(context, "选择下载线程数", _values); if (value != null) { await method.saveDownloadThreadCount(value); diff --git a/lib/basic/config/ExportRename.dart b/lib/basic/config/ExportRename.dart index 822e487..53b4b0b 100644 --- a/lib/basic/config/ExportRename.dart +++ b/lib/basic/config/ExportRename.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../Common.dart'; import '../Method.dart'; +import 'IsPro.dart'; const _propertyName = "exportRename"; late bool _exportRename; @@ -29,9 +30,18 @@ Widget exportRenameSetting() { return StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { return ListTile( - title: const Text("导出时进行重命名"), + title: Text( + "导出时进行重命名" + (!isPro ? "(发电)" : ""), + style: TextStyle( + color: !isPro ? Colors.grey : null, + ), + ), subtitle: Text(_exportRename ? "是" : "否"), onTap: () async { + if (!isPro) { + defaultToast(context, "请先发电再使用"); + return; + } await _chooseExportRename(context); setState(() {}); }, diff --git a/lib/basic/config/IsPro.dart b/lib/basic/config/IsPro.dart new file mode 100644 index 0000000..3ff794f --- /dev/null +++ b/lib/basic/config/IsPro.dart @@ -0,0 +1,14 @@ +import 'package:event/event.dart'; +import 'package:pikapika/basic/Method.dart'; + +var isPro = false; +var isProEx = 0; + +final proEvent = Event(); + +Future reloadIsPro() async { + final p = await method.isPro(); + isPro = p.isPro; + isProEx = p.expire; + proEvent.broadcast(); +} diff --git a/lib/basic/config/Version.dart b/lib/basic/config/Version.dart index d2c1d22..4c53dd7 100644 --- a/lib/basic/config/Version.dart +++ b/lib/basic/config/Version.dart @@ -15,9 +15,6 @@ late String _version; String? _latestVersion; String? _latestVersionInfo; -const _propertyName = "checkVersionPeriod"; -late int _period = -1; - Future initVersion() async { // 当前版本 try { @@ -25,14 +22,6 @@ Future initVersion() async { } catch (e) { _version = "dirty"; } - // 检查周期 - _period = int.parse(await method.loadProperty(_propertyName, "0")); - if (_period > 0) { - if (DateTime.now().millisecondsSinceEpoch > _period) { - await method.saveProperty(_propertyName, "0"); - _period = 0; - } - } } var versionEvent = Event(); @@ -50,10 +39,6 @@ String? latestVersionInfo() { } Future autoCheckNewVersion() { - if (_period != 0) { - // -1 不检查, >0 未到检查时间 - return Future.value(); - } return _versionCheck(); } @@ -86,75 +71,3 @@ Future _versionCheck() async { } // else dirtyVersion versionEvent.broadcast(); } - -String _periodText() { - if (_period < 0) { - return "自动检查更新已关闭"; - } - if (_period == 0) { - return "自动检查更新已开启"; - } - return "下次检查时间 : " + - formatDateTimeToDateTime( - DateTime.fromMillisecondsSinceEpoch(_period), - ); -} - -Future _choosePeriod(BuildContext context) async { - var result = await chooseListDialog( - context, - "自动检查更新", - ["开启", "一周后", "一个月后", "一年后", "关闭"], - tips: "重启后红点会消失", - ); - switch (result) { - case "开启": - await method.saveProperty(_propertyName, "0"); - _period = 0; - break; - case "一周后": - var time = DateTime.now().millisecondsSinceEpoch + (1000 * 3600 * 24 * 7); - await method.saveProperty(_propertyName, "$time"); - _period = time; - break; - case "一个月后": - var time = - DateTime.now().millisecondsSinceEpoch + (1000 * 3600 * 24 * 30); - await method.saveProperty(_propertyName, "$time"); - _period = time; - break; - case "一年后": - var time = - DateTime.now().millisecondsSinceEpoch + (1000 * 3600 * 24 * 365); - await method.saveProperty(_propertyName, "$time"); - _period = time; - break; - case "关闭": - await method.saveProperty(_propertyName, "-1"); - _period = -1; - break; - } -} - -Widget autoUpdateCheckSetting() { - return StatefulBuilder( - builder: (BuildContext context, void Function(void Function()) setState) { - return ListTile( - title: const Text("自动检查更新"), - subtitle: Text(_periodText()), - onTap: () async { - await _choosePeriod(context); - setState(() {}); - }, - ); - }, - ); -} - -String formatDateTimeToDateTime(DateTime c) { - try { - return "${add0(c.year, 4)}-${add0(c.month, 2)}-${add0(c.day, 2)} ${add0(c.hour, 2)}:${add0(c.minute, 2)}"; - } catch (e) { - return "-"; - } -} diff --git a/lib/screens/AboutScreen.dart b/lib/screens/AboutScreen.dart index b410a36..0f06061 100644 --- a/lib/screens/AboutScreen.dart +++ b/lib/screens/AboutScreen.dart @@ -98,8 +98,6 @@ class _AboutScreenState extends State { ), ), const Divider(), - autoUpdateCheckSetting(), - const Divider(), Container( padding: const EdgeInsets.all(20), child: const SelectableText( diff --git a/lib/screens/AccountScreen.dart b/lib/screens/AccountScreen.dart index 6c9a38f..443e8db 100644 --- a/lib/screens/AccountScreen.dart +++ b/lib/screens/AccountScreen.dart @@ -5,18 +5,15 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Method.dart'; +import 'package:pikapika/basic/config/IsPro.dart'; import 'package:pikapika/basic/config/Themes.dart'; import 'package:pikapika/basic/enum/ErrorTypes.dart'; import 'package:pikapika/screens/RegisterScreen.dart'; import 'package:pikapika/screens/SettingsScreen.dart'; import 'package:pikapika/screens/components/NetworkSetting.dart'; -import 'package:uni_links/uni_links.dart'; -import 'package:uri_to_file/uri_to_file.dart'; -import '../basic/Navigator.dart'; import 'AppScreen.dart'; import 'DownloadListScreen.dart'; -import 'PkzArchiveScreen.dart'; import 'ThemeScreen.dart'; import 'components/ContentLoading.dart'; @@ -186,6 +183,7 @@ class _AccountScreenState extends State { }); try { await method.login(); + await reloadIsPro(); Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const AppScreen()), diff --git a/lib/screens/DownloadConfirmScreen.dart b/lib/screens/DownloadConfirmScreen.dart index 411c77a..aea3ee8 100644 --- a/lib/screens/DownloadConfirmScreen.dart +++ b/lib/screens/DownloadConfirmScreen.dart @@ -87,20 +87,24 @@ class _DownloadConfirmScreenState extends State { }); } } - // 如果之前下载过就将EP加入下载 - // 如果之前没有下载过就创建下载 - if (_task != null) { - await method.addDownload(create, list); - } else { - await method.createDownload(create, list); + try { + // 如果之前下载过就将EP加入下载 + // 如果之前没有下载过就创建下载 + if (_task != null) { + await method.addDownload(create, list); + } else { + await method.createDownload(create, list); + } + // 退出 + defaultToast(context, "已经加入下载列表"); + Navigator.pop(context); + } catch (e, s) { + defaultToast(context, e.toString()); } - // 退出 - defaultToast(context, "已经加入下载列表"); - Navigator.pop(context); } @override - Widget build(BuildContext context){ + Widget build(BuildContext context) { return rightClickPop( child: buildScreen(context), context: context, diff --git a/lib/screens/DownloadExportingGroupScreen.dart b/lib/screens/DownloadExportingGroupScreen.dart index c5de0d4..e4c32e8 100644 --- a/lib/screens/DownloadExportingGroupScreen.dart +++ b/lib/screens/DownloadExportingGroupScreen.dart @@ -5,6 +5,7 @@ import '../basic/Channels.dart'; import '../basic/Cross.dart'; import '../basic/Method.dart'; import '../basic/config/ExportRename.dart'; +import '../basic/config/IsPro.dart'; import 'components/ContentLoading.dart'; class DownloadExportingGroupScreen extends StatefulWidget { @@ -51,17 +52,31 @@ class _DownloadExportingGroupScreenState return Center(child: Text("导出失败\n$e")); } if (exported) { - return Center(child: Text("导出成功")); + return const Center(child: Text("导出成功")); } - return Center( - child: MaterialButton( - onPressed: _export, - child: const Text("选择导出位置"), - ), + return ListView( + children: [ + Container(height: 20), + MaterialButton( + onPressed: _exportPkz, + child: const Text("导出PKZ"), + ), + Container(height: 20), + MaterialButton( + onPressed: _exportPkis, + child: Text("分别导出PKI" + (!isPro ? "\n(发电后使用)" : "")), + ), + Container(height: 20), + MaterialButton( + onPressed: _exportZips, + child: Text("分别导出ZIP" + (!isPro ? "\n(发电后使用)" : "")), + ), + Container(height: 20), + ], ); } - _export() async { + _exportPkz() async { late String? path; try { path = await chooseFolder(context); @@ -105,6 +120,74 @@ class _DownloadExportingGroupScreenState } } + _exportPkis() async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + late String? path; + try { + path = 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; + }); + } + } + } + + _exportZips() async { + if (!isPro) { + defaultToast(context, "请先发电鸭"); + return; + } + late String? path; + try { + path = await chooseFolder(context); + } catch (e) { + defaultToast(context, "$e"); + return; + } + print("path $path"); + if (path != null) { + try { + setState(() { + exporting = true; + }); + await method.exportAnyComicDownloadsToZip( + widget.idList, + path, + ); + exported = true; + } catch (err) { + e = err; + exportFail = true; + } finally { + setState(() { + exporting = false; + }); + } + } + } + @override Widget build(BuildContext context) { return WillPopScope( diff --git a/lib/screens/DownloadImportScreen.dart b/lib/screens/DownloadImportScreen.dart index 726e7d8..e2cc995 100644 --- a/lib/screens/DownloadImportScreen.dart +++ b/lib/screens/DownloadImportScreen.dart @@ -8,6 +8,8 @@ import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/config/ChooserRoot.dart'; +import '../basic/Cross.dart'; +import '../basic/config/IsPro.dart'; import 'PkzArchiveScreen.dart'; import 'components/ContentLoading.dart'; import 'components/RightClickPop.dart'; @@ -64,6 +66,7 @@ class _DownloadImportScreenState extends State { actions.add(_fileImportButton()); actions.add(_networkImportButton()); + actions.add(_importDirFilesZipButton()); return Scaffold( appBar: AppBar( @@ -103,7 +106,7 @@ class _DownloadImportScreenState extends State { allowedExtensions: ['.pkz', '.zip', '.pki'], fileTileSelectMode: FileTileSelectMode.wholeTile, ); - }else{ + } else { var ls = await FilePicker.platform.pickFiles( dialogTitle: '选择要导入的文件', allowMultiple: false, @@ -127,9 +130,9 @@ class _DownloadImportScreenState extends State { setState(() { _importing = true; }); - if(path.endsWith(".zip")){ + if (path.endsWith(".zip")) { await method.importComicDownload(path); - } else if(path.endsWith(".pki")){ + } else if (path.endsWith(".pki")) { await method.importComicDownloadPki(path); } setState(() { @@ -184,4 +187,43 @@ class _DownloadImportScreenState extends State { child: const Text('从其他设备导入'), ); } + + Widget _importDirFilesZipButton() { + return MaterialButton( + height: 80, + onPressed: () async { + late String? path; + try { + path = await chooseFolder(context); + } catch (e) { + defaultToast(context, "$e"); + return; + } + if (path != null) { + try { + setState(() { + _importing = true; + }); + await method.importComicDownloadDir(path); + setState(() { + _importMessage = "导入成功"; + }); + } catch (e) { + setState(() { + _importMessage = "导入失败 $e"; + }); + } finally { + setState(() { + _importing = false; + }); + } + } + }, + child: Text( + '选择文件夹\n(导入里面所有的zip)' + (!isPro ? "\n(发电后使用)" : ""), + style: TextStyle(), + textAlign: TextAlign.center, + ), + ); + } } diff --git a/lib/screens/InitScreen.dart b/lib/screens/InitScreen.dart index 668a7e9..316fa39 100644 --- a/lib/screens/InitScreen.dart +++ b/lib/screens/InitScreen.dart @@ -38,6 +38,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/ExportRename.dart'; +import '../basic/config/IsPro.dart'; import 'AccountScreen.dart'; import 'AppScreen.dart'; import 'DownloadOnlyImportScreen.dart'; @@ -94,6 +95,7 @@ class _InitScreenState extends State { await initVersion(); await initUsingRightClickPop(); await initAuthentication(); + await reloadIsPro(); autoCheckNewVersion(); String? initUrl; diff --git a/lib/screens/ProScreen.dart b/lib/screens/ProScreen.dart new file mode 100644 index 0000000..3a63e6b --- /dev/null +++ b/lib/screens/ProScreen.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:pikapika/basic/Common.dart'; +import 'package:pikapika/basic/Method.dart'; + +import '../basic/config/IsPro.dart'; + +class ProScreen extends StatefulWidget { + const ProScreen({Key? key}) : super(key: key); + + @override + State createState() => _ProScreenState(); +} + +class _ProScreenState extends State { + String _username = ""; + + @override + void initState() { + method.getUsername().then((value) { + setState(() { + _username = value; + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + var min = size.width < size.height ? size.width : size.height; + return Scaffold( + appBar: AppBar( + title: const Text("发电中心"), + ), + body: ListView( + children: [ + SizedBox( + width: min / 2, + height: min / 2, + child: Center( + child: Icon( + isPro ? Icons.offline_bolt : Icons.offline_bolt_outlined, + size: min / 3, + color: Colors.grey.shade500, + ), + ), + ), + Center(child: Text(_username)), + Container(height: 20), + const Divider(), + const Padding( + padding: EdgeInsets.all(20), + child: Text( + "点击\"我曾经发过电\"进同步发电状态\n" + "点击\"我刚才发了电\"兑换作者给您的礼物卡\n" + "去\"关于\"界面找到维护地址用爱发电", + ), + ), + const Divider(), + ListTile( + title: const Text("发电详情"), + subtitle: Text( + isPro + ? "发电中 (${DateTime.fromMillisecondsSinceEpoch(1000 * isProEx).toString()})" + : "未发电", + ), + ), + const Divider(), + ListTile( + title: const Text("我曾经发过电"), + onTap: () async { + try { + await method.reloadPro(); + defaultToast(context, "SUCCESS"); + } catch (e, s) { + defaultToast(context, "FAIL"); + } + await reloadIsPro(); + setState(() {}); + }, + ), + const Divider(), + ListTile( + title: const Text("我刚才发了电"), + onTap: () async { + final code = await inputString(context, "输入代码"); + if (code != null && code.isNotEmpty) { + try { + await method.inputCdKey(code); + defaultToast(context, "SUCCESS"); + } catch (e, s) { + defaultToast(context, "FAIL"); + } + } + await reloadIsPro(); + setState(() {}); + }, + ), + const Divider(), + ], + ), + ); + } +} diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index ec3a3f4..eed52c8 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -13,6 +13,7 @@ import 'package:pikapika/basic/config/DownloadThreadCount.dart'; import 'package:pikapika/basic/config/ExportRename.dart'; import 'package:pikapika/basic/config/FullScreenAction.dart'; import 'package:pikapika/basic/config/FullScreenUI.dart'; +import 'package:pikapika/basic/config/IsPro.dart'; import 'package:pikapika/basic/config/KeyboardController.dart'; import 'package:pikapika/basic/config/NoAnimation.dart'; import 'package:pikapika/basic/config/PagerAction.dart'; @@ -113,8 +114,6 @@ class SettingsScreen extends StatelessWidget { const Divider(), migrate(context), const Divider(), - autoUpdateCheckSetting(), - const Divider(), ], ), ); @@ -122,9 +121,18 @@ class SettingsScreen extends StatelessWidget { Widget migrate(BuildContext context) { if (Platform.isAndroid) { return ListTile( - title: const Text("文件迁移"), - subtitle: const Text("更换您的数据文件夹"), + title: Text( + "文件迁移" + (!isPro ? "(发电)" : ""), + style: TextStyle( + color: !isPro ? Colors.grey : null, + ), + ), + subtitle: const Text("更换您的数据文件夹到内存卡"), onTap: () async { + if (!isPro) { + defaultToast(context, "请先发电再使用"); + return; + } var f = await confirmDialog(context, "文件迁移", "此功能菜单保存后, 需要重启程序, 您确认吗"); if (f) { diff --git a/lib/screens/SpaceScreen.dart b/lib/screens/SpaceScreen.dart index cb900ac..94d9365 100644 --- a/lib/screens/SpaceScreen.dart +++ b/lib/screens/SpaceScreen.dart @@ -5,10 +5,12 @@ import 'package:pikapika/screens/AboutScreen.dart'; import 'package:pikapika/screens/AccountScreen.dart'; import 'package:pikapika/screens/DownloadListScreen.dart'; import 'package:pikapika/screens/FavouritePaperScreen.dart'; +import 'package:pikapika/screens/ProScreen.dart'; import 'package:pikapika/screens/ThemeScreen.dart'; import 'package:pikapika/screens/ViewLogsScreen.dart'; import 'package:pikapika/basic/Method.dart'; +import '../basic/config/IsPro.dart'; import '../basic/config/Themes.dart'; import 'SettingsScreen.dart'; import 'components/Badge.dart'; @@ -25,17 +27,19 @@ class SpaceScreen extends StatefulWidget { class _SpaceScreenState extends State { @override void initState() { - versionEvent.subscribe(_onVersion); + versionEvent.subscribe(_onEvent); + proEvent.subscribe(_onEvent); super.initState(); } @override void dispose() { - versionEvent.unsubscribe(_onVersion); + versionEvent.unsubscribe(_onEvent); + proEvent.unsubscribe(_onEvent); super.dispose(); } - void _onVersion(dynamic a) { + void _onEvent(dynamic a) { setState(() {}); } @@ -73,6 +77,17 @@ class _SpaceScreenState extends State { badge: latestVersion() == null ? null : "1", ), ), + IconButton( + onPressed: () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (BuildContext context) { + return const ProScreen(); + })); + }, + icon: Icon( + isPro ? Icons.offline_bolt : Icons.offline_bolt_outlined, + ), + ), IconButton( onPressed: () { Navigator.push( diff --git a/lib/screens/ViewLogsScreen.dart b/lib/screens/ViewLogsScreen.dart index a1423ec..c8d18e4 100644 --- a/lib/screens/ViewLogsScreen.dart +++ b/lib/screens/ViewLogsScreen.dart @@ -1,8 +1,12 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Method.dart'; +import 'package:pikapika/screens/components/ComicInfoCard.dart'; import 'package:pikapika/screens/components/RightClickPop.dart'; +import '../basic/Entities.dart'; import 'ComicInfoScreen.dart'; import 'components/Images.dart'; @@ -19,7 +23,7 @@ class _ViewLogsScreenState extends State { static const _scrollPhysics = AlwaysScrollableScrollPhysics(); // 即使不足一页仍可滚动 final _scrollController = ScrollController(); - final _comicList = []; + final _comicList = []; var _isLoading = false; // 是否加载中 var _scrollOvered = false; // 滚动到最后 @@ -70,8 +74,7 @@ class _ViewLogsScreenState extends State { if (page.isEmpty) { _scrollOvered = true; } else { - _comicList.addAll(page.map((e) => - ViewLogWrapEntity(e.id, e.title, e.thumbFileServer, e.thumbPath))); + _comicList.addAll(page); } _offset += _pageSize; } finally { @@ -106,25 +109,37 @@ class _ViewLogsScreenState extends State { @override Widget build(BuildContext context) { + var entries = _comicList.map((e) { + return InkWell( + onTap: () { + _chooseComic(e.id); + }, + onLongPress: () { + _clearOnce(e.id); + }, + child: ViewInfoCard( + fileServer: e.thumbFileServer, + author: e.author, + categories: _decodeCate(e.categories), + path: e.thumbPath, + title: e.title, + ), + ); + }); + final screen = NotificationListener( child: Scaffold( appBar: AppBar( title: const Text('浏览记录'), actions: [ - IconButton(onPressed: _clearAll, icon: const Icon(Icons.auto_delete)), + IconButton( + onPressed: _clearAll, icon: const Icon(Icons.auto_delete)), ], ), body: ListView( physics: _scrollPhysics, controller: _scrollController, - children: [ - Container(height: 10), - ViewLogWrap( - onTapComic: _chooseComic, - comics: _comicList, - onDelete: _clearOnce, - ), - ], + children: entries.toList(), ), ), onNotification: (scrollNotification) { @@ -151,88 +166,90 @@ class _ViewLogsScreenState extends State { ), ); } + + List _decodeCate(String categories) { + try { + var decode = jsonDecode(categories); + if (decode is List) { + return List.of(decode).cast(); + } + return [decode]; + } catch (e) { + return [categories]; + } + } } -class ViewLogWrap extends StatelessWidget { - final Function(String) onTapComic; - final List comics; - final Function(String id) onDelete; +class ViewInfoCard extends StatelessWidget { + final String fileServer; + final String path; + final String title; + final String author; + final List categories; - const ViewLogWrap({ + const ViewInfoCard({ Key? key, - required this.onTapComic, - required this.comics, - required this.onDelete, + required this.fileServer, + required this.path, + required this.title, + required this.author, + required this.categories, }) : super(key: key); @override Widget build(BuildContext context) { - var size = MediaQuery.of(context).size; - var min = size.width < size.height ? size.width : size.height; - var width = (min - 45) / 4; - - var entries = comics.map((e) { - return InkWell( - key: e.key, - onTap: () { - onTapComic(e.id); - }, - onLongPress: () { - onDelete(e.id); - }, - child: Card( - child: SizedBox( - width: width, - child: Column( + var theme = Theme.of(context); + return Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: theme.dividerColor, + ), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.only(right: 10), + child: RemoteImage( + fileServer: fileServer, + path: path, + width: imageWidth, + height: imageHeight, + ), + ), + Expanded( + child: Row( children: [ - LayoutBuilder(builder: - (BuildContext context, BoxConstraints constraints) { - return RemoteImage( - width: constraints.maxWidth, - fileServer: e.fileServer, - path: e.path); - }), - Text( - e.title + '\n', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(height: 1.4), - strutStyle: const StrutStyle(height: 1.4), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: titleStyle), + Container(height: 5), + Text(author, style: authorStyle), + Container(height: 5), + Text.rich( + TextSpan(text: "分类 : ${categories.join(' ')}"), + style: TextStyle( + fontSize: 13, + color: Theme.of(context) + .textTheme + .bodyText1! + .color! + .withAlpha(0xCC), + ), + ), + Container(height: 5), + ], + ), ), ], ), ), - ), - ); - }); - - Map> map = {}; - for (var i = 0; i < entries.length; i++) { - late List list; - if (i % 4 == 0) { - list = []; - map[i ~/ 4] = list; - } else { - list = map[i ~/ 4]!; - } - list.add(entries.elementAt(i)); - } - - return Column( - children: map.values.map((e) => Wrap( - alignment: WrapAlignment.spaceAround, - children: e, - )).toList(), + ], + ), ); } } - -class ViewLogWrapEntity { - final Key key = UniqueKey(); - final String id; - final String title; - final String fileServer; - final String path; - - ViewLogWrapEntity(this.id, this.title, this.fileServer, this.path); -} diff --git a/lib/screens/components/ComicPager.dart b/lib/screens/components/ComicPager.dart index 82dd56c..ca22013 100644 --- a/lib/screens/components/ComicPager.dart +++ b/lib/screens/components/ComicPager.dart @@ -253,6 +253,8 @@ class StreamComicPager extends StatefulWidget { } class _StreamComicPagerState extends State { + final TextEditingController _textEditController = + TextEditingController(text: ''); final _scrollController = ScrollController(); late String _currentSort = SORT_DEFAULT; late int _currentPage = 1; @@ -264,6 +266,12 @@ class _StreamComicPagerState extends State { // late Future _pageFuture; + _onSetOffset(int i) { + _list.clear(); + _currentPage = i; + _load(); + } + void _onScroll() { if (_over || _error || _loading) { return; @@ -317,6 +325,7 @@ class _StreamComicPagerState extends State { void dispose() { _scrollController.removeListener(_onScroll); _scrollController.dispose(); + _textEditController.dispose(); super.dispose(); } @@ -367,7 +376,60 @@ class _StreamComicPagerState extends State { ), Row( children: [ - Text("已经加载 ${_currentPage - 1} / $_maxPage 页"), + InkWell( + onTap: () { + _textEditController.clear(); + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Card( + child: TextField( + controller: _textEditController, + decoration: const InputDecoration( + labelText: "请输入页数:", + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'\d+')), + ], + ), + ), + actions: [ + MaterialButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('取消'), + ), + MaterialButton( + onPressed: () { + Navigator.pop(context); + var text = _textEditController.text; + if (text.isEmpty || text.length > 5) { + return; + } + var num = int.parse(text); + if (num == 0 || num > _maxPage) { + return; + } + _currentPage = num; + _onSetOffset(num); + }, + child: const Text('确定'), + ), + ], + ); + }, + ); + }, + child: Row( + children: [ + Text("已经加载 ${_currentPage - 1} / $_maxPage 页"), + ], + ), + ), + ], ), ], diff --git a/pubspec.lock b/pubspec.lock index 48deebf..98df542 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -178,7 +178,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -244,7 +244,7 @@ packages: name: image_picker_ios url: "https://pub.dartlang.org" source: hosted - version: "0.8.5+5" + version: "0.8.5+6" image_picker_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dc536fe..a36bbf8 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.0.0+1 +version: 1.0.1+1 environment: sdk: ">=2.12.0 <3.0.0"