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:
env:
go_version: '1.17'
go_version: '1.18'
flutter_channel: 'stable'
GH_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@ -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'

View File

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

View File

@ -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

View File

@ -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;

View File

@ -2,15 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>pika</string>
</array>
</dict>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@ -31,36 +22,6 @@
</array>
</dict>
</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>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -72,11 +33,20 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>pika</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.entertainment</string>
<key>LSRequiresIPhoneOS</key>
@ -104,5 +74,35 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<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>
</plist>

View File

@ -1012,3 +1012,13 @@ class PkzComicViewLog {
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
Future<dynamic> exportComicDownloadToPki(String comicId, String dir, String name) {
Future<dynamic> exportComicDownloadToPki(
String comicId, String dir, String name) {
return _flatInvoke("exportComicDownloadToPki", {
"comicId": comicId,
"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 {
return int.parse(await _flatInvoke("exportComicUsingSocket", comicId));
@ -833,4 +863,16 @@ class Method {
.map((e) => Knight.fromJson(e))
.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 '../Common.dart';
import 'IsPro.dart';
const _propertyName = "androidDisplayMode";
List<String> _modes = [];
@ -27,7 +28,11 @@ Future<void> _chooseAndroidDisplayMode(BuildContext context) async {
if (Platform.isAndroid) {
List<String> list = [""];
list.addAll(_modes);
String? result = await chooseListDialog<String>(context, "安卓屏幕刷新率", list);
String? result = await chooseListDialog<String>(
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(() {});
},

View File

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

View File

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

View File

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

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? _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<EventArgs>();
@ -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 "-";
}
}

View File

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

View File

@ -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<AccountScreen> {
});
try {
await method.login();
await reloadIsPro();
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AppScreen()),

View File

@ -87,20 +87,24 @@ class _DownloadConfirmScreenState extends State<DownloadConfirmScreen> {
});
}
}
// 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,

View File

@ -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(

View File

@ -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<DownloadImportScreen> {
actions.add(_fileImportButton());
actions.add(_networkImportButton());
actions.add(_importDirFilesZipButton());
return Scaffold(
appBar: AppBar(
@ -103,7 +106,7 @@ class _DownloadImportScreenState extends State<DownloadImportScreen> {
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<DownloadImportScreen> {
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<DownloadImportScreen> {
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: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<InitScreen> {
await initVersion();
await initUsingRightClickPop();
await initAuthentication();
await reloadIsPro();
autoCheckNewVersion();
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/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) {

View File

@ -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<SpaceScreen> {
@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<SpaceScreen> {
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(

View File

@ -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<ViewLogsScreen> {
static const _scrollPhysics = AlwaysScrollableScrollPhysics(); // 使
final _scrollController = ScrollController();
final _comicList = <ViewLogWrapEntity>[];
final _comicList = <ViewLog>[];
var _isLoading = false; //
var _scrollOvered = false; //
@ -70,8 +74,7 @@ class _ViewLogsScreenState extends State<ViewLogsScreen> {
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<ViewLogsScreen> {
@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<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 {
final Function(String) onTapComic;
final List<ViewLogWrapEntity> comics;
final Function(String id) onDelete;
class ViewInfoCard extends StatelessWidget {
final String fileServer;
final String path;
final String title;
final String author;
final List<String> 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<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> {
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<StreamComicPager> {
// late Future<dynamic> _pageFuture;
_onSetOffset(int i) {
_list.clear();
_currentPage = i;
_load();
}
void _onScroll() {
if (_over || _error || _loading) {
return;
@ -317,6 +325,7 @@ class _StreamComicPagerState extends State<StreamComicPager> {
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
_textEditController.dispose();
super.dispose();
}
@ -367,7 +376,60 @@ class _StreamComicPagerState extends State<StreamComicPager> {
),
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: <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
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:

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.
# 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"