This commit is contained in:
niuhuan 2022-07-09 15:11:26 +08:00
parent ef4ce20587
commit d02510c3cf
27 changed files with 632 additions and 265 deletions

View File

@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
go_version: '1.17' go_version: '1.18'
flutter_channel: 'stable' flutter_channel: 'stable'
GH_TOKEN: ${{ secrets.GH_TOKEN }} GH_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@ -13,12 +13,12 @@ if (flutterRoot == null) {
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '2'
} }
def flutterVersionName = localProperties.getProperty('flutter.versionName') def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) { if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0.1'
} }
apply plugin: 'com.android.application' apply plugin: 'com.android.application'

View File

@ -1 +1 @@
v1.5.4 v1.5.5

View File

@ -1,11 +1,13 @@
更新 更新
- [x] 苹果手机支持导出导出了 - [x] 对历史记录页面进行优化
- [x] 修复了安卓导入时不能选择文件 (AOSP个别机型的问题) - [x] 对导入进行优化
- [x] 增加了导出 PKI/PKZ 等格式 - [x] 增加了批量导出ZIP/PKI到文件夹
- [x] 苹果/安卓 在文件管理器中可以直接打开 PKI/PKZ 导入或观看 - [x] 增加了从一个文件夹中导入所有ZIP/PKI的功能
- [x] 增加了骑士榜 - [x] 增加了发电页面, 对作者发过电的用户会展示发电特权图标
计划 计划
- [ ] 本地骑士书签 (漫画书签?/快捷搜索书签?) - [ ] 本地骑士书签 (漫画书签?/快捷搜索书签?)
- [ ] 将Jasmine导出的PKI/JMI/导入, 阅读Jasmine导出的PKI

View File

@ -359,7 +359,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -368,10 +368,12 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika; PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Profile; name = Profile;
@ -491,7 +493,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -500,11 +502,13 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika; PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Debug; name = Debug;
@ -515,7 +519,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -524,10 +528,12 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika; PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapika;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Release; name = Release;

View File

@ -2,15 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>pika</string>
</array>
</dict>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
@ -31,36 +22,6 @@
</array> </array>
</dict> </dict>
</array> </array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>niuhuan.pkz</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
<string>com.apple.package</string>
</array>
<key>UTTypeDescription</key>
<string>PKZ Archive</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pkz</string>
<string>pki</string>
<string>zip</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/vnd.niuhuan.pkz</string>
<string>text/vnd.niuhuan.pki</string>
<string>text/vnd.niuhuan.zip</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -72,11 +33,20 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>pika</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.entertainment</string> <string>public.app-category.entertainment</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
@ -104,5 +74,35 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
<string>com.apple.package</string>
</array>
<key>UTTypeDescription</key>
<string>PKZ Archive</string>
<key>UTTypeIdentifier</key>
<string>niuhuan.pkz</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pkz</string>
<string>pki</string>
<string>zip</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/vnd.niuhuan.pkz</string>
<string>text/vnd.niuhuan.pki</string>
<string>text/vnd.niuhuan.zip</string>
</array>
</dict>
</dict>
</array>
</dict> </dict>
</plist> </plist>

View File

@ -1012,3 +1012,13 @@ class PkzComicViewLog {
return _data; return _data;
} }
} }
class IsPro {
late bool isPro;
late int expire;
IsPro.fromJson(Map<String, dynamic> json) {
this.isPro = json["isPro"];
this.expire = json["expire"];
}
}

View File

