diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2e0145d..14822ff 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -48,6 +48,19 @@
+
+
+
+
+
+
+
diff --git a/ci/cmd/send_to_community/main.go b/ci/cmd/send_to_community/main.go
index d9578d1..ebe0877 100644
--- a/ci/cmd/send_to_community/main.go
+++ b/ci/cmd/send_to_community/main.go
@@ -14,7 +14,6 @@ import (
)
func main() {
- return
// get version
var version commons.Version
codeFile, err := ioutil.ReadFile("version.code.txt")
diff --git a/lib/basic/Entities.dart b/lib/basic/Entities.dart
index ee66e1a..9045938 100644
--- a/lib/basic/Entities.dart
+++ b/lib/basic/Entities.dart
@@ -687,3 +687,328 @@ class Collection {
.toList();
}
}
+
+class PkzArchive {
+ PkzArchive({
+ required this.coverPath,
+ required this.authorAvatarPath,
+ required this.comics,
+ required this.comicCount,
+ required this.volumesCount,
+ required this.chapterCount,
+ required this.pictureCount,
+ });
+
+ late final String coverPath;
+ late final String authorAvatarPath;
+ late final List comics;
+ late final int comicCount;
+ late final int volumesCount;
+ late final int chapterCount;
+ late final int pictureCount;
+
+ PkzArchive.fromJson(Map json) {
+ coverPath = json['cover_path'];
+ authorAvatarPath = json['author_avatar_path'];
+ comics =
+ List.from(json['comics']).map((e) => PkzComic.fromJson(e)).toList();
+ comicCount = json['comic_count'];
+ volumesCount = json['volumes_count'];
+ chapterCount = json['chapter_count'];
+ pictureCount = json['picture_count'];
+ }
+
+ Map toJson() {
+ final _data = {};
+ _data['cover_path'] = coverPath;
+ _data['author_avatar_path'] = authorAvatarPath;
+ _data['comics'] = comics.map((e) => e.toJson()).toList();
+ _data['comic_count'] = comicCount;
+ _data['volumes_count'] = volumesCount;
+ _data['chapter_count'] = chapterCount;
+ _data['picture_count'] = pictureCount;
+ return _data;
+ }
+}
+
+class PkzComic {
+ PkzComic({
+ required this.id,
+ required this.title,
+ required this.categories,
+ required this.tags,
+ required this.updatedAt,
+ required this.createdAt,
+ required this.description,
+ required this.chineseTeam,
+ required this.finished,
+ required this.coverPath,
+ required this.authorAvatarPath,
+ required this.volumes,
+ required this.volumesCount,
+ required this.chapterCount,
+ required this.pictureCount,
+ required this.idx,
+ });
+
+ late final String id;
+ late final String title;
+ late final List categories;
+ late final List tags;
+ late final int updatedAt;
+ late final int createdAt;
+ late final String description;
+ late final String chineseTeam;
+ late final bool finished;
+ late final String coverPath;
+ late final String authorAvatarPath;
+ late final List volumes;
+ late final int volumesCount;
+ late final int chapterCount;
+ late final int pictureCount;
+ late final int idx;
+ late final String author;
+ late final String authorId;
+
+ PkzComic.fromJson(Map json) {
+ id = json['id'];
+ title = json['title'];
+ categories = List.castFrom(json['categories']);
+ tags = List.castFrom(json['tags']);
+ updatedAt = json['updated_at'];
+ createdAt = json['created_at'];
+ description = json['description'];
+ chineseTeam = json['chinese_team'];
+ finished = json['finished'];
+ coverPath = json['cover_path'];
+ authorAvatarPath = json['author_avatar_path'];
+ volumes =
+ List.from(json['volumes']).map((e) => PkzVolume.fromJson(e)).toList();
+ volumesCount = json['volumes_count'];
+ chapterCount = json['chapter_count'];
+ pictureCount = json['picture_count'];
+ idx = json['idx'];
+ author = json['author'];
+ authorId = json['author_id'];
+ }
+
+ Map toJson() {
+ final _data = {};
+ _data['id'] = id;
+ _data['title'] = title;
+ _data['categories'] = categories;
+ _data['tags'] = tags;
+ _data['updated_at'] = updatedAt;
+ _data['created_at'] = createdAt;
+ _data['description'] = description;
+ _data['chinese_team'] = chineseTeam;
+ _data['finished'] = finished;
+ _data['cover_path'] = coverPath;
+ _data['author_avatar_path'] = authorAvatarPath;
+ _data['volumes'] = volumes.map((e) => e.toJson()).toList();
+ _data['volumes_count'] = volumesCount;
+ _data['chapter_count'] = chapterCount;
+ _data['picture_count'] = pictureCount;
+ _data['idx'] = idx;
+ _data['author'] = author;
+ _data['author_id'] = authorId;
+ return _data;
+ }
+}
+
+class PkzVolume {
+ PkzVolume({
+ required this.id,
+ required this.title,
+ required this.updatedAt,
+ required this.createdAt,
+ required this.coverPath,
+ required this.chapters,
+ required this.chapterCount,
+ required this.pictureCount,
+ required this.idx,
+ });
+
+ late final String id;
+ late final String title;
+ late final int updatedAt;
+ late final int createdAt;
+ late final String coverPath;
+ late final List chapters;
+ late final int chapterCount;
+ late final int pictureCount;
+ late final int idx;
+
+ PkzVolume.fromJson(Map json) {
+ id = json['id'];
+ title = json['title'];
+ updatedAt = json['updated_at'];
+ createdAt = json['created_at'];
+ coverPath = json['cover_path'];
+ chapters =
+ List.from(json['chapters']).map((e) => PkzChapter.fromJson(e)).toList();
+ chapterCount = json['chapter_count'];
+ pictureCount = json['picture_count'];
+ idx = json['idx'];
+ }
+
+ Map toJson() {
+ final _data = {};
+ _data['id'] = id;
+ _data['title'] = title;
+ _data['updated_at'] = updatedAt;
+ _data['created_at'] = createdAt;
+ _data['cover_path'] = coverPath;
+ _data['chapters'] = chapters.map((e) => e.toJson()).toList();
+ _data['chapter_count'] = chapterCount;
+ _data['picture_count'] = pictureCount;
+ _data['idx'] = idx;
+ return _data;
+ }
+}
+
+class PkzChapter {
+ PkzChapter({
+ required this.id,
+ required this.title,
+ required this.updatedAt,
+ required this.createdAt,
+ required this.coverPath,
+ required this.pictures,
+ required this.pictureCount,
+ required this.idx,
+ });
+
+ late final String id;
+ late final String title;
+ late final int updatedAt;
+ late final int createdAt;
+ late final String coverPath;
+ late final List pictures;
+ late final int pictureCount;
+ late final int idx;
+
+ PkzChapter.fromJson(Map json) {
+ id = json['id'];
+ title = json['title'];
+ updatedAt = json['updated_at'];
+ createdAt = json['created_at'];
+ coverPath = json['cover_path'];
+ pictures =
+ List.from(json['pictures']).map((e) => PkzPicture.fromJson(e)).toList();
+ pictureCount = json['picture_count'];
+ idx = json['idx'];
+ }
+
+ Map toJson() {
+ final _data = {};
+ _data['id'] = id;
+ _data['title'] = title;
+ _data['updated_at'] = updatedAt;
+ _data['created_at'] = createdAt;
+ _data['cover_path'] = coverPath;
+ _data['pictures'] = pictures.map((e) => e.toJson()).toList();
+ _data['picture_count'] = pictureCount;
+ _data['idx'] = idx;
+ return _data;
+ }
+}
+
+class PkzPicture {
+ PkzPicture({
+ required this.id,
+ required this.title,
+ required this.width,
+ required this.height,
+ required this.format,
+ required this.picturePath,
+ required this.idx,
+ });
+
+ late final String id;
+ late final String title;
+ late final int width;
+ late final int height;
+ late final String format;
+ late final String picturePath;
+ late final int idx;
+
+ PkzPicture.fromJson(Map json) {
+ id = json['id'];
+ title = json['title'];
+ width = json['width'];
+ height = json['height'];
+ format = json['format'];
+ picturePath = json['picture_path'];
+ idx = json['idx'];
+ }
+
+ Map toJson() {
+ final _data = {};
+ _data['id'] = id;
+ _data['title'] = title;
+ _data['width'] = width;
+ _data['height'] = height;
+ _data['format'] = format;
+ _data['picture_path'] = picturePath;
+ _data['idx'] = idx;
+ return _data;
+ }
+}
+
+class Knight extends BasicUser {
+ late final String role;
+ late final String character;
+ late final int comicsUploaded;
+
+ Knight.fromJson(Map json) : super.fromJson(json) {
+ role = json['role'];
+ character = json['character'];
+ comicsUploaded = json['comicsUploaded'];
+ }
+}
+
+class PkzComicViewLog {
+ PkzComicViewLog({
+ required this.fileName,
+ required this.lastViewComicId,
+ required this.filePath,
+ required this.lastViewComicTitle,
+ required this.lastViewEpId,
+ required this.lastViewEpName,
+ required this.lastViewPictureRank,
+ required this.lastViewTime,
+ });
+ late final String fileName;
+ late final String lastViewComicId;
+ late final String filePath;
+ late final String lastViewComicTitle;
+ late final String lastViewEpId;
+ late final String lastViewEpName;
+ late final int lastViewPictureRank;
+ late final String lastViewTime;
+
+ PkzComicViewLog.fromJson(Map json){
+ fileName = json['fileName'];
+ lastViewComicId = json['lastViewComicId'];
+ filePath = json['filePath'];
+ lastViewComicTitle = json['lastViewComicTitle'];
+ lastViewEpId = json['lastViewEpId'];
+ lastViewEpName = json['lastViewEpName'];
+ lastViewPictureRank = json['lastViewPictureRank'];
+ lastViewTime = json['lastViewTime'];
+ }
+
+ Map toJson() {
+ final _data = {};
+ _data['fileName'] = fileName;
+ _data['lastViewComicId'] = lastViewComicId;
+ _data['filePath'] = filePath;
+ _data['lastViewComicTitle'] = lastViewComicTitle;
+ _data['lastViewEpId'] = lastViewEpId;
+ _data['lastViewEpName'] = lastViewEpName;
+ _data['lastViewPictureRank'] = lastViewPictureRank;
+ _data['lastViewTime'] = lastViewTime;
+ return _data;
+ }
+}
diff --git a/lib/basic/Method.dart b/lib/basic/Method.dart
index 0ccf184..bd72427 100644
--- a/lib/basic/Method.dart
+++ b/lib/basic/Method.dart
@@ -560,6 +560,19 @@ class Method {
});
}
+ /// 导出下载的图片到PKZ
+ Future exportComicDownloadToPkz(
+ List comicIds,
+ String dir,
+ String name,
+ ) {
+ return _flatInvoke("exportComicDownloadToPkz", {
+ "comicIds": comicIds,
+ "dir": dir,
+ "name": name,
+ });
+ }
+
/// 使用网络将下载传输到其他设备
Future exportComicUsingSocket(String comicId) async {
return int.parse(await _flatInvoke("exportComicUsingSocket", comicId));
@@ -721,4 +734,89 @@ class Method {
Future verifyAuthentication() async {
return await _channel.invokeMethod("verifyAuthentication");
}
+
+ Future pkzInfo(String pkzPath) async {
+ return PkzArchive.fromJson(
+ jsonDecode(await _flatInvoke("pkzInfo", pkzPath)));
+ }
+
+ Future loadPkzFile(String pkzPath, String path) async {
+ return base64Decode(await _flatInvoke("loadPkzFile", {
+ "pkzPath": pkzPath,
+ "path": path,
+ }));
+ }
+
+ Future> pkzComicViewLogs(
+ String fileName,
+ String comicId,
+ ) async {
+ return List.of(jsonDecode(await _flatInvoke("pkzComicViewLogs", fileName)))
+ .map((e) => PkzComicViewLog.fromJson(e))
+ .toList();
+ }
+
+ Future pkzComicViewLogByPkzNameAndId(
+ String fileName,
+ String comicId,
+ ) async {
+ String data = await _flatInvoke("pkzComicViewLogByPkzNameAndId", {
+ "fileName": fileName,
+ "comicId": comicId,
+ });
+ if (data == "" || data == "nil" || data == "null") {
+ return null;
+ }
+ return PkzComicViewLog.fromJson(jsonDecode(data));
+ }
+
+ Future viewPkz(
+ String fileName,
+ String filePath,
+ ) async {
+ return _flatInvoke("viewPkz", {
+ "fileName": fileName,
+ "filePath": filePath,
+ });
+ }
+
+ Future viewPkzComic(
+ String fileName,
+ String filePath,
+ String comicId,
+ String comicTitle,
+ ) async {
+ return _flatInvoke("viewPkzComic", {
+ "fileName": fileName,
+ "filePath": filePath,
+ "comicId": comicId,
+ "comicTitle": comicTitle,
+ });
+ }
+
+ Future viewPkzEpAndPicture(
+ String fileName,
+ String filePath,
+ String comicId,
+ String comicTitle,
+ String epId,
+ String epTitle,
+ int pictureRank,
+ ) async {
+ return _flatInvoke("viewPkzEpAndPicture", {
+ "fileName": fileName,
+ "filePath": filePath,
+ "comicId": comicId,
+ "comicTitle": comicTitle,
+ "epId": epId,
+ "epTitle": epTitle,
+ "pictureRank": pictureRank,
+ });
+ }
+
+ Future> leaderboardOfKnight() async {
+ return List.of(jsonDecode(await _flatInvoke("leaderboardOfKnight", "")))
+ .map((e) => Knight.fromJson(e))
+ .toList();
+ }
}
diff --git a/lib/screens/AccountScreen.dart b/lib/screens/AccountScreen.dart
index 96e2233..f38380c 100644
--- a/lib/screens/AccountScreen.dart
+++ b/lib/screens/AccountScreen.dart
@@ -1,3 +1,7 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:app_links/app_links.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pikapika/basic/Common.dart';
@@ -7,9 +11,12 @@ 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: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';
@@ -25,13 +32,34 @@ class _AccountScreenState extends State {
late bool _logging = false;
late String _username = "";
late String _password = "";
+ late StreamSubscription _linkSubscription;
@override
void initState() {
+ final appLinks = AppLinks();
+ // todo 不必要cancel 随机监听就好了, APP关闭时销毁, 考虑移动到APP里
+ _linkSubscription = appLinks.uriLinkStream.listen((uri) async {
+ RegExp regExp = RegExp(r"^.*\.pkz$");
+ final matches = regExp.allMatches(uri.toString());
+ if (matches.isNotEmpty) {
+ File file = await toFile(uri.toString());
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (BuildContext context) =>
+ PkzArchiveScreen(pkzPath: file.path),
+ ));
+ }
+ });
+
_loadProperties();
super.initState();
}
+ @override
+ void dispose() {
+ _linkSubscription.cancel();
+ super.dispose();
+ }
+
Future _loadProperties() async {
var username = await method.getUsername();
var password = await method.getPassword();
diff --git a/lib/screens/AppScreen.dart b/lib/screens/AppScreen.dart
index 34dbe71..3332b5a 100644
--- a/lib/screens/AppScreen.dart
+++ b/lib/screens/AppScreen.dart
@@ -1,8 +1,14 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:pikapika/basic/config/Version.dart';
import 'package:pikapika/screens/components/Badge.dart';
+import 'package:uri_to_file/uri_to_file.dart';
import 'CategoriesScreen.dart';
+import 'PkzArchiveScreen.dart';
import 'SpaceScreen.dart';
// MAIN UI 底部导航栏
@@ -14,15 +20,32 @@ class AppScreen extends StatefulWidget {
}
class _AppScreenState extends State {
+ late StreamSubscription _linkSubscription;
+
@override
void initState() {
versionEvent.subscribe(_onVersion);
+ final appLinks = AppLinks();
+ // todo 不必要cancel 随机监听就好了, APP关闭时销毁, 考虑移动到APP里
+ _linkSubscription = appLinks.uriLinkStream.listen((uri) async {
+ RegExp regExp = RegExp(r"^.*\.pkz$");
+ final matches = regExp.allMatches(uri.toString());
+ if (matches.isNotEmpty) {
+ File file = await toFile(uri.toString());
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (BuildContext context) =>
+ PkzArchiveScreen(pkzPath: file.path),
+ ));
+ }
+ });
+
super.initState();
}
@override
void dispose() {
versionEvent.unsubscribe(_onVersion);
+ _linkSubscription.cancel();
super.dispose();
}
diff --git a/lib/screens/CategoriesScreen.dart b/lib/screens/CategoriesScreen.dart
index 8038da1..0e7f64c 100644
--- a/lib/screens/CategoriesScreen.dart
+++ b/lib/screens/CategoriesScreen.dart
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_search_bar/flutter_search_bar.dart';
import 'package:pikapika/basic/Entities.dart';
import 'package:pikapika/basic/config/ShadowCategoriesEvent.dart';
-import 'package:pikapika/basic/config/shadowCategoriesMode.dart';
+import 'package:pikapika/basic/config/ShadowCategoriesMode.dart';
import 'package:pikapika/basic/store/Categories.dart';
import 'package:pikapika/basic/config/ShadowCategories.dart';
import 'package:pikapika/screens/ComicCollectionsScreen.dart';
diff --git a/lib/screens/ComicsScreen.dart b/lib/screens/ComicsScreen.dart
index 123b211..a4f77fc 100644
--- a/lib/screens/ComicsScreen.dart
+++ b/lib/screens/ComicsScreen.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_search_bar/flutter_search_bar.dart';
import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/config/ShadowCategories.dart';
-import 'package:pikapika/basic/config/shadowCategoriesMode.dart';
+import 'package:pikapika/basic/config/ShadowCategoriesMode.dart';
import 'package:pikapika/basic/store/Categories.dart';
import 'package:pikapika/basic/config/ListLayout.dart';
import 'package:pikapika/basic/Method.dart';
diff --git a/lib/screens/DownloadExportToFileScreen.dart b/lib/screens/DownloadExportToFileScreen.dart
index 9341bd2..05f7dd4 100644
--- a/lib/screens/DownloadExportToFileScreen.dart
+++ b/lib/screens/DownloadExportToFileScreen.dart
@@ -62,7 +62,7 @@ class _DownloadExportToFileScreenState
}
@override
- Widget build(BuildContext context){
+ Widget build(BuildContext context) {
return rightClickPop(
child: buildScreen(context),
context: context,
@@ -185,6 +185,59 @@ class _DownloadExportToFileScreenState
child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'),
));
widgets.add(Container(height: 10));
+ /////////////////////
+ widgets.add(MaterialButton(
+ onPressed: () async {
+ late String? path;
+ try {
+ path = await chooseFolder(context);
+ } catch (e) {
+ defaultToast(context, "$e");
+ return;
+ }
+ var name = "";
+ if (currentExportRename()) {
+ var rename = await inputString(
+ context,
+ "请输入保存后的名称",
+ defaultValue: _task.title,
+ );
+ if (rename != null && rename.isNotEmpty) {
+ name = rename;
+ } else {
+ return;
+ }
+ }
+ print("path $path");
+ if (path != null) {
+ try {
+ setState(() {
+ exporting = true;
+ });
+ await method.exportComicDownloadToPkz(
+ [widget.comicId],
+ path,
+ name,
+ );
+ setState(() {
+ exportResult = "导出成功";
+ });
+ } catch (e) {
+ setState(() {
+ exportResult = "导出失败 $e";
+ });
+ } finally {
+ setState(() {
+ exporting = false;
+ });
+ }
+ }
+ },
+ child:
+ _buildButtonInner('导出到xxx.pkz\n(可直接打开观看的格式,不支持导入,可以躲避BD网盘或者TX的检测)'),
+ ));
+ widgets.add(Container(height: 10));
+ /////////////////////
widgets.add(MaterialButton(
onPressed: () async {
late String? path;
diff --git a/lib/screens/DownloadReaderScreen.dart b/lib/screens/DownloadReaderScreen.dart
index 9bc56ce..28d2bd0 100644
--- a/lib/screens/DownloadReaderScreen.dart
+++ b/lib/screens/DownloadReaderScreen.dart
@@ -76,7 +76,7 @@ class _DownloadReaderScreenState extends State {
}
FutureOr _onDownload() async {
- defaultToast(context, "您已经在下载阅读");
+ defaultToast(context, "您阅读的是下载漫画");
}
FutureOr _onChangeEp(int epOrder) {
diff --git a/lib/screens/InitScreen.dart b/lib/screens/InitScreen.dart
index 9152031..69a467f 100644
--- a/lib/screens/InitScreen.dart
+++ b/lib/screens/InitScreen.dart
@@ -1,3 +1,6 @@
+import 'dart:async';
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:pikapika/basic/config/Address.dart';
import 'package:pikapika/basic/config/AndroidDisplayMode.dart';
@@ -29,8 +32,10 @@ import 'package:pikapika/basic/config/TimeOffsetHour.dart';
import 'package:pikapika/basic/config/UsingRightClickPop.dart';
import 'package:pikapika/basic/config/Version.dart';
import 'package:pikapika/basic/config/VolumeController.dart';
-import 'package:pikapika/basic/config/shadowCategoriesMode.dart';
-
+import 'package:pikapika/basic/config/ShadowCategoriesMode.dart';
+import 'package:pikapika/screens/PkzArchiveScreen.dart';
+import 'package:app_links/app_links.dart';
+import 'package:uri_to_file/uri_to_file.dart';
import '../basic/config/ExportRename.dart';
import 'AccountScreen.dart';
import 'AppScreen.dart';
@@ -88,6 +93,32 @@ class _InitScreenState extends State {
await initUsingRightClickPop();
await initAuthentication();
autoCheckNewVersion();
+
+ final appLinks = AppLinks();
+ String? initUrl;
+ if (Platform.isAndroid || Platform.isIOS) {
+ try {
+ initUrl = (await appLinks.getInitialAppLink())?.toString();
+ // Use the uri and warn the user, if it is not correct,
+ // but keep in mind it could be `null`.
+ } on FormatException {
+ // Handle exception by warning the user their action did not succeed
+ // return?
+ }
+ }
+ if (initUrl != null) {
+ RegExp regExp = RegExp(r"^.*\.pkz$");
+ final matches = regExp.allMatches(initUrl!);
+ if (matches.isNotEmpty) {
+ File file = await toFile(initUrl!);
+ Navigator.of(context).pushReplacement(MaterialPageRoute(
+ builder: (BuildContext context) =>
+ PkzArchiveScreen(pkzPath: file.path),
+ ));
+ return;
+ }
+ }
+
setState(() {
_authenticating = currentAuthentication();
});
@@ -112,7 +143,8 @@ class _InitScreenState extends State {
onPressed: () {
_goAuthentication();
},
- child: const Text('您在之前使用APP时开启了身份验证, 请点这段文字进行身份核查, 核查通过后将会进入APP'),
+ child:
+ const Text('您在之前使用APP时开启了身份验证, 请点这段文字进行身份核查, 核查通过后将会进入APP'),
),
),
),
diff --git a/lib/screens/PkzArchiveScreen.dart b/lib/screens/PkzArchiveScreen.dart
new file mode 100644
index 0000000..b0835b4
--- /dev/null
+++ b/lib/screens/PkzArchiveScreen.dart
@@ -0,0 +1,123 @@
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:path/path.dart' as p;
+import 'package:permission_handler/permission_handler.dart';
+import 'package:pikapika/basic/Entities.dart';
+import 'package:pikapika/basic/Method.dart';
+import 'package:pikapika/screens/components/ContentBuilder.dart';
+import 'package:pikapika/screens/components/PkzComicInfoCard.dart';
+
+import '../basic/Navigator.dart';
+import 'PkzComicInfoScreen.dart';
+
+class PkzArchiveScreen extends StatefulWidget {
+ final String pkzPath;
+
+ const PkzArchiveScreen({Key? key, required this.pkzPath}) : super(key: key);
+
+ @override
+ State createState() => _PkzArchiveScreenState();
+}
+
+class _PkzArchiveScreenState extends State with RouteAware {
+ Map _logMap = {};
+ late String _fileName;
+ late Future _future;
+ late PkzArchive _info;
+
+ @override
+ void initState() {
+ _fileName = p.basename(widget.pkzPath);
+ _future = _load();
+ super.initState();
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ routeObserver.subscribe(this, ModalRoute.of(context)!);
+ }
+
+ @override
+ void dispose() {
+ routeObserver.unsubscribe(this);
+ super.dispose();
+ }
+
+ @override
+ void didPopNext() {
+ () async {
+ var a = await method.pkzComicViewLogs(_fileName, widget.pkzPath);
+ for (var value in a) {
+ _logMap[value.lastViewComicId] = value;
+ }
+ setState(() {});
+ }();
+ }
+
+ Future _load() async {
+ await method.viewPkz(_fileName, widget.pkzPath);
+ var p = await Permission.storage.request();
+ if (!p.isGranted) {
+ throw 'error permission';
+ }
+ _info = await method.pkzInfo(widget.pkzPath);
+ if (_info.comics.length == 1) {
+ Navigator.of(context).pushReplacement(MaterialPageRoute(
+ builder: (BuildContext context) => PkzComicInfoScreen(
+ pkzPath: widget.pkzPath,
+ pkzComic: _info.comics.first,
+ ),
+ ));
+ }
+ var a = await method.pkzComicViewLogs(_fileName, widget.pkzPath);
+ for (var value in a) {
+ _logMap[value.lastViewComicId] = value;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(_fileName),
+ ),
+ body: ContentBuilder(
+ future: _future,
+ onRefresh: () async {
+ setState(() {
+ _future = _load();
+ });
+ },
+ successBuilder: (
+ BuildContext context,
+ AsyncSnapshot snapshot,
+ ) {
+ return ListView(children: [
+ ..._info.comics
+ .map((e) => GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (BuildContext context) {
+ return PkzComicInfoScreen(
+ pkzComic: e,
+ pkzPath: widget.pkzPath,
+ );
+ },
+ ));
+ },
+ child: PkzComicInfoCard(
+ info: e,
+ pkzPath: widget.pkzPath,
+ displayViewLog: _logMap[e.id],
+ ),
+ ))
+ .toList(),
+ ]);
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/screens/PkzComicInfoScreen.dart b/lib/screens/PkzComicInfoScreen.dart
new file mode 100644
index 0000000..9d8fed9
--- /dev/null
+++ b/lib/screens/PkzComicInfoScreen.dart
@@ -0,0 +1,249 @@
+import 'package:flutter/material.dart';
+import 'package:path/path.dart' as p;
+import 'package:pikapika/basic/Entities.dart';
+import 'package:pikapika/basic/Method.dart';
+import 'package:pikapika/screens/PkzReaderScreen.dart';
+
+import '../basic/Navigator.dart';
+import 'components/PkzComicInfoCard.dart';
+
+class PkzComicInfoScreen extends StatefulWidget {
+ final String pkzPath;
+ final PkzComic pkzComic;
+
+ const PkzComicInfoScreen(
+ {Key? key, required this.pkzPath, required this.pkzComic})
+ : super(key: key);
+
+ @override
+ State createState() => _PkzComicInfoScreenState();
+}
+
+class _PkzComicInfoScreenState extends State
+ with RouteAware {
+ PkzComicViewLog? _log;
+
+ @override
+ void initState() {
+ _load();
+ super.initState();
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ routeObserver.subscribe(this, ModalRoute.of(context)!);
+ }
+
+ @override
+ void dispose() {
+ routeObserver.unsubscribe(this);
+ super.dispose();
+ }
+
+ @override
+ void didPopNext() {
+ () async {
+ _log = await method.pkzComicViewLogByPkzNameAndId(
+ p.basename(widget.pkzPath),
+ widget.pkzComic.id,
+ );
+ setState(() {});
+ }();
+ }
+
+ _load() async {
+ await method.viewPkzComic(
+ p.basename(widget.pkzPath),
+ widget.pkzPath,
+ widget.pkzComic.id,
+ widget.pkzComic.title,
+ );
+ _log = await method.pkzComicViewLogByPkzNameAndId(
+ p.basename(widget.pkzPath),
+ widget.pkzComic.id,
+ );
+ setState(() {});
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ List chapterButtons = [];
+ for (var volume in widget.pkzComic.volumes) {
+ for (var chapter in volume.chapters) {
+ chapterButtons.add(MaterialButton(
+ onPressed: () {
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (BuildContext context) {
+ return PkzReaderScreen(
+ comicInfo: widget.pkzComic,
+ currentEpId: chapter.id,
+ pkzPath: widget.pkzPath,
+ );
+ },
+ ));
+ },
+ color: Colors.white,
+ child: Text(
+ chapter.title,
+ style: const TextStyle(color: Colors.black),
+ ),
+ ));
+ }
+ }
+
+ final theme = Theme.of(context);
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(
+ widget.pkzComic.title,
+ ),
+ ),
+ body: ListView(children: [
+ PkzComicInfoCard(info: widget.pkzComic, pkzPath: widget.pkzPath),
+ Container(
+ padding: const EdgeInsets.only(top: 5, bottom: 5),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: theme.dividerColor,
+ ),
+ ),
+ ),
+ child: Wrap(
+ children: widget.pkzComic.tags.map((e) {
+ return Container(
+ padding: const EdgeInsets.only(
+ left: 10,
+ right: 10,
+ top: 3,
+ bottom: 3,
+ ),
+ margin: const EdgeInsets.only(
+ left: 5,
+ right: 5,
+ top: 3,
+ bottom: 3,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.pink.shade100,
+ border: Border.all(
+ style: BorderStyle.solid,
+ color: Colors.pink.shade400,
+ ),
+ borderRadius: const BorderRadius.all(Radius.circular(30)),
+ ),
+ child: Text(
+ e,
+ style: TextStyle(
+ color: Colors.pink.shade500,
+ height: 1.4,
+ ),
+ strutStyle: const StrutStyle(
+ height: 1.4,
+ ),
+ ),
+ );
+ }).toList(),
+ ),
+ ),
+ Container(
+ padding: const EdgeInsets.only(
+ top: 5,
+ bottom: 5,
+ left: 10,
+ right: 10,
+ ),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: theme.dividerColor,
+ ),
+ ),
+ ),
+ child: SelectableText(
+ widget.pkzComic.description,
+ style: const TextStyle(
+ fontSize: 13,
+ color: Colors.grey,
+ ),
+ ),
+ ),
+ LayoutBuilder(
+ builder: (BuildContext context, BoxConstraints constraints) {
+ PkzChapter? first;
+ Map chapters = {};
+ for (var vol in widget.pkzComic.volumes) {
+ for (var c in vol.chapters) {
+ first ??= c;
+ chapters[c.id] = (c);
+ }
+ }
+ print("chapters : ${chapters}");
+ if (chapters.isEmpty) {
+ return Container();
+ }
+ final width = constraints.maxWidth;
+ return Container(
+ padding: const EdgeInsets.only(left: 10, right: 10),
+ margin: const EdgeInsets.only(bottom: 10),
+ width: width,
+ child: MaterialButton(
+ onPressed: () {
+ if (chapters.containsKey(_log?.lastViewEpId)) {
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (BuildContext context) {
+ return PkzReaderScreen(
+ comicInfo: widget.pkzComic,
+ currentEpId: _log!.lastViewEpId,
+ pkzPath: widget.pkzPath,
+ initPicturePosition: _log!.lastViewPictureRank,
+ );
+ },
+ ));
+ return;
+ }
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (BuildContext context) {
+ return PkzReaderScreen(
+ comicInfo: widget.pkzComic,
+ currentEpId: first!.id,
+ pkzPath: widget.pkzPath,
+ );
+ },
+ ));
+ },
+ child: Row(
+ children: [
+ Expanded(
+ child: Container(
+ color: Theme.of(context)
+ .textTheme
+ .bodyText1!
+ .color!
+ .withOpacity(.05),
+ padding: const EdgeInsets.all(10),
+ child: Text(
+ chapters.containsKey(_log?.lastViewEpId)
+ ? "继续阅读 ${chapters[_log?.lastViewEpId]!.title}"
+ : "开始阅读",
+ textAlign: TextAlign.center,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ );
+ },
+ ),
+ Wrap(
+ spacing: 10,
+ runSpacing: 10,
+ alignment: WrapAlignment.spaceAround,
+ children: chapterButtons,
+ ),
+ ]),
+ );
+ }
+}
diff --git a/lib/screens/PkzReaderScreen.dart b/lib/screens/PkzReaderScreen.dart
new file mode 100644
index 0000000..8414755
--- /dev/null
+++ b/lib/screens/PkzReaderScreen.dart
@@ -0,0 +1,253 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:path/path.dart' as p;
+import 'package:pikapika/basic/Common.dart';
+import 'package:pikapika/basic/Entities.dart';
+import 'package:pikapika/basic/config/AutoFullScreen.dart';
+import 'package:pikapika/basic/config/FullScreenUI.dart';
+import 'package:pikapika/basic/config/ReaderDirection.dart';
+import 'package:pikapika/basic/config/ReaderType.dart';
+import 'package:pikapika/basic/Method.dart';
+import 'components/ContentError.dart';
+import 'components/ContentLoading.dart';
+import 'components/ImageReader.dart';
+import 'components/RightClickPop.dart';
+
+// 阅读下载的内容
+class PkzReaderScreen extends StatefulWidget {
+ final String pkzPath;
+ final PkzComic comicInfo;
+ late final List epList;
+ final String currentEpId;
+ final int? initPicturePosition;
+ final ReaderType pagerType = currentReaderType();
+ final ReaderDirection pagerDirection = gReaderDirection;
+ late final bool autoFullScreen;
+
+ PkzReaderScreen({
+ Key? key,
+ required this.comicInfo,
+ required this.currentEpId,
+ this.initPicturePosition,
+ bool? autoFullScreen,
+ required this.pkzPath,
+ }) : super(key: key) {
+ epList = [];
+ for (var volume in comicInfo.volumes) {
+ for (var chapter in volume.chapters) {
+ epList.add(chapter);
+ }
+ }
+ this.autoFullScreen = autoFullScreen ?? currentAutoFullScreen();
+ }
+
+ @override
+ State createState() => _PkzReaderScreenState();
+}
+
+class _PkzReaderScreenState extends State {
+ late PkzChapter _ep;
+ late int _epOrder;
+ late bool _fullScreen = false;
+ late List pictures = [];
+ late Future _future = _load();
+ int? _lastChangeRank;
+ bool _replacement = false;
+
+ @override
+ void initState() {
+ // EP
+ pictures.clear();
+ for (var ep in widget.epList) {
+ if (ep.id == widget.currentEpId) {
+ _ep = ep;
+ _epOrder = widget.epList.indexOf(ep);
+ pictures.addAll(ep.pictures);
+ break;
+ }
+ }
+ if (widget.autoFullScreen) {
+ setState(() {
+ SystemChrome.setEnabledSystemUIMode(
+ SystemUiMode.manual,
+ overlays: [],
+ );
+ _fullScreen = true;
+ });
+ }
+ // INIT
+ _future = _load();
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ if (!_replacement) {
+ switchFullScreenUI();
+ }
+ super.dispose();
+ }
+
+ Future _load() async {
+ if (widget.initPicturePosition == null) {
+ await method.viewPkzEpAndPicture(
+ p.basename(widget.pkzPath),
+ widget.pkzPath,
+ widget.comicInfo.id,
+ widget.comicInfo.title,
+ _ep.id,
+ _ep.title,
+ 0,
+ );
+ }
+ }
+
+ Future _onPositionChange(int position) async {
+ _lastChangeRank = position;
+ await method.viewPkzEpAndPicture(
+ p.basename(widget.pkzPath),
+ widget.pkzPath,
+ widget.comicInfo.id,
+ widget.comicInfo.title,
+ _ep.id,
+ _ep.title,
+ position,
+ );
+ return;
+ }
+
+ FutureOr _onDownload() async {
+ defaultToast(context, "您阅读的是下载漫画");
+ }
+
+ FutureOr _onChangeEp(int epOrder) {
+ final ep = widget.epList[epOrder];
+ _replacement = true;
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (context) => PkzReaderScreen(
+ comicInfo: widget.comicInfo,
+ pkzPath: widget.pkzPath,
+ currentEpId: ep.id,
+ autoFullScreen: _fullScreen,
+ ),
+ ),
+ );
+ }
+
+ FutureOr _onReloadEp() {
+ _replacement = true;
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (context) => PkzReaderScreen(
+ comicInfo: widget.comicInfo,
+ currentEpId: widget.currentEpId,
+ initPicturePosition: _lastChangeRank ?? widget.initPicturePosition,
+ // maybe null
+ autoFullScreen: _fullScreen,
+ pkzPath: widget.pkzPath,
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return rightClickPop(
+ child: buildScreen(context),
+ context: context,
+ canPop: true,
+ );
+ }
+
+ Widget buildScreen(BuildContext context) {
+ return readerKeyboardHolder(_build(context));
+ }
+
+ Widget _build(BuildContext context) {
+ return FutureBuilder(
+ future: _future,
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
+ if (snapshot.hasError) {
+ return Scaffold(
+ appBar: _fullScreen
+ ? null
+ : AppBar(
+ title: Text("${_ep.title} - ${widget.comicInfo.title}"),
+ ),
+ body: ContentError(
+ error: snapshot.error,
+ stackTrace: snapshot.stackTrace,
+ onRefresh: () async {
+ setState(() {
+ _future = _load();
+ });
+ },
+ ),
+ );
+ }
+ if (snapshot.connectionState != ConnectionState.done) {
+ return Scaffold(
+ appBar: _fullScreen
+ ? null
+ : AppBar(
+ title: Text("${_ep.title} - ${widget.comicInfo.title}"),
+ ),
+ body: const ContentLoading(label: '加载中'),
+ );
+ }
+ var epNameMap = {};
+ for (var i = 0; i < widget.epList.length; i++) {
+ epNameMap[i] = widget.epList[i].title;
+ }
+ return Scaffold(
+ body: ImageReader(
+ ImageReaderStruct(
+ images: pictures
+ .map((e) => ReaderImageInfo(
+ "",
+ "",
+ "",
+ e.width,
+ e.height,
+ e.format,
+ 0,
+ pkzFile: PkzFile(widget.pkzPath, e.picturePath),
+ ))
+ .toList(),
+ fullScreen: _fullScreen,
+ onFullScreenChange: _onFullScreenChange,
+ onPositionChange: _onPositionChange,
+ initPosition: widget.initPicturePosition,
+ epOrder: _epOrder,
+ epNameMap: epNameMap,
+ comicTitle: widget.comicInfo.title,
+ onReloadEp: _onReloadEp,
+ onChangeEp: _onChangeEp,
+ onDownload: _onDownload,
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ Future _onFullScreenChange(bool fullScreen) async {
+ setState(() {
+ if (fullScreen) {
+ if (Platform.isAndroid || Platform.isIOS) {
+ SystemChrome.setEnabledSystemUIMode(
+ SystemUiMode.manual,
+ overlays: [],
+ );
+ }
+ } else {
+ switchFullScreenUI();
+ }
+ _fullScreen = fullScreen;
+ });
+ }
+}
diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart
index 9b53d8e..bd28fe8 100644
--- a/lib/screens/SettingsScreen.dart
+++ b/lib/screens/SettingsScreen.dart
@@ -25,7 +25,7 @@ import 'package:pikapika/basic/config/Themes.dart';
import 'package:pikapika/basic/config/TimeOffsetHour.dart';
import 'package:pikapika/basic/config/Version.dart';
import 'package:pikapika/basic/config/VolumeController.dart';
-import 'package:pikapika/basic/config/shadowCategoriesMode.dart';
+import 'package:pikapika/basic/config/ShadowCategoriesMode.dart';
import 'package:pikapika/screens/components/NetworkSetting.dart';
import 'package:pikapika/screens/components/RightClickPop.dart';
diff --git a/lib/screens/components/ComicInfoCard.dart b/lib/screens/components/ComicInfoCard.dart
index c7310d9..3b8fcf0 100644
--- a/lib/screens/components/ComicInfoCard.dart
+++ b/lib/screens/components/ComicInfoCard.dart
@@ -367,3 +367,8 @@ final authorStyle = TextStyle(
fontSize: 13,
color: Colors.pink.shade300,
);
+
+final authorStyleX = TextStyle(
+ fontSize: 13,
+ color: Colors.pink.shade300.withOpacity(.7),
+);
diff --git a/lib/screens/components/ComicList.dart b/lib/screens/components/ComicList.dart
index df61515..1bbf9a7 100644
--- a/lib/screens/components/ComicList.dart
+++ b/lib/screens/components/ComicList.dart
@@ -7,7 +7,7 @@ import 'package:pikapika/basic/Entities.dart';
import 'package:pikapika/basic/Method.dart';
import 'package:pikapika/basic/config/ShadowCategories.dart';
import 'package:pikapika/basic/config/ListLayout.dart';
-import 'package:pikapika/basic/config/shadowCategoriesMode.dart';
+import 'package:pikapika/basic/config/ShadowCategoriesMode.dart';
import 'ComicInfoCard.dart';
import 'Images.dart';
diff --git a/lib/screens/components/ImageReader.dart b/lib/screens/components/ImageReader.dart
index 23042bc..f6a856a 100644
--- a/lib/screens/components/ImageReader.dart
+++ b/lib/screens/components/ImageReader.dart
@@ -21,6 +21,7 @@ import 'package:pikapika/basic/config/ReaderDirection.dart';
import 'package:pikapika/basic/config/ReaderSliderPosition.dart';
import 'package:pikapika/basic/config/ReaderType.dart';
import 'package:pikapika/basic/config/VolumeController.dart';
+import 'package:pikapika/screens/components/PkzImages.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../FilePhotoViewScreen.dart';
import 'gesture_zoom_box.dart';
@@ -30,7 +31,7 @@ import 'Images.dart';
///////////////
Event<_ReaderControllerEventArgs> _readerControllerEvent =
-Event<_ReaderControllerEventArgs>();
+ Event<_ReaderControllerEventArgs>();
class _ReaderControllerEventArgs extends EventArgs {
final String key;
@@ -93,6 +94,13 @@ void delVolumeListen() {
// 对Reader的传参以及封装
+class PkzFile {
+ final String pkzPath;
+ final String path;
+
+ PkzFile(this.pkzPath, this.path);
+}
+
class ReaderImageInfo {
final String fileServer;
final String path;
@@ -101,9 +109,18 @@ class ReaderImageInfo {
final int? height;
final String? format;
final int? fileSize;
+ final PkzFile? pkzFile;
- ReaderImageInfo(this.fileServer, this.path, this.downloadLocalPath,
- this.width, this.height, this.format, this.fileSize);
+ ReaderImageInfo(
+ this.fileServer,
+ this.path,
+ this.downloadLocalPath,
+ this.width,
+ this.height,
+ this.format,
+ this.fileSize, {
+ this.pkzFile,
+ });
}
class ImageReaderStruct {
@@ -156,7 +173,7 @@ class _ImageReaderState extends State {
late final FullScreenAction _fullScreenAction = currentFullScreenAction();
late final ReaderSliderPosition _readerSliderPosition =
- currentReaderSliderPosition();
+ currentReaderSliderPosition();
@override
Widget build(BuildContext context) {
@@ -186,12 +203,12 @@ class _ImageReaderContent extends StatefulWidget {
final ImageReaderStruct struct;
const _ImageReaderContent(
- this.struct,
- this.pagerDirection,
- this.pagerType,
- this.fullScreenAction,
- this.readerSliderPosition,
- );
+ this.struct,
+ this.pagerDirection,
+ this.pagerType,
+ this.fullScreenAction,
+ this.readerSliderPosition,
+ );
@override
State createState() {
@@ -290,7 +307,7 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> {
@override
Widget build(BuildContext context) {
switch (currentFullScreenAction()) {
- // 按钮
+ // 按钮
case FullScreenAction.CONTROLLER:
return Stack(
children: [
@@ -339,37 +356,37 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> {
widget.struct.fullScreen
? Container()
: Container(
- height: 45,
- color: const Color(0x88000000),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Container(width: 15),
- IconButton(
- icon: const Icon(Icons.fullscreen),
- color: Colors.white,
- onPressed: () {
- widget.struct
- .onFullScreenChange(!widget.struct.fullScreen);
- },
+ height: 45,
+ color: const Color(0x88000000),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(width: 15),
+ IconButton(
+ icon: const Icon(Icons.fullscreen),
+ color: Colors.white,
+ onPressed: () {
+ widget.struct
+ .onFullScreenChange(!widget.struct.fullScreen);
+ },
+ ),
+ Container(width: 10),
+ Expanded(
+ child:
+ widget.pagerType != ReaderType.WEB_TOON_FREE_ZOOM
+ ? _buildSliderBottom()
+ : Container(),
+ ),
+ Container(width: 10),
+ IconButton(
+ icon: const Icon(Icons.skip_next_outlined),
+ color: Colors.white,
+ onPressed: _onNextAction,
+ ),
+ Container(width: 15),
+ ],
+ ),
),
- Container(width: 10),
- Expanded(
- child:
- widget.pagerType != ReaderType.WEB_TOON_FREE_ZOOM
- ? _buildSliderBottom()
- : Container(),
- ),
- Container(width: 10),
- IconButton(
- icon: const Icon(Icons.skip_next_outlined),
- color: Colors.white,
- onPressed: _onNextAction,
- ),
- Container(width: 15),
- ],
- ),
- ),
],
);
case ReaderSliderPosition.RIGHT:
@@ -406,19 +423,19 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> {
Widget _buildAppBar() => widget.struct.fullScreen
? Container()
: AppBar(
- title: Text(
- "${widget.struct.epNameMap[widget.struct.epOrder] ?? ""} - ${widget.struct.comicTitle}"),
- actions: [
- IconButton(
- onPressed: _onChooseEp,
- icon: const Icon(Icons.menu_open),
- ),
- IconButton(
- onPressed: _onMoreSetting,
- icon: const Icon(Icons.more_horiz),
- ),
- ],
- );
+ title: Text(
+ "${widget.struct.epNameMap[widget.struct.epOrder] ?? ""} - ${widget.struct.comicTitle}"),
+ actions: [
+ IconButton(
+ onPressed: _onChooseEp,
+ icon: const Icon(Icons.menu_open),
+ ),
+ IconButton(
+ onPressed: _onMoreSetting,
+ icon: const Icon(Icons.more_horiz),
+ ),
+ ],
+ );
Widget _buildSliderBottom() {
return Column(
@@ -436,52 +453,52 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> {
Widget _buildSliderLeft() => widget.struct.fullScreen
? Container()
: Align(
- alignment: Alignment.centerLeft,
- child: Material(
- color: Colors.transparent,
- child: Container(
- width: 35,
- height: 300,
- decoration: const BoxDecoration(
- color: Color(0x66000000),
- borderRadius: BorderRadius.only(
- topRight: Radius.circular(10),
- bottomRight: Radius.circular(10),
+ alignment: Alignment.centerLeft,
+ child: Material(
+ color: Colors.transparent,
+ child: Container(
+ width: 35,
+ height: 300,
+ decoration: const BoxDecoration(
+ color: Color(0x66000000),
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(10),
+ bottomRight: Radius.circular(10),
+ ),
+ ),
+ padding:
+ const EdgeInsets.only(top: 10, bottom: 10, left: 6, right: 5),
+ child: Center(
+ child: _buildSliderWidget(Axis.vertical),
+ ),
+ ),
),
- ),
- padding:
- const EdgeInsets.only(top: 10, bottom: 10, left: 6, right: 5),
- child: Center(
- child: _buildSliderWidget(Axis.vertical),
- ),
- ),
- ),
- );
+ );
Widget _buildSliderRight() => widget.struct.fullScreen
? Container()
: Align(
- alignment: Alignment.centerRight,
- child: Material(
- color: Colors.transparent,
- child: Container(
- width: 35,
- height: 300,
- decoration: const BoxDecoration(
- color: Color(0x66000000),
- borderRadius: BorderRadius.only(
- topLeft: Radius.circular(10),
- bottomLeft: Radius.circular(10),
+ alignment: Alignment.centerRight,
+ child: Material(
+ color: Colors.transparent,
+ child: Container(
+ width: 35,
+ height: 300,
+ decoration: const BoxDecoration(
+ color: Color(0x66000000),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(10),
+ bottomLeft: Radius.circular(10),
+ ),
+ ),
+ padding:
+ const EdgeInsets.only(top: 10, bottom: 10, left: 5, right: 6),
+ child: Center(
+ child: _buildSliderWidget(Axis.vertical),
+ ),
+ ),
),
- ),
- padding:
- const EdgeInsets.only(top: 10, bottom: 10, left: 5, right: 6),
- child: Center(
- child: _buildSliderWidget(Axis.vertical),
- ),
- ),
- ),
- );
+ );
Widget _buildSliderWidget(Axis axis) {
return FlutterSlider(
@@ -544,7 +561,7 @@ abstract class _ImageReaderContentState extends State<_ImageReaderContent> {
color: Colors.transparent,
child: Container(
padding:
- const EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4),
+ const EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4),
margin: const EdgeInsets.only(bottom: 10),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
@@ -930,7 +947,13 @@ class _WebToonReaderState extends _ImageReaderContentState {
@override
void initState() {
for (var e in widget.struct.images) {
- if (e.downloadLocalPath != null) {
+ if (e.pkzFile != null &&
+ e.width != null &&
+ e.height != null &&
+ e.width! > 0 &&
+ e.height! > 0) {
+ _trueSizes.add(Size(e.width!.toDouble(), e.height!.toDouble()));
+ } else if (e.downloadLocalPath != null) {
_trueSizes.add(Size(e.width!.toDouble(), e.height!.toDouble()));
} else {
_trueSizes.add(null);
@@ -1030,7 +1053,16 @@ class _WebToonReaderState extends _ImageReaderContentState {
}
var e = widget.struct.images[index];
- if (e.downloadLocalPath != null) {
+ if (e.pkzFile != null) {
+ _images.add(_WebToonPkzImage(
+ width: e.width!,
+ height: e.height!,
+ format: e.format!,
+ size: renderSize,
+ onTrueSize: onTrueSize,
+ pkzFile: e.pkzFile!,
+ ));
+ } else if (e.downloadLocalPath != null) {
_images.add(_WebToonDownloadImage(
fileServer: e.fileServer,
path: e.path,
@@ -1054,9 +1086,9 @@ class _WebToonReaderState extends _ImageReaderContentState {
return ScrollablePositionedList.builder(
initialScrollIndex: super._startIndex,
scrollDirection:
- widget.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
- ? Axis.vertical
- : Axis.horizontal,
+ widget.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
+ ? Axis.vertical
+ : Axis.horizontal,
reverse: widget.pagerDirection == ReaderDirection.RIGHT_TO_LEFT,
padding: EdgeInsets.only(
// 不管全屏与否, 滚动方向如何, 顶部永远保持间距
@@ -1064,9 +1096,9 @@ class _WebToonReaderState extends _ImageReaderContentState {
bottom: widget.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
? 130 // 纵向滚动 底部永远都是130的空白
: ( // 横向滚动
- widget.struct.fullScreen
- ? super._topBarHeight() // 全屏时底部和顶部到屏幕边框距离一样保持美观
- : super._bottomBarHeight())
+ widget.struct.fullScreen
+ ? super._topBarHeight() // 全屏时底部和顶部到屏幕边框距离一样保持美观
+ : super._bottomBarHeight())
// 非全屏时, 顶部去掉顶部BAR的高度, 底部去掉底部BAR的高度, 形成看似填充的效果
,
),
@@ -1147,17 +1179,47 @@ class _WebToonDownloadImage extends _WebToonReaderImage {
}
}
+// 来自PKZ
+class _WebToonPkzImage extends StatelessWidget {
+ final PkzFile pkzFile;
+ final int width;
+ final int height;
+ final String format;
+ final Size size;
+ Function(Size)? onTrueSize;
+
+ _WebToonPkzImage({
+ required this.pkzFile,
+ required this.width,
+ required this.height,
+ required this.format,
+ required this.size,
+ required this.onTrueSize,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return PkzLoadingImage(
+ pkzPath: pkzFile.pkzPath,
+ path: pkzFile.path,
+ width: size.width,
+ height: size.height,
+ onTrueSize: onTrueSize,
+ );
+ }
+}
+
// 来自远端
class _WebToonRemoteImage extends _WebToonReaderImage {
final String fileServer;
final String path;
_WebToonRemoteImage(
- this.fileServer,
- this.path,
- Size size,
- Function(Size)? onTrueSize,
- ) : super(size, onTrueSize);
+ this.fileServer,
+ this.path,
+ Size size,
+ Function(Size)? onTrueSize,
+ ) : super(size, onTrueSize);
@override
Future imageData() async {
@@ -1197,14 +1259,14 @@ class _WebToonReaderImageState extends State<_WebToonReaderImage> {
return FutureBuilder(
future: _future,
builder: (
- BuildContext context,
- AsyncSnapshot snapshot,
- ) {
+ BuildContext context,
+ AsyncSnapshot snapshot,
+ ) {
if (snapshot.hasError) {
return GestureDetector(
onLongPress: () async {
String? choose =
- await chooseListDialog(context, '请选择', ['重新加载图片']);
+ await chooseListDialog(context, '请选择', ['重新加载图片']);
switch (choose) {
case '重新加载图片':
setState(() {
@@ -1257,7 +1319,13 @@ class _ListViewReaderState extends _ImageReaderContentState
@override
void initState() {
for (var e in widget.struct.images) {
- if (e.downloadLocalPath != null) {
+ if (e.pkzFile != null &&
+ e.width != null &&
+ e.height != null &&
+ e.width! > 0 &&
+ e.height! > 0) {
+ _trueSizes.add(Size(e.width!.toDouble(), e.height!.toDouble()));
+ } else if (e.downloadLocalPath != null) {
_trueSizes.add(Size(e.width!.toDouble(), e.height!.toDouble()));
} else {
_trueSizes.add(null);
@@ -1332,7 +1400,16 @@ class _ListViewReaderState extends _ImageReaderContentState
}
var e = widget.struct.images[index];
- if (e.downloadLocalPath != null) {
+ if (e.pkzFile != null) {
+ _images.add(_WebToonPkzImage(
+ width: e.width!,
+ height: e.height!,
+ format: e.format!,
+ size: renderSize,
+ onTrueSize: onTrueSize,
+ pkzFile: e.pkzFile!,
+ ));
+ } else if (e.downloadLocalPath != null) {
_images.add(_WebToonDownloadImage(
fileServer: e.fileServer,
path: e.path,
@@ -1355,9 +1432,9 @@ class _ListViewReaderState extends _ImageReaderContentState
}
var list = ListView.builder(
scrollDirection:
- widget.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
- ? Axis.vertical
- : Axis.horizontal,
+ widget.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
+ ? Axis.vertical
+ : Axis.horizontal,
reverse: widget.pagerDirection == ReaderDirection.RIGHT_TO_LEFT,
padding: EdgeInsets.only(
// 不管全屏与否, 滚动方向如何, 顶部永远保持间距
@@ -1365,9 +1442,9 @@ class _ListViewReaderState extends _ImageReaderContentState
bottom: widget.pagerDirection == ReaderDirection.TOP_TO_BOTTOM
? 130 // 纵向滚动 底部永远都是130的空白
: ( // 横向滚动
- widget.struct.fullScreen
- ? super._topBarHeight() // 全屏时底部和顶部到屏幕边框距离一样保持美观
- : super._bottomBarHeight())
+ widget.struct.fullScreen
+ ? super._topBarHeight() // 全屏时底部和顶部到屏幕边框距离一样保持美观
+ : super._bottomBarHeight())
// 非全屏时, 顶部去掉顶部BAR的高度, 底部去掉底部BAR的高度, 形成看似填充的效果
,
),
@@ -1499,10 +1576,26 @@ class _GalleryReaderState extends _ImageReaderContentState {
itemCount: widget.struct.images.length,
builder: (BuildContext context, int index) {
var item = widget.struct.images[index];
+ if (item.pkzFile != null) {
+ return PhotoViewGalleryPageOptions(
+ imageProvider:
+ PkzImageProvider(item.pkzFile!.pkzPath, item.pkzFile!.path),
+ errorBuilder: (b, e, s) {
+ print("$e,$s");
+ return LayoutBuilder(
+ builder: (BuildContext context, BoxConstraints constraints) {
+ return buildError(
+ constraints.maxWidth, constraints.maxHeight);
+ },
+ );
+ },
+ filterQuality: FilterQuality.high,
+ );
+ }
if (item.downloadLocalPath != null) {
return PhotoViewGalleryPageOptions(
imageProvider:
- ResourceDownloadFileImageProvider(item.downloadLocalPath!),
+ ResourceDownloadFileImageProvider(item.downloadLocalPath!),
errorBuilder: (b, e, s) {
print("$e,$s");
return LayoutBuilder(
@@ -1517,7 +1610,7 @@ class _GalleryReaderState extends _ImageReaderContentState {
}
return PhotoViewGalleryPageOptions(
imageProvider:
- ResourceRemoteImageProvider(item.fileServer, item.path),
+ ResourceRemoteImageProvider(item.fileServer, item.path),
errorBuilder: (b, e, s) {
print("$e,$s");
return LayoutBuilder(
@@ -1535,6 +1628,11 @@ class _GalleryReaderState extends _ImageReaderContentState {
child: gallery,
onLongPress: () async {
if (_current >= 0 && _current < widget.struct.images.length) {
+ var item = widget.struct.images[_current];
+ if (item.pkzFile != null) {
+ return;
+ }
+
Future load() async {
var item = widget.struct.images[_current];
if (item.downloadLocalPath != null) {
@@ -1545,7 +1643,7 @@ class _GalleryReaderState extends _ImageReaderContentState {
}
String? choose =
- await chooseListDialog(context, '请选择', ['预览图片', '保存图片']);
+ await chooseListDialog(context, '请选择', ['预览图片', '保存图片']);
switch (choose) {
case '预览图片':
try {
@@ -1594,7 +1692,7 @@ class _GalleryReaderState extends _ImageReaderContentState {
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding:
- const EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4),
+ const EdgeInsets.only(left: 10, right: 10, top: 4, bottom: 4),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
@@ -1619,4 +1717,4 @@ class _GalleryReaderState extends _ImageReaderContentState {
}
}
-///////////////////////////////////////////////////////////////////////////////
\ No newline at end of file
+///////////////////////////////////////////////////////////////////////////////
diff --git a/lib/screens/components/PkzComicInfoCard.dart b/lib/screens/components/PkzComicInfoCard.dart
new file mode 100644
index 0000000..3d05888
--- /dev/null
+++ b/lib/screens/components/PkzComicInfoCard.dart
@@ -0,0 +1,138 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:pikapika/basic/Cross.dart';
+import 'package:pikapika/basic/Entities.dart';
+import 'package:pikapika/basic/Method.dart';
+import 'package:pikapika/screens/SearchScreen.dart';
+import 'package:pikapika/basic/Navigator.dart';
+
+import 'ComicInfoCard.dart';
+import 'PkzImages.dart';
+
+// 漫画卡片
+class PkzComicInfoCard extends StatefulWidget {
+ final String pkzPath;
+ final PkzComic info;
+ final bool linkItem;
+ final PkzComicViewLog? displayViewLog;
+
+ const PkzComicInfoCard({
+ required this.info,
+ required this.pkzPath,
+ this.linkItem = false,
+ Key? key,
+ this.displayViewLog,
+ }) : super(key: key);
+
+ @override
+ State createState() => _ComicInfoCard();
+}
+
+class _ComicInfoCard extends State {
+ @override
+ Widget build(BuildContext context) {
+ var info = widget.info;
+ final 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: PkzImage(
+ pkzPath: widget.pkzPath,
+ path: info.coverPath,
+ width: imageWidth,
+ height: imageHeight,
+ ),
+ ),
+ Expanded(
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ widget.linkItem
+ ? GestureDetector(
+ onLongPress: () {
+ confirmCopy(context, info.title);
+ },
+ child: Text(info.title, style: titleStyle),
+ )
+ : Text(info.title, style: titleStyle),
+ Container(height: 5),
+ widget.linkItem
+ ? InkWell(
+ onTap: () {
+ // todo
+ },
+ onLongPress: () {
+ confirmCopy(context, info.author);
+ },
+ child: Text(info.author, style: authorStyle),
+ )
+ : Text(info.author, style: authorStyle),
+ Container(height: 5),
+ Text.rich(
+ widget.linkItem
+ ? TextSpan(
+ children: [
+ const TextSpan(text: '分类 :'),
+ ...info.categories.map(
+ (e) => TextSpan(
+ children: [
+ const TextSpan(text: ' '),
+ TextSpan(
+ text: e,
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ // todo
+ }),
+ ],
+ ),
+ ),
+ ],
+ )
+ : TextSpan(
+ text: "分类 : ${info.categories.join(' ')}"),
+ style: TextStyle(
+ fontSize: 13,
+ color: Theme.of(context)
+ .textTheme
+ .bodyText1!
+ .color!
+ .withAlpha(0xCC),
+ ),
+ ),
+ Container(height: 5),
+ widget.displayViewLog != null &&
+ widget.displayViewLog!.lastViewEpId.isNotEmpty
+ ? Container(
+ padding: EdgeInsets.only(bottom: 5),
+ child: Text(
+ "上次观看到 ${widget.displayViewLog!.lastViewEpName}",
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: authorStyleX,
+ ),
+ )
+ : Container(),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/screens/components/PkzImages.dart b/lib/screens/components/PkzImages.dart
new file mode 100644
index 0000000..9d3ee71
--- /dev/null
+++ b/lib/screens/components/PkzImages.dart
@@ -0,0 +1,176 @@
+import 'dart:typed_data';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:pikapika/basic/Common.dart';
+import 'package:pikapika/basic/Cross.dart';
+import 'package:pikapika/basic/Method.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:pikapika/basic/config/ImageAddress.dart';
+import 'dart:io';
+import 'dart:ui' as ui show Codec;
+
+import '../FilePhotoViewScreen.dart';
+import 'Images.dart';
+
+// 从本地加载图片
+class PkzImageProvider extends ImageProvider {
+ final String pkzPath;
+ final String path;
+ final double scale;
+
+ PkzImageProvider(this.pkzPath, this.path, {this.scale = 1.0});
+
+ @override
+ ImageStreamCompleter load(PkzImageProvider key, DecoderCallback decode) {
+ return MultiFrameImageStreamCompleter(
+ codec: _loadAsync(key),
+ scale: key.scale,
+ );
+ }
+
+ @override
+ Future obtainKey(ImageConfiguration configuration) {
+ return SynchronousFuture(this);
+ }
+
+ Future _loadAsync(PkzImageProvider key) async {
+ assert(key == this);
+ return PaintingBinding.instance!.instantiateImageCodec(
+ await method.loadPkzFile(pkzPath, path),
+ );
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ if (other.runtimeType != runtimeType) return false;
+ final PkzImageProvider typedOther = other;
+ return pkzPath == typedOther.pkzPath &&
+ path == typedOther.path &&
+ scale == typedOther.scale;
+ }
+
+ @override
+ int get hashCode => hashValues(path, scale);
+
+ @override
+ String toString() => '$runtimeType('
+ ' pkzPath: ${describeIdentity(pkzPath)},'
+ ' path: ${describeIdentity(path)},'
+ ' scale: $scale'
+ ')';
+}
+
+// 远端图片
+class PkzImage extends StatefulWidget {
+ final String pkzPath;
+ final String path;
+ final double? width;
+ final double? height;
+ final BoxFit fit;
+
+ const PkzImage({
+ Key? key,
+ required this.pkzPath,
+ required this.path,
+ this.width,
+ this.height,
+ this.fit = BoxFit.cover,
+ }) : super(key: key);
+
+ @override
+ State createState() => _PkzImageState();
+}
+
+class _PkzImageState extends State {
+ late bool _mock;
+
+ @override
+ void initState() {
+ _mock = widget.path == "";
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (_mock) {
+ return buildMock(widget.width, widget.height);
+ }
+ return Image(
+ image: PkzImageProvider(widget.pkzPath, widget.path),
+ width: widget.width,
+ height: widget.height,
+ errorBuilder: (a, b, c) {
+ print("$b");
+ print("$c");
+ return buildError(widget.width, widget.height);
+ },
+ fit: widget.fit,
+ );
+ }
+}
+
+
+// 远端图片
+class PkzLoadingImage extends StatefulWidget {
+ final String pkzPath;
+ final String path;
+ final double? width;
+ final double? height;
+ final BoxFit fit;
+ final Function(Size)? onTrueSize;
+
+ const PkzLoadingImage({
+ Key? key,
+ required this.pkzPath,
+ required this.path,
+ this.width,
+ this.height,
+ this.fit = BoxFit.cover,
+ this.onTrueSize,
+ }) : super(key: key);
+
+ @override
+ State createState() => _PkzLoadingImageState();
+}
+
+class _PkzLoadingImageState extends State {
+
+ late bool _mock;
+ late Future data;
+
+ @override
+ void initState() {
+ _mock = widget.path == "";
+ if (!_mock) {
+ data = () async {
+ final data = await method.loadPkzFile(widget.pkzPath, widget.path);
+ if (widget.onTrueSize != null) {
+ var decodedImage = await decodeImageFromList(data);
+ widget.onTrueSize!(Size(
+ decodedImage.width.toDouble(), decodedImage.height.toDouble(),),);
+ }
+ return data;
+ }();
+ }
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (_mock) {
+ return buildMock(widget.width, widget.height);
+ }
+ return Image(
+ image: PkzImageProvider(widget.pkzPath, widget.path),
+ width: widget.width,
+ height: widget.height,
+ errorBuilder: (a, b, c) {
+ print("$b");
+ print("$c");
+ return buildError(widget.width, widget.height);
+ },
+ fit: widget.fit,
+ );
+ }
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 8236f57..a1eb7ee 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,8 +5,10 @@
import FlutterMacOS
import Foundation
+import app_links_macos
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index 7cfbb8d..f4dc71f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -8,6 +8,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1+2"
+ app_links:
+ dependency: "direct main"
+ description:
+ name: app_links
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.2.0"
+ app_links_macos:
+ dependency: transitive
+ description:
+ name: app_links_macos
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
+ app_links_platform_interface:
+ dependency: transitive
+ description:
+ name: app_links_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.2"
+ app_links_web:
+ dependency: transitive
+ description:
+ name: app_links_web
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
+ app_links_windows:
+ dependency: transitive
+ description:
+ name: app_links_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
archive:
dependency: transitive
description:
@@ -171,7 +206,7 @@ packages:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0"
+ version: "1.1.1"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -300,7 +335,7 @@ packages:
name: modal_bottom_sheet
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.1"
+ version: "2.1.0"
multi_select_flutter:
dependency: "direct main"
description:
@@ -309,7 +344,7 @@ packages:
source: hosted
version: "4.1.2"
path:
- dependency: transitive
+ dependency: "direct main"
description:
name: path
url: "https://pub.dartlang.org"
@@ -432,13 +467,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
+ uri_to_file:
+ dependency: "direct main"
+ description:
+ name: uri_to_file
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
- version: "6.1.3"
+ version: "6.1.4"
url_launcher_android:
dependency: transitive
description:
@@ -511,4 +553,4 @@ packages:
version: "6.1.0"
sdks:
dart: ">=2.17.0 <3.0.0"
- flutter: ">=2.11.0-0.1.pre"
+ flutter: ">=3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 6f15ccd..c667fc8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -47,6 +47,9 @@ dependencies:
file_picker: ^4.5.1
crop_image: ^1.0.2
image: ^3.1.3
+ path: ^1.8.0
+ app_links: ^3.2.0
+ uri_to_file: ^0.2.0
dev_dependencies:
flutter_test:
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 4f78848..96d0d6d 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,9 +6,12 @@
#include "generated_plugin_registrant.h"
+#include
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ AppLinksWindowsPluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AppLinksWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 88b22e5..9a732f7 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ app_links_windows
url_launcher_windows
)