add loginWithWebview, mixed explore page, app links, html node api;

improve ui
This commit is contained in:
nyne
2024-10-17 12:27:20 +08:00
parent d01d0b5ddb
commit 6c8a7d62a6
28 changed files with 686 additions and 199 deletions

View File

@@ -88,7 +88,8 @@ class RandomCategoryPart extends BaseCategoryPart {
if (randomNumber >= tags.length) {
return tags;
}
return tags.sublist(math.Random().nextInt(tags.length - randomNumber));
var start = math.Random().nextInt(tags.length - randomNumber);
return tags.sublist(start, start + randomNumber);
}
@override

View File

@@ -197,6 +197,8 @@ class ComicSource {
final HandleClickTagEvent? handleClickTagEvent;
final LinkHandler? linkHandler;
Future<void> loadData() async {
var file = File("${App.dataPath}/comic_source/$key.data");
if (await file.exists()) {
@@ -261,14 +263,13 @@ class ComicSource {
this.idMatcher,
this.translations,
this.handleClickTagEvent,
this.linkHandler,
);
}
class AccountConfig {
final LoginFunction? login;
final FutureOr<void> Function(BuildContext)? onLogin;
final String? loginWebsite;
final String? registerWebsite;
@@ -279,10 +280,15 @@ class AccountConfig {
final List<AccountInfoItem> infoItems;
final bool Function(String url, String title)? checkLoginStatus;
const AccountConfig(
this.login, this.loginWebsite, this.registerWebsite, this.logout,
{this.onLogin})
: allowReLogin = true,
this.login,
this.loginWebsite,
this.registerWebsite,
this.logout,
this.checkLoginStatus,
) : allowReLogin = true,
infoItems = const [];
}
@@ -315,11 +321,13 @@ class ExplorePageData {
/// return a `List` contains `List<Comic>` or `ExplorePagePart`
final Future<Res<List<Object>>> Function(int index)? loadMixed;
final WidgetBuilder? overridePageBuilder;
ExplorePageData(this.title, this.type, this.loadPage, this.loadMultiPart)
: loadMixed = null,
overridePageBuilder = null;
ExplorePageData(
this.title,
this.type,
this.loadPage,
this.loadMultiPart,
this.loadMixed,
);
}
class ExplorePagePart {
@@ -422,3 +430,12 @@ class CategoryComicsOptions {
const CategoryComicsOptions(this.options, this.notShowWhen, this.showWhen);
}
class LinkHandler {
final List<String> domains;
final String? Function(String url) linkToId;
const LinkHandler(this.domains, this.linkToId);
}

View File

@@ -11,11 +11,23 @@ class Comment {
final bool? isLiked;
final int? voteStatus; // 1: upvote, -1: downvote, 0: none
static String? parseTime(dynamic value) {
if(value == null) return null;
if(value is int) {
if(value < 10000000000) {
return DateTime.fromMillisecondsSinceEpoch(value * 1000).toString().substring(0, 19);
} else {
return DateTime.fromMillisecondsSinceEpoch(value).toString().substring(0, 19);
}
}
return value.toString();
}
Comment.fromJson(Map<String, dynamic> json)
: userName = json["userName"],
avatar = json["avatar"],
content = json["content"],
time = json["time"],
time = parseTime(json["time"]),
replyCount = json["replyCount"],
id = json["id"].toString(),
score = json["score"],
@@ -40,8 +52,19 @@ class Comic {
final int? maxPage;
const Comic(this.title, this.cover, this.id, this.subtitle, this.tags,
this.description, this.sourceKey, this.maxPage);
final String? language;
const Comic(
this.title,
this.cover,
this.id,
this.subtitle,
this.tags,
this.description,
this.sourceKey,
this.maxPage,
this.language,
);
Map<String, dynamic> toJson() {
return {
@@ -53,6 +76,7 @@ class Comic {
"description": description,
"sourceKey": sourceKey,
"maxPage": maxPage,
"language": language,
};
}
@@ -63,7 +87,8 @@ class Comic {
id = json["id"],
tags = List<String>.from(json["tags"] ?? []),
description = json["description"] ?? "",
maxPage = json["maxPage"];
maxPage = json["maxPage"],
language = json["language"];
}
class ComicDetails with HistoryMixin {
@@ -109,7 +134,7 @@ class ComicDetails with HistoryMixin {
final String? url;
static Map<String, List<String>> _generateMap(Map<String, dynamic> map) {
static Map<String, List<String>> _generateMap(Map<dynamic, dynamic> map) {
var res = <String, List<String>>{};
map.forEach((key, value) {
res[key] = List<String>.from(value);

View File

@@ -147,6 +147,7 @@ class ComicSourceParser {
_parseIdMatch(),
_parseTranslation(),
_parseClickTagEvent(),
_parseLinkHandler(),
);
await source.loadData();
@@ -199,8 +200,28 @@ class ComicSourceParser {
JsEngine().runCode("ComicSource.sources.$_key.account.logout()");
}
return AccountConfig(login, _getValue("account.login.website"),
_getValue("account.registerWebsite"), logout);
if(!_checkExists('account.loginWithWebview')) {
return AccountConfig(
login,
null,
_getValue("account.registerWebsite"),
logout,
null,
);
} else {
return AccountConfig(
null,
_getValue("account.loginWithWebview.url"),
_getValue("account.registerWebsite"),
logout,
(url, title) {
return JsEngine().runCode("""
ComicSource.sources.$_key.account.loginWithWebview.checkStatus(
${jsonEncode(url)}, ${jsonEncode(title)})
""");
},
);
}
}
List<ExplorePageData> _loadExploreData() {
@@ -214,6 +235,7 @@ class ComicSourceParser {
final String type = _getValue("explore[$i].type");
Future<Res<List<ExplorePagePart>>> Function()? loadMultiPart;
Future<Res<List<Comic>>> Function(int page)? loadPage;
Future<Res<List<Object>>> Function(int index)? loadMixed;
if (type == "singlePageWithMultiPart") {
loadMultiPart = () async {
try {
@@ -246,18 +268,69 @@ class ComicSourceParser {
return Res.error(e.toString());
}
};
} else if (type == "multiPartPage") {
loadMultiPart = () async {
try {
var res = await JsEngine()
.runCode("ComicSource.sources.$_key.explore[$i].load()");
return Res(
List.from(
(res as List).map((e) {
return ExplorePagePart(
e['title'],
(e['comics'] as List).map((e) {
return Comic.fromJson(e, _key!);
}).toList(),
e['viewMore'],
);
}),
),
);
} catch (e, s) {
Log.error("Data Analysis", "$e\n$s");
return Res.error(e.toString());
}
};
} else if (type == 'mixed') {
loadMixed = (index) async {
try {
var res = await JsEngine().runCode(
"ComicSource.sources.$_key.explore[$i].load(${jsonEncode(index)})");
var list = <Object>[];
for (var data in (res['data'] as List)) {
if (data is List) {
list.add(data.map((e) => Comic.fromJson(e, _key!)).toList());
} else if (data is Map) {
list.add(ExplorePagePart(
data['title'],
(data['comics'] as List).map((e) {
return Comic.fromJson(e, _key!);
}).toList(),
data['viewMore'],
));
}
}
return Res(list, subData: res['maxPage']);
} catch (e, s) {
Log.error("Network", "$e\n$s");
return Res.error(e.toString());
}
};
}
pages.add(ExplorePageData(
title,
switch (type) {
"singlePageWithMultiPart" =>
ExplorePageType.singlePageWithMultiPart,
"multiPageComicList" => ExplorePageType.multiPageComicList,
_ =>
throw ComicSourceParseException("Unknown explore page type $type")
},
loadPage,
loadMultiPart));
title,
switch (type) {
"singlePageWithMultiPart" => ExplorePageType.singlePageWithMultiPart,
"multiPartPage" => ExplorePageType.singlePageWithMultiPart,
"multiPageComicList" => ExplorePageType.multiPageComicList,
"mixed" => ExplorePageType.mixed,
_ =>
throw ComicSourceParseException("Unknown explore page type $type")
},
loadPage,
loadMultiPart,
loadMixed,
));
}
return pages;
}
@@ -279,8 +352,11 @@ class ComicSourceParser {
final String type = c["type"];
final List<String> tags = List.from(c["categories"]);
final String itemType = c["itemType"];
final List<String>? categoryParams =
c["categoryParams"] == null ? null : List.from(c["categoryParams"]);
List<String>? categoryParams = ListOrNull.from(c["categoryParams"]);
final String? groupParam = c["groupParam"];
if (groupParam != null) {
categoryParams = List.filled(tags.length, groupParam);
}
if (type == "fixed") {
categoryParts
.add(FixedCategoryPart(name, tags, itemType, categoryParams));
@@ -407,6 +483,7 @@ class ComicSourceParser {
if (res is! Map<String, dynamic>) throw "Invalid data";
res['comicId'] = id;
res['sourceKey'] = _key;
JsEngine().clearHtml();
return Res(ComicDetails.fromJson(res));
} catch (e, s) {
Log.error("Network", "$e\n$s");
@@ -728,4 +805,18 @@ class ComicSourceParser {
return Map.from(r);
};
}
LinkHandler? _parseLinkHandler() {
if (!_checkExists("linkHandler")) {
return null;
}
List<String> domains = List.from(_getValue("link.domains"));
linkToId(String link) {
var res = JsEngine().runCode("""
ComicSource.sources.$_key.link.linkToId(${jsonEncode(link)})
""");
return res as String?;
}
return LinkHandler(domains, linkToId);
}
}