@ -548,7 +548,8 @@ class Method {
} }
/// pki /// pki
Future<dynamic> exportComicDownloadToPki(String comicId, String dir, String name) { Future<dynamic> exportComicDownloadToPki(
String comicId, String dir, String name) {
return _flatInvoke("exportComicDownloadToPki", { return _flatInvoke("exportComicDownloadToPki", {
"comicId": comicId, "comicId": comicId,
"dir": dir, "dir": dir,
@ -582,6 +583,35 @@ class Method {
}); });
} }
/// zip
Future<dynamic> exportAnyComicDownloadsToZip(
List<String> comicIds,
String dir,
) {
return _flatInvoke("exportAnyComicDownloadsToZip", {
"comicIds": comicIds,
"dir": dir,
});
}
/// pki
Future<dynamic> exportAnyComicDownloadsToPki(
List<String> comicIds,
String dir,
) {
return _flatInvoke("exportAnyComicDownloadsToPki", {
"comicIds": comicIds,
"dir": dir,
});
}
///
Future<dynamic> importComicDownloadDir(
String dir,
) {
return _flatInvoke("importComicDownloadDir", dir);
}
/// 使 /// 使
Future<int> exportComicUsingSocket(String comicId) async { Future<int> exportComicUsingSocket(String comicId) async {
return int.parse(await _flatInvoke("exportComicUsingSocket", comicId)); return int.parse(await _flatInvoke("exportComicUsingSocket", comicId));
@ -833,4 +863,16 @@ class Method {
.map((e) => Knight.fromJson(e)) .map((e) => Knight.fromJson(e))
.toList(); .toList();
} }
Future<IsPro> isPro() async {
return IsPro.fromJson(jsonDecode(await _flatInvoke("isPro", "")));
}
Future reloadPro() {
return _flatInvoke("reloadPro", "");
}
Future inputCdKey(String cdKey) {
return _flatInvoke("inputCdKey", cdKey);
}
} }

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import '../Common.dart'; import '../Common.dart';
import 'IsPro.dart';
const _propertyName = "androidDisplayMode"; const _propertyName = "androidDisplayMode";
List<String> _modes = []; List<String> _modes = [];
@ -27,7 +28,11 @@ Future<void> _chooseAndroidDisplayMode(BuildContext context) async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
List<String> list = [""]; List<String> list = [""];
list.addAll(_modes); list.addAll(_modes);
String? result = await chooseListDialog<String>(context, "安卓屏幕刷新率", list); String? result = await chooseListDialog<String>(
context,
"安卓屏幕刷新率 \n(省电模式下不会高刷)",
list,
);
if (result != null) { if (result != null) {
await method.saveProperty(_propertyName, result); await method.saveProperty(_propertyName, result);
_androidDisplayMode = result; _androidDisplayMode = result;
@ -41,9 +46,18 @@ Widget androidDisplayModeSetting() {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) { builder: (BuildContext context, void Function(void Function()) setState) {
return ListTile( return ListTile(
title: const Text("屏幕刷新率(安卓)"), title: Text(
"屏幕刷新率(安卓)" + (!isPro ? "(发电)" : ""),
style: TextStyle(
color: !isPro ? Colors.grey : null,
),
),
subtitle: Text(_androidDisplayMode), subtitle: Text(_androidDisplayMode),
onTap: () async { onTap: () async {
if (!isPro) {
defaultToast(context, "请先发电再使用");
return;
}
await _chooseAndroidDisplayMode(context); await _chooseAndroidDisplayMode(context);
setState(() {}); setState(() {});
}, },

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import '../Common.dart'; import '../Common.dart';
import '../Method.dart'; import '../Method.dart';
import 'IsPro.dart';
const _propertyName = "androidSecureFlag"; const _propertyName = "androidSecureFlag";
@ -37,9 +38,20 @@ Widget androidSecureFlagSetting() {
return StatefulBuilder(builder: return StatefulBuilder(builder:
(BuildContext context, void Function(void Function()) setState) { (BuildContext context, void Function(void Function()) setState) {
return ListTile( return ListTile(
title: const Text("禁止截图/禁止显示在任务视图"), title: Text(
subtitle: Text(_androidSecureFlag ? "" : ""), "禁止截图/禁止显示在任务视图" + (!isPro ? "(发电)" : ""),
style: TextStyle(
color: !isPro ? Colors.grey : null,
),
),
subtitle: Text(
_androidSecureFlag ? "" : "",
),
onTap: () async { onTap: () async {
if (!isPro) {
defaultToast(context, "请先发电再使用");
return;
}
await _chooseAndroidSecureFlag(context); await _chooseAndroidSecureFlag(context);
setState(() {}); setState(() {});
}); });

View File

@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import 'IsPro.dart';
late int _downloadThreadCount; late int _downloadThreadCount;
const _values = [1, 2, 3, 4, 5]; const _values = [1, 2, 3, 4, 5];
@ -15,9 +17,18 @@ Widget downloadThreadCountSetting() {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) { builder: (BuildContext context, void Function(void Function()) setState) {
return ListTile( return ListTile(
title: const Text("下载线程数"), title: Text(
"下载线程数" + (!isPro ? "(发电)" : ""),
style: TextStyle(
color: !isPro ? Colors.grey : null,
),
),
subtitle: Text("$_downloadThreadCount"), subtitle: Text("$_downloadThreadCount"),
onTap: () async { onTap: () async {
if (!isPro) {
defaultToast(context, "请先发电再使用");
return;
}
int? value = await chooseListDialog(context, "选择下载线程数", _values); int? value = await chooseListDialog(context, "选择下载线程数", _values);
if (value != null) { if (value != null) {
await method.saveDownloadThreadCount(value); await method.saveDownloadThreadCount(value);

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import '../Common.dart'; import '../Common.dart';
import '../Method.dart'; import '../Method.dart';
import 'IsPro.dart';
const _propertyName = "exportRename"; const _propertyName = "exportRename";
late bool _exportRename; late bool _exportRename;
@ -29,9 +30,18 @@ Widget exportRenameSetting() {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) { builder: (BuildContext context, void Function(void Function()) setState) {
return ListTile( return ListTile(
title: const Text("导出时进行重命名"), title: Text(
"导出时进行重命名" + (!isPro ? "(发电)" : ""),
style: TextStyle(
color: !isPro ? Colors.grey : null,
),
),
subtitle: Text(_exportRename ? "" : ""), subtitle: Text(_exportRename ? "" : ""),
onTap: () async { onTap: () async {
if (!isPro) {
defaultToast(context, "请先发电再使用");
return;
}
await _chooseExportRename(context); await _chooseExportRename(context);
setState(() {}); setState(() {});
}, },

View File

@ -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();
}

View File

@ -15,9 +15,6 @@ late String _version;
String? _latestVersion; String? _latestVersion;
String? _latestVersionInfo; String? _latestVersionInfo;
const _propertyName = "checkVersionPeriod";
late int _period = -1;
Future initVersion() async { Future initVersion() async {
// //
try { try {
@ -25,14 +22,6 @@ Future initVersion() async {
} catch (e) { } catch (e) {
_version = "dirty"; _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<EventArgs>(); var versionEvent = Event<EventArgs>();
@ -50,10 +39,6 @@ String? latestVersionInfo() {
} }
Future autoCheckNewVersion() { Future autoCheckNewVersion() {
if (_period != 0) {
// -1 , >0
return Future.value();
}
return _versionCheck(); return _versionCheck();
} }
@ -86,75 +71,3 @@ Future _versionCheck() async {
} // else dirtyVersion } // else dirtyVersion
versionEvent.broadcast(); 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 "-";
}
}

View File

@ -98,8 +98,6 @@ class _AboutScreenState extends State<AboutScreen> {
), ),
), ),
const Divider(), const Divider(),
autoUpdateCheckSetting(),
const Divider(),
Container( Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: const SelectableText( child: const SelectableText(

View File

@ -5,18 +5,15 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/Method.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/config/Themes.dart';
import 'package:pikapika/basic/enum/ErrorTypes.dart'; import 'package:pikapika/basic/enum/ErrorTypes.dart';
import 'package:pikapika/screens/RegisterScreen.dart'; import 'package:pikapika/screens/RegisterScreen.dart';
import 'package:pikapika/screens/SettingsScreen.dart'; import 'package:pikapika/screens/SettingsScreen.dart';
import 'package:pikapika/screens/components/NetworkSetting.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 'AppScreen.dart';
import 'DownloadListScreen.dart'; import 'DownloadListScreen.dart';
import 'PkzArchiveScreen.dart';
import 'ThemeScreen.dart'; import 'ThemeScreen.dart';
import 'components/ContentLoading.dart'; import 'components/ContentLoading.dart';
@ -186,6 +183,7 @@ class _AccountScreenState extends State<AccountScreen> {
}); });
try { try {
await method.login(); await method.login();
await reloadIsPro();
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const AppScreen()), MaterialPageRoute(builder: (context) => const AppScreen()),

View File

@ -87,20 +87,24 @@ class _DownloadConfirmScreenState extends State<DownloadConfirmScreen> {
}); });
} }
} }
// EP加入下载 try {
// // EP加入下载
if (_task != null) { //
await method.addDownload(create, list); if (_task != null) {
} else { await method.addDownload(create, list);
await method.createDownload(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 @override
Widget build(BuildContext context){ Widget build(BuildContext context) {
return rightClickPop( return rightClickPop(
child: buildScreen(context), child: buildScreen(context),
context: context, context: context,

View File

@ -5,6 +5,7 @@ import '../basic/Channels.dart';
import '../basic/Cross.dart'; import '../basic/Cross.dart';
import '../basic/Method.dart'; import '../basic/Method.dart';
import '../basic/config/ExportRename.dart'; import '../basic/config/ExportRename.dart';
import '../basic/config/IsPro.dart';
import 'components/ContentLoading.dart'; import 'components/ContentLoading.dart';
class DownloadExportingGroupScreen extends StatefulWidget { class DownloadExportingGroupScreen extends StatefulWidget {
@ -51,17 +52,31 @@ class _DownloadExportingGroupScreenState
return Center(child: Text("导出失败\n$e")); return Center(child: Text("导出失败\n$e"));
} }
if (exported) { if (exported) {
return Center(child: Text("导出成功")); return const Center(child: Text("导出成功"));
} }
return Center( return ListView(
child: MaterialButton( children: [
onPressed: _export, Container(height: 20),
child: const Text("选择导出位置"), 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; late String? path;
try { try {
path = await chooseFolder(context); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return WillPopScope(

View File

@ -8,6 +8,8 @@ import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import 'package:pikapika/basic/config/ChooserRoot.dart'; import 'package:pikapika/basic/config/ChooserRoot.dart';
import '../basic/Cross.dart';
import '../basic/config/IsPro.dart';
import 'PkzArchiveScreen.dart'; import 'PkzArchiveScreen.dart';
import 'components/ContentLoading.dart'; import 'components/ContentLoading.dart';
import 'components/RightClickPop.dart'; import 'components/RightClickPop.dart';
@ -64,6 +66,7 @@ class _DownloadImportScreenState extends State<DownloadImportScreen> {
actions.add(_fileImportButton()); actions.add(_fileImportButton());
actions.add(_networkImportButton()); actions.add(_networkImportButton());
actions.add(_importDirFilesZipButton());
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -103,7 +106,7 @@ class _DownloadImportScreenState extends State<DownloadImportScreen> {
allowedExtensions: ['.pkz', '.zip', '.pki'], allowedExtensions: ['.pkz', '.zip', '.pki'],
fileTileSelectMode: FileTileSelectMode.wholeTile, fileTileSelectMode: FileTileSelectMode.wholeTile,
); );
}else{ } else {
var ls = await FilePicker.platform.pickFiles( var ls = await FilePicker.platform.pickFiles(
dialogTitle: '选择要导入的文件', dialogTitle: '选择要导入的文件',
allowMultiple: false, allowMultiple: false,
@ -127,9 +130,9 @@ class _DownloadImportScreenState extends State<DownloadImportScreen> {
setState(() { setState(() {
_importing = true; _importing = true;
}); });
if(path.endsWith(".zip")){ if (path.endsWith(".zip")) {
await method.importComicDownload(path); await method.importComicDownload(path);
} else if(path.endsWith(".pki")){ } else if (path.endsWith(".pki")) {
await method.importComicDownloadPki(path); await method.importComicDownloadPki(path);
} }
setState(() { setState(() {
@ -184,4 +187,43 @@ class _DownloadImportScreenState extends State<DownloadImportScreen> {
child: const Text('从其他设备导入'), 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,
),
);
}
} }

View File

@ -38,6 +38,7 @@ import 'package:pikapika/screens/PkzArchiveScreen.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import 'package:uri_to_file/uri_to_file.dart'; import 'package:uri_to_file/uri_to_file.dart';
import '../basic/config/ExportRename.dart'; import '../basic/config/ExportRename.dart';
import '../basic/config/IsPro.dart';
import 'AccountScreen.dart'; import 'AccountScreen.dart';
import 'AppScreen.dart'; import 'AppScreen.dart';
import 'DownloadOnlyImportScreen.dart'; import 'DownloadOnlyImportScreen.dart';
@ -94,6 +95,7 @@ class _InitScreenState extends State<InitScreen> {
await initVersion(); await initVersion();
await initUsingRightClickPop(); await initUsingRightClickPop();
await initAuthentication(); await initAuthentication();
await reloadIsPro();
autoCheckNewVersion(); autoCheckNewVersion();
String? initUrl; String? initUrl;

104
lib/screens/ProScreen.dart Normal file
View File

@ -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<StatefulWidget> createState() => _ProScreenState();
}
class _ProScreenState extends State<ProScreen> {
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(),
],
),
);
}
}

View File

@ -13,6 +13,7 @@ import 'package:pikapika/basic/config/DownloadThreadCount.dart';
import 'package:pikapika/basic/config/ExportRename.dart'; import 'package:pikapika/basic/config/ExportRename.dart';
import 'package:pikapika/basic/config/FullScreenAction.dart'; import 'package:pikapika/basic/config/FullScreenAction.dart';
import 'package:pikapika/basic/config/FullScreenUI.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/KeyboardController.dart';
import 'package:pikapika/basic/config/NoAnimation.dart'; import 'package:pikapika/basic/config/NoAnimation.dart';
import 'package:pikapika/basic/config/PagerAction.dart'; import 'package:pikapika/basic/config/PagerAction.dart';
@ -113,8 +114,6 @@ class SettingsScreen extends StatelessWidget {
const Divider(), const Divider(),
migrate(context), migrate(context),
const Divider(), const Divider(),
autoUpdateCheckSetting(),
const Divider(),
], ],
), ),
); );
@ -122,9 +121,18 @@ class SettingsScreen extends StatelessWidget {
Widget migrate(BuildContext context) { Widget migrate(BuildContext context) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
return ListTile( return ListTile(
title: const Text("文件迁移"), title: Text(
subtitle: const Text("更换您的数据文件夹"), "文件迁移" + (!isPro ? "(发电)" : ""),
style: TextStyle(
color: !isPro ? Colors.grey : null,
),
),
subtitle: const Text("更换您的数据文件夹到内存卡"),
onTap: () async { onTap: () async {
if (!isPro) {
defaultToast(context, "请先发电再使用");
return;
}
var f = var f =
await confirmDialog(context, "文件迁移", "此功能菜单保存后, 需要重启程序, 您确认吗"); await confirmDialog(context, "文件迁移", "此功能菜单保存后, 需要重启程序, 您确认吗");
if (f) { if (f) {

View File

@ -5,10 +5,12 @@ import 'package:pikapika/screens/AboutScreen.dart';
import 'package:pikapika/screens/AccountScreen.dart'; import 'package:pikapika/screens/AccountScreen.dart';
import 'package:pikapika/screens/DownloadListScreen.dart'; import 'package:pikapika/screens/DownloadListScreen.dart';
import 'package:pikapika/screens/FavouritePaperScreen.dart'; import 'package:pikapika/screens/FavouritePaperScreen.dart';
import 'package:pikapika/screens/ProScreen.dart';
import 'package:pikapika/screens/ThemeScreen.dart'; import 'package:pikapika/screens/ThemeScreen.dart';
import 'package:pikapika/screens/ViewLogsScreen.dart'; import 'package:pikapika/screens/ViewLogsScreen.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import '../basic/config/IsPro.dart';
import '../basic/config/Themes.dart'; import '../basic/config/Themes.dart';
import 'SettingsScreen.dart'; import 'SettingsScreen.dart';
import 'components/Badge.dart'; import 'components/Badge.dart';
@ -25,17 +27,19 @@ class SpaceScreen extends StatefulWidget {
class _SpaceScreenState extends State<SpaceScreen> { class _SpaceScreenState extends State<SpaceScreen> {
@override @override
void initState() { void initState() {
versionEvent.subscribe(_onVersion); versionEvent.subscribe(_onEvent);
proEvent.subscribe(_onEvent);
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
versionEvent.unsubscribe(_onVersion); versionEvent.unsubscribe(_onEvent);
proEvent.unsubscribe(_onEvent);
super.dispose(); super.dispose();
} }
void _onVersion(dynamic a) { void _onEvent(dynamic a) {
setState(() {}); setState(() {});
} }
@ -73,6 +77,17 @@ class _SpaceScreenState extends State<SpaceScreen> {
badge: latestVersion() == null ? null : "1", 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( IconButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(

View File

@ -1,8 +1,12 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import 'package:pikapika/screens/components/ComicInfoCard.dart';
import 'package:pikapika/screens/components/RightClickPop.dart'; import 'package:pikapika/screens/components/RightClickPop.dart';
import '../basic/Entities.dart';
import 'ComicInfoScreen.dart'; import 'ComicInfoScreen.dart';
import 'components/Images.dart'; import 'components/Images.dart';
@ -19,7 +23,7 @@ class _ViewLogsScreenState extends State<ViewLogsScreen> {
static const _scrollPhysics = AlwaysScrollableScrollPhysics(); // 使 static const _scrollPhysics = AlwaysScrollableScrollPhysics(); // 使
final _scrollController = ScrollController(); final _scrollController = ScrollController();
final _comicList = <ViewLogWrapEntity>[]; final _comicList = <ViewLog>[];
var _isLoading = false; // var _isLoading = false; //
var _scrollOvered = false; // var _scrollOvered = false; //
@ -70,8 +74,7 @@ class _ViewLogsScreenState extends State<ViewLogsScreen> {
if (page.isEmpty) { if (page.isEmpty) {
_scrollOvered = true; _scrollOvered = true;
} else { } else {
_comicList.addAll(page.map((e) => _comicList.addAll(page);
ViewLogWrapEntity(e.id, e.title, e.thumbFileServer, e.thumbPath)));
} }
_offset += _pageSize; _offset += _pageSize;
} finally { } finally {
@ -106,25 +109,37 @@ class _ViewLogsScreenState extends State<ViewLogsScreen> {
@override @override
Widget build(BuildContext context) { 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( final screen = NotificationListener(
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('浏览记录'), title: const Text('浏览记录'),
actions: [ actions: [
IconButton(onPressed: _clearAll, icon: const Icon(Icons.auto_delete)), IconButton(
onPressed: _clearAll, icon: const Icon(Icons.auto_delete)),
], ],
), ),
body: ListView( body: ListView(
physics: _scrollPhysics, physics: _scrollPhysics,
controller: _scrollController, controller: _scrollController,
children: [ children: entries.toList(),
Container(height: 10),
ViewLogWrap(
onTapComic: _chooseComic,
comics: _comicList,
onDelete: _clearOnce,
),
],
), ),
), ),
onNotification: (scrollNotification) { onNotification: (scrollNotification) {
@ -151,88 +166,90 @@ class _ViewLogsScreenState extends State<ViewLogsScreen> {
), ),
); );
} }
List<String> _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 { class ViewInfoCard extends StatelessWidget {
final Function(String) onTapComic; final String fileServer;
final List<ViewLogWrapEntity> comics; final String path;
final Function(String id) onDelete; final String title;
final String author;
final List<String> categories;
const ViewLogWrap({ const ViewInfoCard({
Key? key, Key? key,
required this.onTapComic, required this.fileServer,
required this.comics, required this.path,
required this.onDelete, required this.title,
required this.author,
required this.categories,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var size = MediaQuery.of(context).size; var theme = Theme.of(context);
var min = size.width < size.height ? size.width : size.height; return Container(
var width = (min - 45) / 4; padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
var entries = comics.map((e) { border: Border(
return InkWell( bottom: BorderSide(
key: e.key, color: theme.dividerColor,
onTap: () { ),
onTapComic(e.id); ),
}, ),
onLongPress: () { child: Row(
onDelete(e.id); children: [
}, Container(
child: Card( padding: const EdgeInsets.only(right: 10),
child: SizedBox( child: RemoteImage(
width: width, fileServer: fileServer,
child: Column( path: path,
width: imageWidth,
height: imageHeight,
),
),
Expanded(
child: Row(
children: [ children: [
LayoutBuilder(builder: Expanded(
(BuildContext context, BoxConstraints constraints) { child: Column(
return RemoteImage( crossAxisAlignment: CrossAxisAlignment.start,
width: constraints.maxWidth, children: [
fileServer: e.fileServer, Text(title, style: titleStyle),
path: e.path); Container(height: 5),
}), Text(author, style: authorStyle),
Text( Container(height: 5),
e.title + '\n', Text.rich(
maxLines: 2, TextSpan(text: "分类 : ${categories.join(' ')}"),
overflow: TextOverflow.ellipsis, style: TextStyle(
style: const TextStyle(height: 1.4), fontSize: 13,
strutStyle: const StrutStyle(height: 1.4), color: Theme.of(context)
.textTheme
.bodyText1!
.color!
.withAlpha(0xCC),
),
),
Container(height: 5),
],
),
), ),
], ],
), ),
), ),
), ],
); ),
});
Map<int, List<Widget>> map = {};
for (var i = 0; i < entries.length; i++) {
late List<Widget> 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);
}

View File

@ -253,6 +253,8 @@ class StreamComicPager extends StatefulWidget {
} }
class _StreamComicPagerState extends State<StreamComicPager> { class _StreamComicPagerState extends State<StreamComicPager> {
final TextEditingController _textEditController =
TextEditingController(text: '');
final _scrollController = ScrollController(); final _scrollController = ScrollController();
late String _currentSort = SORT_DEFAULT; late String _currentSort = SORT_DEFAULT;
late int _currentPage = 1; late int _currentPage = 1;
@ -264,6 +266,12 @@ class _StreamComicPagerState extends State<StreamComicPager> {
// late Future<dynamic> _pageFuture; // late Future<dynamic> _pageFuture;
_onSetOffset(int i) {
_list.clear();
_currentPage = i;
_load();
}
void _onScroll() { void _onScroll() {
if (_over || _error || _loading) { if (_over || _error || _loading) {
return; return;
@ -317,6 +325,7 @@ class _StreamComicPagerState extends State<StreamComicPager> {
void dispose() { void dispose() {
_scrollController.removeListener(_onScroll); _scrollController.removeListener(_onScroll);
_scrollController.dispose(); _scrollController.dispose();
_textEditController.dispose();
super.dispose(); super.dispose();
} }
@ -367,7 +376,60 @@ class _StreamComicPagerState extends State<StreamComicPager> {
), ),
Row( Row(
children: [ 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: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'\d+')),
],
),
),
actions: <Widget>[
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"),
],
),
),
], ],
), ),
], ],

View File

@ -178,7 +178,7 @@ packages:
name: flutter_svg name: flutter_svg
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -244,7 +244,7 @@ packages:
name: image_picker_ios name: image_picker_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.5+5" version: "0.8.5+6"
image_picker_platform_interface: image_picker_platform_interface:
dependency: transitive dependency: transitive
description: description:

View File

@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1 version: 1.0.1+1
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"