ReadPKZ
This commit is contained in:
parent
b164b74e31
commit
cc5b16ee33
|
@ -48,6 +48,19 @@
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:mimeType="*/*"
|
||||||
|
android:pathPattern=".*\\.pkz"
|
||||||
|
android:scheme="content" />
|
||||||
|
<data
|
||||||
|
android:mimeType="*/*"
|
||||||
|
android:pathPattern=".*\\.pkz"
|
||||||
|
android:scheme="file" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
return
|
|
||||||
// get version
|
// get version
|
||||||
var version commons.Version
|
var version commons.Version
|
||||||
codeFile, err := ioutil.ReadFile("version.code.txt")
|
codeFile, err := ioutil.ReadFile("version.code.txt")
|
||||||
|
|
|
@ -687,3 +687,328 @@ class Collection {
|
||||||
.toList();
|
.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<PkzComic> comics;
|
||||||
|
late final int comicCount;
|
||||||
|
late final int volumesCount;
|
||||||
|
late final int chapterCount;
|
||||||
|
late final int pictureCount;
|
||||||
|
|
||||||
|
PkzArchive.fromJson(Map<String, dynamic> 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<String, dynamic> toJson() {
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_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<String> categories;
|
||||||
|
late final List<String> 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<PkzVolume> 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<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
title = json['title'];
|
||||||
|
categories = List.castFrom<dynamic, String>(json['categories']);
|
||||||
|
tags = List.castFrom<dynamic, String>(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<String, dynamic> toJson() {
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_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<PkzChapter> chapters;
|
||||||
|
late final int chapterCount;
|
||||||
|
late final int pictureCount;
|
||||||
|
late final int idx;
|
||||||
|
|
||||||
|
PkzVolume.fromJson(Map<String, dynamic> 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<String, dynamic> toJson() {
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_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<PkzPicture> pictures;
|
||||||
|
late final int pictureCount;
|
||||||
|
late final int idx;
|
||||||
|
|
||||||
|
PkzChapter.fromJson(Map<String, dynamic> 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<String, dynamic> toJson() {
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_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<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
title = json['title'];
|
||||||
|
width = json['width'];
|
||||||
|
height = json['height'];
|
||||||
|
format = json['format'];
|
||||||
|
picturePath = json['picture_path'];
|
||||||
|
idx = json['idx'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_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<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() {
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -560,6 +560,19 @@ class Method {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 导出下载的图片到PKZ
|
||||||
|
Future<dynamic> exportComicDownloadToPkz(
|
||||||
|
List<String> comicIds,
|
||||||
|
String dir,
|
||||||
|
String name,
|
||||||
|
) {
|
||||||
|
return _flatInvoke("exportComicDownloadToPkz", {
|
||||||
|
"comicIds": comicIds,
|
||||||
|
"dir": dir,
|
||||||
|
"name": name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// 使用网络将下载传输到其他设备
|
/// 使用网络将下载传输到其他设备
|
||||||
Future<int> exportComicUsingSocket(String comicId) async {
|
Future<int> exportComicUsingSocket(String comicId) async {
|
||||||
return int.parse(await _flatInvoke("exportComicUsingSocket", comicId));
|
return int.parse(await _flatInvoke("exportComicUsingSocket", comicId));
|
||||||
|
@ -721,4 +734,89 @@ class Method {
|
||||||
Future<bool> verifyAuthentication() async {
|
Future<bool> verifyAuthentication() async {
|
||||||
return await _channel.invokeMethod("verifyAuthentication");
|
return await _channel.invokeMethod("verifyAuthentication");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<PkzArchive> pkzInfo(String pkzPath) async {
|
||||||
|
return PkzArchive.fromJson(
|
||||||
|
jsonDecode(await _flatInvoke("pkzInfo", pkzPath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uint8List> loadPkzFile(String pkzPath, String path) async {
|
||||||
|
return base64Decode(await _flatInvoke("loadPkzFile", {
|
||||||
|
"pkzPath": pkzPath,
|
||||||
|
"path": path,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<PkzComicViewLog>> pkzComicViewLogs(
|
||||||
|
String fileName,
|
||||||
|
String comicId,
|
||||||
|
) async {
|
||||||
|
return List.of(jsonDecode(await _flatInvoke("pkzComicViewLogs", fileName)))
|
||||||
|
.map((e) => PkzComicViewLog.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PkzComicViewLog?> 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<List<Knight>> leaderboardOfKnight() async {
|
||||||
|
return List.of(jsonDecode(await _flatInvoke("leaderboardOfKnight", "")))
|
||||||
|
.map((e) => Knight.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pikapika/basic/Common.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/RegisterScreen.dart';
|
||||||
import 'package:pikapika/screens/SettingsScreen.dart';
|
import 'package:pikapika/screens/SettingsScreen.dart';
|
||||||
import 'package:pikapika/screens/components/NetworkSetting.dart';
|
import 'package:pikapika/screens/components/NetworkSetting.dart';
|
||||||
|
import 'package:uri_to_file/uri_to_file.dart';
|
||||||
|
|
||||||
|
import '../basic/Navigator.dart';
|
||||||
import 'AppScreen.dart';
|
import 'AppScreen.dart';
|
||||||
import 'DownloadListScreen.dart';
|
import 'DownloadListScreen.dart';
|
||||||
|
import 'PkzArchiveScreen.dart';
|
||||||
import 'ThemeScreen.dart';
|
import 'ThemeScreen.dart';
|
||||||
import 'components/ContentLoading.dart';
|
import 'components/ContentLoading.dart';
|
||||||
|
|
||||||
|
@ -25,13 +32,34 @@ class _AccountScreenState extends State<AccountScreen> {
|
||||||
late bool _logging = false;
|
late bool _logging = false;
|
||||||
late String _username = "";
|
late String _username = "";
|
||||||
late String _password = "";
|
late String _password = "";
|
||||||
|
late StreamSubscription<Uri> _linkSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
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();
|
_loadProperties();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_linkSubscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future _loadProperties() async {
|
Future _loadProperties() async {
|
||||||
var username = await method.getUsername();
|
var username = await method.getUsername();
|
||||||
var password = await method.getPassword();
|
var password = await method.getPassword();
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pikapika/basic/config/Version.dart';
|
import 'package:pikapika/basic/config/Version.dart';
|
||||||
import 'package:pikapika/screens/components/Badge.dart';
|
import 'package:pikapika/screens/components/Badge.dart';
|
||||||
|
import 'package:uri_to_file/uri_to_file.dart';
|
||||||
|
|
||||||
import 'CategoriesScreen.dart';
|
import 'CategoriesScreen.dart';
|
||||||
|
import 'PkzArchiveScreen.dart';
|
||||||
import 'SpaceScreen.dart';
|
import 'SpaceScreen.dart';
|
||||||
|
|
||||||
// MAIN UI 底部导航栏
|
// MAIN UI 底部导航栏
|
||||||
|
@ -14,15 +20,32 @@ class AppScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppScreenState extends State<AppScreen> {
|
class _AppScreenState extends State<AppScreen> {
|
||||||
|
late StreamSubscription<Uri> _linkSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
versionEvent.subscribe(_onVersion);
|
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();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
versionEvent.unsubscribe(_onVersion);
|
versionEvent.unsubscribe(_onVersion);
|
||||||
|
_linkSubscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_search_bar/flutter_search_bar.dart';
|
import 'package:flutter_search_bar/flutter_search_bar.dart';
|
||||||
import 'package:pikapika/basic/Entities.dart';
|
import 'package:pikapika/basic/Entities.dart';
|
||||||
import 'package:pikapika/basic/config/ShadowCategoriesEvent.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/store/Categories.dart';
|
||||||
import 'package:pikapika/basic/config/ShadowCategories.dart';
|
import 'package:pikapika/basic/config/ShadowCategories.dart';
|
||||||
import 'package:pikapika/screens/ComicCollectionsScreen.dart';
|
import 'package:pikapika/screens/ComicCollectionsScreen.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_search_bar/flutter_search_bar.dart';
|
import 'package:flutter_search_bar/flutter_search_bar.dart';
|
||||||
import 'package:pikapika/basic/Common.dart';
|
import 'package:pikapika/basic/Common.dart';
|
||||||
import 'package:pikapika/basic/config/ShadowCategories.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/store/Categories.dart';
|
||||||
import 'package:pikapika/basic/config/ListLayout.dart';
|
import 'package:pikapika/basic/config/ListLayout.dart';
|
||||||
import 'package:pikapika/basic/Method.dart';
|
import 'package:pikapika/basic/Method.dart';
|
||||||
|
|
|
@ -185,6 +185,59 @@ class _DownloadExportToFileScreenState
|
||||||
child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'),
|
child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'),
|
||||||
));
|
));
|
||||||
widgets.add(Container(height: 10));
|
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(
|
widgets.add(MaterialButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
late String? path;
|
late String? path;
|
||||||
|
|
|
@ -76,7 +76,7 @@ class _DownloadReaderScreenState extends State<DownloadReaderScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<dynamic> _onDownload() async {
|
FutureOr<dynamic> _onDownload() async {
|
||||||
defaultToast(context, "您已经在下载阅读");
|
defaultToast(context, "您阅读的是下载漫画");
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<dynamic> _onChangeEp(int epOrder) {
|
FutureOr<dynamic> _onChangeEp(int epOrder) {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pikapika/basic/config/Address.dart';
|
import 'package:pikapika/basic/config/Address.dart';
|
||||||
import 'package:pikapika/basic/config/AndroidDisplayMode.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/UsingRightClickPop.dart';
|
||||||
import 'package:pikapika/basic/config/Version.dart';
|
import 'package:pikapika/basic/config/Version.dart';
|
||||||
import 'package:pikapika/basic/config/VolumeController.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 '../basic/config/ExportRename.dart';
|
||||||
import 'AccountScreen.dart';
|
import 'AccountScreen.dart';
|
||||||
import 'AppScreen.dart';
|
import 'AppScreen.dart';
|
||||||
|
@ -88,6 +93,32 @@ class _InitScreenState extends State<InitScreen> {
|
||||||
await initUsingRightClickPop();
|
await initUsingRightClickPop();
|
||||||
await initAuthentication();
|
await initAuthentication();
|
||||||
autoCheckNewVersion();
|
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(() {
|
setState(() {
|
||||||
_authenticating = currentAuthentication();
|
_authenticating = currentAuthentication();
|
||||||
});
|
});
|
||||||
|
@ -112,7 +143,8 @@ class _InitScreenState extends State<InitScreen> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_goAuthentication();
|
_goAuthentication();
|
||||||
},
|
},
|
||||||
child: const Text('您在之前使用APP时开启了身份验证, 请点这段文字进行身份核查, 核查通过后将会进入APP'),
|
child:
|
||||||
|
const Text('您在之前使用APP时开启了身份验证, 请点这段文字进行身份核查, 核查通过后将会进入APP'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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<StatefulWidget> createState() => _PkzArchiveScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PkzArchiveScreenState extends State<PkzArchiveScreen> with RouteAware {
|
||||||
|
Map<String, PkzComicViewLog> _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(),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<StatefulWidget> createState() => _PkzComicInfoScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PkzComicInfoScreenState extends State<PkzComicInfoScreen>
|
||||||
|
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<Widget> 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<String, PkzChapter> 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,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<PkzChapter> 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<StatefulWidget> createState() => _PkzReaderScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PkzReaderScreenState extends State<PkzReaderScreen> {
|
||||||
|
late PkzChapter _ep;
|
||||||
|
late int _epOrder;
|
||||||
|
late bool _fullScreen = false;
|
||||||
|
late List<PkzPicture> 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<dynamic> _onDownload() async {
|
||||||
|
defaultToast(context, "您阅读的是下载漫画");
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<dynamic> _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<dynamic> _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<dynamic> 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 = <int, String>{};
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ import 'package:pikapika/basic/config/Themes.dart';
|
||||||
import 'package:pikapika/basic/config/TimeOffsetHour.dart';
|
import 'package:pikapika/basic/config/TimeOffsetHour.dart';
|
||||||
import 'package:pikapika/basic/config/Version.dart';
|
import 'package:pikapika/basic/config/Version.dart';
|
||||||
import 'package:pikapika/basic/config/VolumeController.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/NetworkSetting.dart';
|
||||||
import 'package:pikapika/screens/components/RightClickPop.dart';
|
import 'package:pikapika/screens/components/RightClickPop.dart';
|
||||||
|
|
||||||
|
|
|
@ -367,3 +367,8 @@ final authorStyle = TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Colors.pink.shade300,
|
color: Colors.pink.shade300,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final authorStyleX = TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.pink.shade300.withOpacity(.7),
|
||||||
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:pikapika/basic/Entities.dart';
|
||||||
import 'package:pikapika/basic/Method.dart';
|
import 'package:pikapika/basic/Method.dart';
|
||||||
import 'package:pikapika/basic/config/ShadowCategories.dart';
|
import 'package:pikapika/basic/config/ShadowCategories.dart';
|
||||||
import 'package:pikapika/basic/config/ListLayout.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 'ComicInfoCard.dart';
|
||||||
import 'Images.dart';
|
import 'Images.dart';
|
||||||
|
|
|
@ -21,6 +21,7 @@ import 'package:pikapika/basic/config/ReaderDirection.dart';
|
||||||
import 'package:pikapika/basic/config/ReaderSliderPosition.dart';
|
import 'package:pikapika/basic/config/ReaderSliderPosition.dart';
|
||||||
import 'package:pikapika/basic/config/ReaderType.dart';
|
import 'package:pikapika/basic/config/ReaderType.dart';
|
||||||
import 'package:pikapika/basic/config/VolumeController.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 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import '../FilePhotoViewScreen.dart';
|
import '../FilePhotoViewScreen.dart';
|
||||||
import 'gesture_zoom_box.dart';
|
import 'gesture_zoom_box.dart';
|
||||||
|
@ -93,6 +94,13 @@ void delVolumeListen() {
|
||||||
|
|
||||||
// 对Reader的传参以及封装
|
// 对Reader的传参以及封装
|
||||||
|
|
||||||
|
class PkzFile {
|
||||||
|
final String pkzPath;
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
PkzFile(this.pkzPath, this.path);
|
||||||
|
}
|
||||||
|
|
||||||
class ReaderImageInfo {
|
class ReaderImageInfo {
|
||||||
final String fileServer;
|
final String fileServer;
|
||||||
final String path;
|
final String path;
|
||||||
|
@ -101,9 +109,18 @@ class ReaderImageInfo {
|
||||||
final int? height;
|
final int? height;
|
||||||
final String? format;
|
final String? format;
|
||||||
final int? fileSize;
|
final int? fileSize;
|
||||||
|
final PkzFile? pkzFile;
|
||||||
|
|
||||||
ReaderImageInfo(this.fileServer, this.path, this.downloadLocalPath,
|
ReaderImageInfo(
|
||||||
this.width, this.height, this.format, this.fileSize);
|
this.fileServer,
|
||||||
|
this.path,
|
||||||
|
this.downloadLocalPath,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.format,
|
||||||
|
this.fileSize, {
|
||||||
|
this.pkzFile,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageReaderStruct {
|
class ImageReaderStruct {
|
||||||
|
@ -930,7 +947,13 @@ class _WebToonReaderState extends _ImageReaderContentState {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
for (var e in widget.struct.images) {
|
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()));
|
_trueSizes.add(Size(e.width!.toDouble(), e.height!.toDouble()));
|
||||||
} else {
|
} else {
|
||||||
_trueSizes.add(null);
|
_trueSizes.add(null);
|
||||||
|
@ -1030,7 +1053,16 @@ class _WebToonReaderState extends _ImageReaderContentState {
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = widget.struct.images[index];
|
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(
|
_images.add(_WebToonDownloadImage(
|
||||||
fileServer: e.fileServer,
|
fileServer: e.fileServer,
|
||||||
path: e.path,
|
path: e.path,
|
||||||
|
@ -1147,6 +1179,36 @@ 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 {
|
class _WebToonRemoteImage extends _WebToonReaderImage {
|
||||||
final String fileServer;
|
final String fileServer;
|
||||||
|
@ -1257,7 +1319,13 @@ class _ListViewReaderState extends _ImageReaderContentState
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
for (var e in widget.struct.images) {
|
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()));
|
_trueSizes.add(Size(e.width!.toDouble(), e.height!.toDouble()));
|
||||||
} else {
|
} else {
|
||||||
_trueSizes.add(null);
|
_trueSizes.add(null);
|
||||||
|
@ -1332,7 +1400,16 @@ class _ListViewReaderState extends _ImageReaderContentState
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = widget.struct.images[index];
|
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(
|
_images.add(_WebToonDownloadImage(
|
||||||
fileServer: e.fileServer,
|
fileServer: e.fileServer,
|
||||||
path: e.path,
|
path: e.path,
|
||||||
|
@ -1499,6 +1576,22 @@ class _GalleryReaderState extends _ImageReaderContentState {
|
||||||
itemCount: widget.struct.images.length,
|
itemCount: widget.struct.images.length,
|
||||||
builder: (BuildContext context, int index) {
|
builder: (BuildContext context, int index) {
|
||||||
var item = widget.struct.images[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) {
|
if (item.downloadLocalPath != null) {
|
||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
imageProvider:
|
imageProvider:
|
||||||
|
@ -1535,6 +1628,11 @@ class _GalleryReaderState extends _ImageReaderContentState {
|
||||||
child: gallery,
|
child: gallery,
|
||||||
onLongPress: () async {
|
onLongPress: () async {
|
||||||
if (_current >= 0 && _current < widget.struct.images.length) {
|
if (_current >= 0 && _current < widget.struct.images.length) {
|
||||||
|
var item = widget.struct.images[_current];
|
||||||
|
if (item.pkzFile != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> load() async {
|
Future<String> load() async {
|
||||||
var item = widget.struct.images[_current];
|
var item = widget.struct.images[_current];
|
||||||
if (item.downloadLocalPath != null) {
|
if (item.downloadLocalPath != null) {
|
||||||
|
|
|
@ -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<StatefulWidget> createState() => _ComicInfoCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ComicInfoCard extends State<PkzComicInfoCard> {
|
||||||
|
@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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<PkzImageProvider> {
|
||||||
|
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<PkzImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||||
|
return SynchronousFuture<PkzImageProvider>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ui.Codec> _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<StatefulWidget> createState() => _PkzImageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PkzImageState extends State<PkzImage> {
|
||||||
|
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<StatefulWidget> createState() => _PkzLoadingImageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PkzLoadingImageState extends State<PkzLoadingImage> {
|
||||||
|
|
||||||
|
late bool _mock;
|
||||||
|
late Future<Uint8List> 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,10 @@
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import app_links_macos
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
|
52
pubspec.lock
52
pubspec.lock
|
@ -8,6 +8,41 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1+2"
|
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:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -171,7 +206,7 @@ packages:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -300,7 +335,7 @@ packages:
|
||||||
name: modal_bottom_sheet
|
name: modal_bottom_sheet
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.1.0"
|
||||||
multi_select_flutter:
|
multi_select_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -309,7 +344,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -432,13 +467,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
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:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.3"
|
version: "6.1.4"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -511,4 +553,4 @@ packages:
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.17.0 <3.0.0"
|
dart: ">=2.17.0 <3.0.0"
|
||||||
flutter: ">=2.11.0-0.1.pre"
|
flutter: ">=3.0.0"
|
||||||
|
|
|
@ -47,6 +47,9 @@ dependencies:
|
||||||
file_picker: ^4.5.1
|
file_picker: ^4.5.1
|
||||||
crop_image: ^1.0.2
|
crop_image: ^1.0.2
|
||||||
image: ^3.1.3
|
image: ^3.1.3
|
||||||
|
path: ^1.8.0
|
||||||
|
app_links: ^3.2.0
|
||||||
|
uri_to_file: ^0.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -6,9 +6,12 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <app_links_windows/app_links_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
AppLinksWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("AppLinksWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
app_links_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue