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"