mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
Compare commits
4 Commits
dfee65c3af
...
4129349c70
Author | SHA1 | Date | |
---|---|---|---|
4129349c70 | |||
77a9aa5457 | |||
97940b9492 | |||
7945c0e54f |
@@ -1428,14 +1428,14 @@ function getClipboard() {
|
|||||||
/**
|
/**
|
||||||
* Compute a function with arguments. The function will be executed in the engine pool which is not in the main thread.
|
* Compute a function with arguments. The function will be executed in the engine pool which is not in the main thread.
|
||||||
* @param func {string} - A js code string which can be evaluated to a function. The function will receive the args as its only argument.
|
* @param func {string} - A js code string which can be evaluated to a function. The function will receive the args as its only argument.
|
||||||
* @param args {any[] | null | undefined} - The arguments to pass to the function.
|
* @param args {any[]} - The arguments to pass to the function.
|
||||||
* @returns {Promise<any>} - The result of the function.
|
* @returns {Promise<any>} - The result of the function.
|
||||||
* @since 1.5.0
|
* @since 1.5.0
|
||||||
*/
|
*/
|
||||||
function compute(func, args) {
|
function compute(func, ...args) {
|
||||||
return sendMessage({
|
return sendMessage({
|
||||||
method: 'compute',
|
method: 'compute',
|
||||||
function: func,
|
function: func,
|
||||||
args: args
|
args: args
|
||||||
})
|
})
|
||||||
}
|
}
|
@@ -13,7 +13,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.4.6";
|
final version = "1.5.0";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
|
@@ -401,9 +401,14 @@ class SearchOptions {
|
|||||||
typedef CategoryComicsLoader = Future<Res<List<Comic>>> Function(
|
typedef CategoryComicsLoader = Future<Res<List<Comic>>> Function(
|
||||||
String category, String? param, List<String> options, int page);
|
String category, String? param, List<String> options, int page);
|
||||||
|
|
||||||
|
typedef CategoryOptionsLoader = Future<Res<List<CategoryComicsOptions>>> Function(
|
||||||
|
String category, String? param);
|
||||||
|
|
||||||
class CategoryComicsData {
|
class CategoryComicsData {
|
||||||
/// options
|
/// options
|
||||||
final List<CategoryComicsOptions> options;
|
final List<CategoryComicsOptions>? options;
|
||||||
|
|
||||||
|
final CategoryOptionsLoader? optionsLoader;
|
||||||
|
|
||||||
/// [category] is the one clicked by the user on the category page.
|
/// [category] is the one clicked by the user on the category page.
|
||||||
///
|
///
|
||||||
@@ -414,7 +419,7 @@ class CategoryComicsData {
|
|||||||
|
|
||||||
final RankingData? rankingData;
|
final RankingData? rankingData;
|
||||||
|
|
||||||
const CategoryComicsData(this.options, this.load, {this.rankingData});
|
const CategoryComicsData({this.options, this.optionsLoader, required this.load, this.rankingData});
|
||||||
}
|
}
|
||||||
|
|
||||||
class RankingData {
|
class RankingData {
|
||||||
@@ -429,6 +434,9 @@ class RankingData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CategoryComicsOptions {
|
class CategoryComicsOptions {
|
||||||
|
// The label will not be displayed if it is empty.
|
||||||
|
final String label;
|
||||||
|
|
||||||
/// Use a [LinkedHashMap] to describe an option list.
|
/// Use a [LinkedHashMap] to describe an option list.
|
||||||
/// key is for loading comics, value is the name displayed on screen.
|
/// key is for loading comics, value is the name displayed on screen.
|
||||||
/// Default value will be the first of the Map.
|
/// Default value will be the first of the Map.
|
||||||
@@ -439,7 +447,7 @@ class CategoryComicsOptions {
|
|||||||
|
|
||||||
final List<String>? showWhen;
|
final List<String>? showWhen;
|
||||||
|
|
||||||
const CategoryComicsOptions(this.options, this.notShowWhen, this.showWhen);
|
const CategoryComicsOptions(this.label, this.options, this.notShowWhen, this.showWhen);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LinkHandler {
|
class LinkHandler {
|
||||||
|
@@ -64,8 +64,13 @@ class ComicSourceParser {
|
|||||||
if (file.existsSync()) {
|
if (file.existsSync()) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (file.existsSync()) {
|
while (file.existsSync()) {
|
||||||
file = File(FilePath.join(App.dataPath, "comic_source",
|
file = File(
|
||||||
"${fileName.split('.').first}($i).js"));
|
FilePath.join(
|
||||||
|
App.dataPath,
|
||||||
|
"comic_source",
|
||||||
|
"${fileName.split('.').first}($i).js",
|
||||||
|
),
|
||||||
|
);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,8 +85,9 @@ class ComicSourceParser {
|
|||||||
|
|
||||||
Future<ComicSource> parse(String js, String filePath) async {
|
Future<ComicSource> parse(String js, String filePath) async {
|
||||||
js = js.replaceAll("\r\n", "\n");
|
js = js.replaceAll("\r\n", "\n");
|
||||||
var line1 =
|
var line1 = js
|
||||||
js.split('\n').firstWhereOrNull((e) => e.trim().startsWith("class "));
|
.split('\n')
|
||||||
|
.firstWhereOrNull((e) => e.trim().startsWith("class "));
|
||||||
if (line1 == null ||
|
if (line1 == null ||
|
||||||
!line1.startsWith("class ") ||
|
!line1.startsWith("class ") ||
|
||||||
!line1.contains("extends ComicSource")) {
|
!line1.contains("extends ComicSource")) {
|
||||||
@@ -93,19 +99,23 @@ class ComicSourceParser {
|
|||||||
this['temp'] = new $className()
|
this['temp'] = new $className()
|
||||||
}).call()
|
}).call()
|
||||||
""", className);
|
""", className);
|
||||||
_name = JsEngine().runCode("this['temp'].name") ??
|
_name =
|
||||||
|
JsEngine().runCode("this['temp'].name") ??
|
||||||
(throw ComicSourceParseException('name is required'));
|
(throw ComicSourceParseException('name is required'));
|
||||||
var key = JsEngine().runCode("this['temp'].key") ??
|
var key =
|
||||||
|
JsEngine().runCode("this['temp'].key") ??
|
||||||
(throw ComicSourceParseException('key is required'));
|
(throw ComicSourceParseException('key is required'));
|
||||||
var version = JsEngine().runCode("this['temp'].version") ??
|
var version =
|
||||||
|
JsEngine().runCode("this['temp'].version") ??
|
||||||
(throw ComicSourceParseException('version is required'));
|
(throw ComicSourceParseException('version is required'));
|
||||||
var minAppVersion = JsEngine().runCode("this['temp'].minAppVersion");
|
var minAppVersion = JsEngine().runCode("this['temp'].minAppVersion");
|
||||||
var url = JsEngine().runCode("this['temp'].url");
|
var url = JsEngine().runCode("this['temp'].url");
|
||||||
if (minAppVersion != null) {
|
if (minAppVersion != null) {
|
||||||
if (compareSemVer(minAppVersion, App.version.split('-').first)) {
|
if (compareSemVer(minAppVersion, App.version.split('-').first)) {
|
||||||
throw ComicSourceParseException(
|
throw ComicSourceParseException(
|
||||||
"minAppVersion @version is required"
|
"minAppVersion @version is required".tlParams({
|
||||||
.tlParams({"version": minAppVersion}),
|
"version": minAppVersion,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,8 +184,10 @@ class ComicSourceParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _checkExists(String index) {
|
bool _checkExists(String index) {
|
||||||
return JsEngine().runCode("ComicSource.sources.$_key.$index !== null "
|
return JsEngine().runCode(
|
||||||
"&& ComicSource.sources.$_key.$index !== undefined");
|
"ComicSource.sources.$_key.$index !== null "
|
||||||
|
"&& ComicSource.sources.$_key.$index !== undefined",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic _getValue(String index) {
|
dynamic _getValue(String index) {
|
||||||
@@ -276,16 +288,24 @@ class ComicSourceParser {
|
|||||||
if (type == "singlePageWithMultiPart") {
|
if (type == "singlePageWithMultiPart") {
|
||||||
loadMultiPart = () async {
|
loadMultiPart = () async {
|
||||||
try {
|
try {
|
||||||
var res = await JsEngine()
|
var res = await JsEngine().runCode(
|
||||||
.runCode("ComicSource.sources.$_key.explore[$i].load()");
|
"ComicSource.sources.$_key.explore[$i].load()",
|
||||||
return Res(List.from(res.keys
|
);
|
||||||
.map((e) => ExplorePagePart(
|
return Res(
|
||||||
e,
|
List.from(
|
||||||
(res[e] as List)
|
res.keys
|
||||||
.map<Comic>((e) => Comic.fromJson(e, _key!))
|
.map(
|
||||||
.toList(),
|
(e) => ExplorePagePart(
|
||||||
null))
|
e,
|
||||||
.toList()));
|
(res[e] as List)
|
||||||
|
.map<Comic>((e) => Comic.fromJson(e, _key!))
|
||||||
|
.toList(),
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Data Analysis", "$e\n$s");
|
Log.error("Data Analysis", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
@@ -296,11 +316,15 @@ class ComicSourceParser {
|
|||||||
loadPage = (int page) async {
|
loadPage = (int page) async {
|
||||||
try {
|
try {
|
||||||
var res = await JsEngine().runCode(
|
var res = await JsEngine().runCode(
|
||||||
"ComicSource.sources.$_key.explore[$i].load(${jsonEncode(page)})");
|
"ComicSource.sources.$_key.explore[$i].load(${jsonEncode(page)})",
|
||||||
|
);
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
subData: res["maxPage"]);
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
|
subData: res["maxPage"],
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Network", "$e\n$s");
|
Log.error("Network", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
@@ -310,10 +334,13 @@ class ComicSourceParser {
|
|||||||
loadNext = (next) async {
|
loadNext = (next) async {
|
||||||
try {
|
try {
|
||||||
var res = await JsEngine().runCode(
|
var res = await JsEngine().runCode(
|
||||||
"ComicSource.sources.$_key.explore[$i].loadNext(${jsonEncode(next)})");
|
"ComicSource.sources.$_key.explore[$i].loadNext(${jsonEncode(next)})",
|
||||||
|
);
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
subData: res["next"],
|
subData: res["next"],
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -325,8 +352,9 @@ class ComicSourceParser {
|
|||||||
} else if (type == "multiPartPage") {
|
} else if (type == "multiPartPage") {
|
||||||
loadMultiPart = () async {
|
loadMultiPart = () async {
|
||||||
try {
|
try {
|
||||||
var res = await JsEngine()
|
var res = await JsEngine().runCode(
|
||||||
.runCode("ComicSource.sources.$_key.explore[$i].load()");
|
"ComicSource.sources.$_key.explore[$i].load()",
|
||||||
|
);
|
||||||
return Res(
|
return Res(
|
||||||
List.from(
|
List.from(
|
||||||
(res as List).map((e) {
|
(res as List).map((e) {
|
||||||
@@ -349,19 +377,22 @@ class ComicSourceParser {
|
|||||||
loadMixed = (index) async {
|
loadMixed = (index) async {
|
||||||
try {
|
try {
|
||||||
var res = await JsEngine().runCode(
|
var res = await JsEngine().runCode(
|
||||||
"ComicSource.sources.$_key.explore[$i].load(${jsonEncode(index)})");
|
"ComicSource.sources.$_key.explore[$i].load(${jsonEncode(index)})",
|
||||||
|
);
|
||||||
var list = <Object>[];
|
var list = <Object>[];
|
||||||
for (var data in (res['data'] as List)) {
|
for (var data in (res['data'] as List)) {
|
||||||
if (data is List) {
|
if (data is List) {
|
||||||
list.add(data.map((e) => Comic.fromJson(e, _key!)).toList());
|
list.add(data.map((e) => Comic.fromJson(e, _key!)).toList());
|
||||||
} else if (data is Map) {
|
} else if (data is Map) {
|
||||||
list.add(ExplorePagePart(
|
list.add(
|
||||||
data['title'],
|
ExplorePagePart(
|
||||||
(data['comics'] as List).map((e) {
|
data['title'],
|
||||||
return Comic.fromJson(e, _key!);
|
(data['comics'] as List).map((e) {
|
||||||
}).toList(),
|
return Comic.fromJson(e, _key!);
|
||||||
data['viewMore'],
|
}).toList(),
|
||||||
));
|
data['viewMore'],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Res(list, subData: res['maxPage']);
|
return Res(list, subData: res['maxPage']);
|
||||||
@@ -371,21 +402,25 @@ class ComicSourceParser {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pages.add(ExplorePageData(
|
pages.add(
|
||||||
title,
|
ExplorePageData(
|
||||||
switch (type) {
|
title,
|
||||||
"singlePageWithMultiPart" => ExplorePageType.singlePageWithMultiPart,
|
switch (type) {
|
||||||
"multiPartPage" => ExplorePageType.singlePageWithMultiPart,
|
"singlePageWithMultiPart" =>
|
||||||
"multiPageComicList" => ExplorePageType.multiPageComicList,
|
ExplorePageType.singlePageWithMultiPart,
|
||||||
"mixed" => ExplorePageType.mixed,
|
"multiPartPage" => ExplorePageType.singlePageWithMultiPart,
|
||||||
_ =>
|
"multiPageComicList" => ExplorePageType.multiPageComicList,
|
||||||
throw ComicSourceParseException("Unknown explore page type $type")
|
"mixed" => ExplorePageType.mixed,
|
||||||
},
|
_ => throw ComicSourceParseException(
|
||||||
loadPage,
|
"Unknown explore page type $type",
|
||||||
loadNext,
|
),
|
||||||
loadMultiPart,
|
},
|
||||||
loadMixed,
|
loadPage,
|
||||||
));
|
loadNext,
|
||||||
|
loadMultiPart,
|
||||||
|
loadMixed,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
@@ -425,18 +460,17 @@ class ComicSourceParser {
|
|||||||
if (type == "fixed") {
|
if (type == "fixed") {
|
||||||
categoryParts.add(FixedCategoryPart(name, cs!));
|
categoryParts.add(FixedCategoryPart(name, cs!));
|
||||||
} else if (type == "random") {
|
} else if (type == "random") {
|
||||||
categoryParts
|
categoryParts.add(
|
||||||
.add(RandomCategoryPart(name, cs!, c["randomNumber"] ?? 1));
|
RandomCategoryPart(name, cs!, c["randomNumber"] ?? 1),
|
||||||
|
);
|
||||||
} else if (type == "dynamic" && categories == null) {
|
} else if (type == "dynamic" && categories == null) {
|
||||||
var loader = c["loader"];
|
var loader = c["loader"];
|
||||||
if (loader is! JSInvokable) {
|
if (loader is! JSInvokable) {
|
||||||
throw "DynamicCategoryPart loader must be a function";
|
throw "DynamicCategoryPart loader must be a function";
|
||||||
}
|
}
|
||||||
categoryParts.add(DynamicCategoryPart(
|
categoryParts.add(
|
||||||
name,
|
DynamicCategoryPart(name, JSAutoFreeFunction(loader), _key!),
|
||||||
JSAutoFreeFunction(loader),
|
);
|
||||||
_key!,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// old format
|
// old format
|
||||||
@@ -453,30 +487,16 @@ class ComicSourceParser {
|
|||||||
for (int i = 0; i < tags.length; i++) {
|
for (int i = 0; i < tags.length; i++) {
|
||||||
PageJumpTarget target;
|
PageJumpTarget target;
|
||||||
if (itemType == 'category') {
|
if (itemType == 'category') {
|
||||||
target = PageJumpTarget(
|
target = PageJumpTarget(_key!, 'category', {
|
||||||
_key!,
|
"category": tags[i],
|
||||||
'category',
|
"param": categoryParams?.elementAtOrNull(i),
|
||||||
{
|
});
|
||||||
"category": tags[i],
|
|
||||||
"param": categoryParams?.elementAtOrNull(i),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (itemType == 'search') {
|
} else if (itemType == 'search') {
|
||||||
target = PageJumpTarget(
|
target = PageJumpTarget(_key!, 'search', {"keyword": tags[i]});
|
||||||
_key!,
|
|
||||||
'search',
|
|
||||||
{
|
|
||||||
"keyword": tags[i],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (itemType == 'search_with_namespace') {
|
} else if (itemType == 'search_with_namespace') {
|
||||||
target = PageJumpTarget(
|
target = PageJumpTarget(_key!, 'search', {
|
||||||
_key!,
|
"keyword": "$name:$tags[i]",
|
||||||
'search',
|
});
|
||||||
{
|
|
||||||
"keyword": "$name:$tags[i]",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
target = PageJumpTarget(_key!, itemType, null);
|
target = PageJumpTarget(_key!, itemType, null);
|
||||||
}
|
}
|
||||||
@@ -485,38 +505,96 @@ class ComicSourceParser {
|
|||||||
if (type == "fixed") {
|
if (type == "fixed") {
|
||||||
categoryParts.add(FixedCategoryPart(name, cs));
|
categoryParts.add(FixedCategoryPart(name, cs));
|
||||||
} else if (type == "random") {
|
} else if (type == "random") {
|
||||||
categoryParts
|
categoryParts.add(
|
||||||
.add(RandomCategoryPart(name, cs, c["randomNumber"] ?? 1));
|
RandomCategoryPart(name, cs, c["randomNumber"] ?? 1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CategoryData(
|
return CategoryData(
|
||||||
title: title,
|
title: title,
|
||||||
categories: categoryParts,
|
categories: categoryParts,
|
||||||
enableRankingPage: enableRankingPage ?? false,
|
enableRankingPage: enableRankingPage ?? false,
|
||||||
key: title);
|
key: title,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CategoryComicsData? _loadCategoryComicsData() {
|
CategoryComicsData? _loadCategoryComicsData() {
|
||||||
if (!_checkExists("categoryComics")) return null;
|
if (!_checkExists("categoryComics")) return null;
|
||||||
var options = <CategoryComicsOptions>[];
|
|
||||||
for (var element in _getValue("categoryComics.optionList") ?? []) {
|
List<CategoryComicsOptions>? options;
|
||||||
LinkedHashMap<String, String> map = LinkedHashMap<String, String>();
|
if (_checkExists("categoryComics.optionList")) {
|
||||||
for (var option in element["options"]) {
|
options = <CategoryComicsOptions>[];
|
||||||
if (option.isEmpty || !option.contains("-")) {
|
for (var element in _getValue("categoryComics.optionList") ?? []) {
|
||||||
continue;
|
LinkedHashMap<String, String> map = LinkedHashMap<String, String>();
|
||||||
|
for (var option in element["options"]) {
|
||||||
|
if (option.isEmpty || !option.contains("-")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var split = option.split("-");
|
||||||
|
var key = split.removeAt(0);
|
||||||
|
var value = split.join("-");
|
||||||
|
map[key] = value;
|
||||||
}
|
}
|
||||||
var split = option.split("-");
|
options.add(
|
||||||
var key = split.removeAt(0);
|
CategoryComicsOptions(
|
||||||
var value = split.join("-");
|
element["label"] ?? "",
|
||||||
map[key] = value;
|
map,
|
||||||
|
List.from(element["notShowWhen"] ?? []),
|
||||||
|
element["showWhen"] == null ? null : List.from(element["showWhen"]),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
options.add(CategoryComicsOptions(
|
|
||||||
map,
|
|
||||||
List.from(element["notShowWhen"] ?? []),
|
|
||||||
element["showWhen"] == null ? null : List.from(element["showWhen"])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CategoryOptionsLoader? optionLoader;
|
||||||
|
if (_checkExists("categoryComics.optionLoader")) {
|
||||||
|
optionLoader = (category, param) async {
|
||||||
|
try {
|
||||||
|
dynamic res = JsEngine().runCode("""
|
||||||
|
ComicSource.sources.$_key.categoryComics.optionLoader(
|
||||||
|
${jsonEncode(category)}, ${jsonEncode(param)})
|
||||||
|
""");
|
||||||
|
if (res is Future) {
|
||||||
|
res = await res;
|
||||||
|
}
|
||||||
|
if (res is! List) {
|
||||||
|
return Res.error("Invalid data:\nExpected: List\nGot: ${res.runtimeType}");
|
||||||
|
}
|
||||||
|
var options = <CategoryComicsOptions>[];
|
||||||
|
for (var element in res) {
|
||||||
|
if (element is! Map) {
|
||||||
|
return Res.error("Invalid option data:\nExpected: Map\nGot: ${element.runtimeType}");
|
||||||
|
}
|
||||||
|
LinkedHashMap<String, String> map = LinkedHashMap<String, String>();
|
||||||
|
for (var option in element["options"] ?? []) {
|
||||||
|
if (option.isEmpty || !option.contains("-")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var split = option.split("-");
|
||||||
|
var key = split.removeAt(0);
|
||||||
|
var value = split.join("-");
|
||||||
|
map[key] = value;
|
||||||
|
}
|
||||||
|
options.add(
|
||||||
|
CategoryComicsOptions(
|
||||||
|
element["label"] ?? "",
|
||||||
|
map,
|
||||||
|
List.from(element["notShowWhen"] ?? []),
|
||||||
|
element["showWhen"] == null ? null : List.from(element["showWhen"]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Res(options);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
Log.error("Data Analysis", "Failed to load category options.\n$e");
|
||||||
|
return Res.error(e.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
RankingData? rankingData;
|
RankingData? rankingData;
|
||||||
if (_checkExists("categoryComics.ranking")) {
|
if (_checkExists("categoryComics.ranking")) {
|
||||||
var options = <String, String>{};
|
var options = <String, String>{};
|
||||||
@@ -531,7 +609,7 @@ class ComicSourceParser {
|
|||||||
}
|
}
|
||||||
Future<Res<List<Comic>>> Function(String option, int page)? load;
|
Future<Res<List<Comic>>> Function(String option, int page)? load;
|
||||||
Future<Res<List<Comic>>> Function(String option, String? next)?
|
Future<Res<List<Comic>>> Function(String option, String? next)?
|
||||||
loadWithNext;
|
loadWithNext;
|
||||||
if (_checkExists("categoryComics.ranking.load")) {
|
if (_checkExists("categoryComics.ranking.load")) {
|
||||||
load = (option, page) async {
|
load = (option, page) async {
|
||||||
try {
|
try {
|
||||||
@@ -540,9 +618,12 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(option)}, ${jsonEncode(page)})
|
${jsonEncode(option)}, ${jsonEncode(page)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
subData: res["maxPage"]);
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
|
subData: res["maxPage"],
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Network", "$e\n$s");
|
Log.error("Network", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
@@ -556,8 +637,10 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(option)}, ${jsonEncode(next)})
|
${jsonEncode(option)}, ${jsonEncode(next)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
subData: res["next"],
|
subData: res["next"],
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -568,25 +651,38 @@ class ComicSourceParser {
|
|||||||
}
|
}
|
||||||
rankingData = RankingData(options, load, loadWithNext);
|
rankingData = RankingData(options, load, loadWithNext);
|
||||||
}
|
}
|
||||||
return CategoryComicsData(options, (category, param, options, page) async {
|
|
||||||
try {
|
if (options == null && optionLoader == null) {
|
||||||
var res = await JsEngine().runCode("""
|
options = [];
|
||||||
ComicSource.sources.$_key.categoryComics.load(
|
}
|
||||||
${jsonEncode(category)},
|
|
||||||
${jsonEncode(param)},
|
return CategoryComicsData(
|
||||||
${jsonEncode(options)},
|
options: options,
|
||||||
${jsonEncode(page)}
|
optionsLoader: optionLoader,
|
||||||
)
|
load: (category, param, options, page) async {
|
||||||
""");
|
try {
|
||||||
return Res(
|
var res = await JsEngine().runCode("""
|
||||||
List.generate(res["comics"].length,
|
ComicSource.sources.$_key.categoryComics.load(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
${jsonEncode(category)},
|
||||||
subData: res["maxPage"]);
|
${jsonEncode(param)},
|
||||||
} catch (e, s) {
|
${jsonEncode(options)},
|
||||||
Log.error("Network", "$e\n$s");
|
${jsonEncode(page)}
|
||||||
return Res.error(e.toString());
|
)
|
||||||
}
|
""");
|
||||||
}, rankingData: rankingData);
|
return Res(
|
||||||
|
List.generate(
|
||||||
|
res["comics"].length,
|
||||||
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
|
subData: res["maxPage"],
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error("Network", "$e\n$s");
|
||||||
|
return Res.error(e.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rankingData: rankingData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchPageData? _loadSearchData() {
|
SearchPageData? _loadSearchData() {
|
||||||
@@ -603,12 +699,14 @@ class ComicSourceParser {
|
|||||||
var value = split.join("-");
|
var value = split.join("-");
|
||||||
map[key] = value;
|
map[key] = value;
|
||||||
}
|
}
|
||||||
options.add(SearchOptions(
|
options.add(
|
||||||
map,
|
SearchOptions(
|
||||||
element["label"],
|
map,
|
||||||
element['type'] ?? 'select',
|
element["label"],
|
||||||
element['default'] == null ? null : jsonEncode(element['default']),
|
element['type'] ?? 'select',
|
||||||
));
|
element['default'] == null ? null : jsonEncode(element['default']),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchFunction? loadPage;
|
SearchFunction? loadPage;
|
||||||
@@ -623,9 +721,12 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(keyword)}, ${jsonEncode(searchOption)}, ${jsonEncode(page)})
|
${jsonEncode(keyword)}, ${jsonEncode(searchOption)}, ${jsonEncode(page)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
subData: res["maxPage"]);
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
|
subData: res["maxPage"],
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Network", "$e\n$s");
|
Log.error("Network", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
@@ -639,8 +740,10 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(keyword)}, ${jsonEncode(searchOption)}, ${jsonEncode(next)})
|
${jsonEncode(keyword)}, ${jsonEncode(searchOption)}, ${jsonEncode(next)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
subData: res["next"],
|
subData: res["next"],
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -689,8 +792,9 @@ class ComicSourceParser {
|
|||||||
|
|
||||||
final bool multiFolder = _getValue("favorites.multiFolder");
|
final bool multiFolder = _getValue("favorites.multiFolder");
|
||||||
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
|
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
|
||||||
final bool? singleFolderForSingleComic =
|
final bool? singleFolderForSingleComic = _getValue(
|
||||||
_getValue("favorites.singleFolderForSingleComic");
|
"favorites.singleFolderForSingleComic",
|
||||||
|
);
|
||||||
|
|
||||||
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
|
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
|
||||||
if (!ComicSource.find(_key!)!.isLogged) {
|
if (!ComicSource.find(_key!)!.isLogged) {
|
||||||
@@ -743,9 +847,12 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(page)}, ${jsonEncode(folder)})
|
${jsonEncode(page)}, ${jsonEncode(folder)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
subData: res["maxPage"]);
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
|
subData: res["maxPage"],
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Network", "$e\n$s");
|
Log.error("Network", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
@@ -765,8 +872,10 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(next)}, ${jsonEncode(folder)})
|
${jsonEncode(next)}, ${jsonEncode(folder)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
List.generate(res["comics"].length,
|
List.generate(
|
||||||
(index) => Comic.fromJson(res["comics"][index], _key!)),
|
res["comics"].length,
|
||||||
|
(index) => Comic.fromJson(res["comics"][index], _key!),
|
||||||
|
),
|
||||||
subData: res["next"],
|
subData: res["next"],
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -857,8 +966,9 @@ class ComicSourceParser {
|
|||||||
${jsonEncode(id)}, ${jsonEncode(subId)}, ${jsonEncode(page)}, ${jsonEncode(replyTo)})
|
${jsonEncode(id)}, ${jsonEncode(subId)}, ${jsonEncode(page)}, ${jsonEncode(replyTo)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
(res["comments"] as List).map((e) => Comment.fromJson(e)).toList(),
|
(res["comments"] as List).map((e) => Comment.fromJson(e)).toList(),
|
||||||
subData: res["maxPage"]);
|
subData: res["maxPage"],
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Network", "$e\n$s");
|
Log.error("Network", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
@@ -1113,7 +1223,8 @@ class ComicSourceParser {
|
|||||||
ComicSource.sources.$_key.comic.archive.getArchives(${jsonEncode(cid)})
|
ComicSource.sources.$_key.comic.archive.getArchives(${jsonEncode(cid)})
|
||||||
""");
|
""");
|
||||||
return Res(
|
return Res(
|
||||||
(res as List).map((e) => ArchiveInfo.fromJson(e)).toList());
|
(res as List).map((e) => ArchiveInfo.fromJson(e)).toList(),
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Network", "$e\n$s");
|
Log.error("Network", "$e\n$s");
|
||||||
return Res.error(e.toString());
|
return Res.error(e.toString());
|
||||||
|
@@ -181,12 +181,17 @@ abstract class ImageDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (configs['onResponse'] is JSInvokable) {
|
if (configs['onResponse'] is JSInvokable) {
|
||||||
buffer = (configs['onResponse'] as JSInvokable)([buffer]);
|
buffer = (configs['onResponse'] as JSInvokable)([Uint8List.fromList(buffer)]);
|
||||||
(configs['onResponse'] as JSInvokable).free();
|
(configs['onResponse'] as JSInvokable).free();
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = Uint8List.fromList(buffer);
|
Uint8List data;
|
||||||
buffer.clear();
|
if (buffer is Uint8List) {
|
||||||
|
data = buffer;
|
||||||
|
} else {
|
||||||
|
data = Uint8List.fromList(buffer);
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (configs['modifyImage'] != null) {
|
if (configs['modifyImage'] != null) {
|
||||||
var newData = await modifyImageWithScript(
|
var newData = await modifyImageWithScript(
|
||||||
|
@@ -27,9 +27,11 @@ class CategoryComicsPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
||||||
late final CategoryComicsData data;
|
late final CategoryComicsData data;
|
||||||
late final List<CategoryComicsOptions> options;
|
late List<CategoryComicsOptions>? options;
|
||||||
|
late final CategoryOptionsLoader? optionsLoader;
|
||||||
late List<String> optionsValue;
|
late List<String> optionsValue;
|
||||||
late String sourceKey;
|
late String sourceKey;
|
||||||
|
String? error;
|
||||||
|
|
||||||
void findData() {
|
void findData() {
|
||||||
for (final source in ComicSource.all()) {
|
for (final source in ComicSource.all()) {
|
||||||
@@ -38,24 +40,23 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
throw "The comic source ${source.name} does not support category comics";
|
throw "The comic source ${source.name} does not support category comics";
|
||||||
}
|
}
|
||||||
data = source.categoryComicsData!;
|
data = source.categoryComicsData!;
|
||||||
options = data.options.where((element) {
|
if (data.options != null) {
|
||||||
if (element.notShowWhen.contains(widget.category)) {
|
options = data.options!.where((element) {
|
||||||
return false;
|
if (element.notShowWhen.contains(widget.category)) {
|
||||||
} else if (element.showWhen != null) {
|
return false;
|
||||||
return element.showWhen!.contains(widget.category);
|
} else if (element.showWhen != null) {
|
||||||
}
|
return element.showWhen!.contains(widget.category);
|
||||||
return true;
|
}
|
||||||
}).toList();
|
return true;
|
||||||
var defaultOptionsValue =
|
}).toList();
|
||||||
options.map((e) => e.options.keys.first).toList();
|
} else {
|
||||||
if (optionsValue.length != options.length) {
|
options = null;
|
||||||
var newOptionsValue = List<String>.filled(options.length, "");
|
|
||||||
for (var i = 0; i < options.length; i++) {
|
|
||||||
newOptionsValue[i] =
|
|
||||||
optionsValue.elementAtOrNull(i) ?? defaultOptionsValue[i];
|
|
||||||
}
|
|
||||||
optionsValue = newOptionsValue;
|
|
||||||
}
|
}
|
||||||
|
if (data.optionsLoader != null) {
|
||||||
|
optionsLoader = data.optionsLoader;
|
||||||
|
loadOptions();
|
||||||
|
}
|
||||||
|
resetOptionsValue();
|
||||||
sourceKey = source.key;
|
sourceKey = source.key;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -63,6 +64,36 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
throw "${widget.categoryKey} Not found";
|
throw "${widget.categoryKey} Not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetOptionsValue() {
|
||||||
|
if (options == null) return;
|
||||||
|
var defaultOptionsValue = options!
|
||||||
|
.map((e) => e.options.keys.first)
|
||||||
|
.toList();
|
||||||
|
if (optionsValue.length != options!.length) {
|
||||||
|
var newOptionsValue = List<String>.filled(options!.length, "");
|
||||||
|
for (var i = 0; i < options!.length; i++) {
|
||||||
|
newOptionsValue[i] =
|
||||||
|
optionsValue.elementAtOrNull(i) ?? defaultOptionsValue[i];
|
||||||
|
}
|
||||||
|
optionsValue = newOptionsValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadOptions() async {
|
||||||
|
final res = await optionsLoader!(widget.category, widget.param);
|
||||||
|
if (res.error) {
|
||||||
|
setState(() {
|
||||||
|
error = res.errorMessage;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
options = res.data;
|
||||||
|
resetOptionsValue();
|
||||||
|
error = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (widget.options != null) {
|
if (widget.options != null) {
|
||||||
@@ -77,27 +108,44 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var topPadding = context.padding.top + 56.0;
|
var topPadding = context.padding.top + 56.0;
|
||||||
|
|
||||||
|
Widget body;
|
||||||
|
|
||||||
|
if (options == null) {
|
||||||
|
body = Center(child: CircularProgressIndicator());
|
||||||
|
} else if (error != null) {
|
||||||
|
body = NetworkError(
|
||||||
|
message: error!,
|
||||||
|
retry: () {
|
||||||
|
setState(() {
|
||||||
|
error = null;
|
||||||
|
});
|
||||||
|
loadOptions();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
body = ComicList(
|
||||||
|
key: Key(widget.category + optionsValue.toString()),
|
||||||
|
errorLeading: buildOptions().paddingTop(topPadding),
|
||||||
|
leadingSliver: buildOptions().paddingTop(topPadding).toSliver(),
|
||||||
|
loadPage: (i) =>
|
||||||
|
data.load(widget.category, widget.param, optionsValue, i),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
appBar: Appbar(
|
appBar: Appbar(title: Text(widget.category)),
|
||||||
title: Text(widget.category),
|
body: body,
|
||||||
),
|
|
||||||
body: ComicList(
|
|
||||||
key: Key(widget.category + optionsValue.toString()),
|
|
||||||
errorLeading: SizedBox(height: topPadding),
|
|
||||||
leadingSliver: buildOptions().paddingTop(topPadding).toSliver(),
|
|
||||||
loadPage: (i) => data.load(
|
|
||||||
widget.category,
|
|
||||||
widget.param,
|
|
||||||
optionsValue,
|
|
||||||
i,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildOptionItem(
|
Widget buildOptionItem(
|
||||||
String text, String value, int group, BuildContext context) {
|
String text,
|
||||||
|
String value,
|
||||||
|
int group,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
return OptionChip(
|
return OptionChip(
|
||||||
text: text.ts(sourceKey),
|
text: text.ts(sourceKey),
|
||||||
isSelected: value == optionsValue[group],
|
isSelected: value == optionsValue[group],
|
||||||
@@ -112,23 +160,57 @@ class _CategoryComicsPageState extends State<CategoryComicsPage> {
|
|||||||
|
|
||||||
Widget buildOptions() {
|
Widget buildOptions() {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
for (var optionList in options) {
|
var group = 0;
|
||||||
children.add(Wrap(
|
for (var optionList in options!) {
|
||||||
spacing: 8,
|
if (optionList.label.isNotEmpty) {
|
||||||
runSpacing: 8,
|
children.add(Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(
|
||||||
for (var option in optionList.options.entries)
|
bottom: 8.0,
|
||||||
buildOptionItem(
|
left: 4.0,
|
||||||
option.value.tl,
|
),
|
||||||
option.key,
|
child: Text(
|
||||||
options.indexOf(optionList),
|
optionList.label.ts(sourceKey),
|
||||||
context,
|
style: TextStyle(
|
||||||
)
|
fontSize: 14,
|
||||||
],
|
fontWeight: FontWeight.bold,
|
||||||
));
|
),
|
||||||
if (options.last != optionList) {
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (optionList.options.length <= 8) {
|
||||||
|
children.add(
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
for (var option in optionList.options.entries)
|
||||||
|
buildOptionItem(
|
||||||
|
option.value.tl,
|
||||||
|
option.key,
|
||||||
|
group,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var g = group;
|
||||||
|
children.add(Select(
|
||||||
|
current: optionList.options[optionsValue[g]],
|
||||||
|
values: optionList.options.values.toList(),
|
||||||
|
onTap: (i) {
|
||||||
|
var key = optionList.options.keys.elementAt(i);
|
||||||
|
if (key == optionsValue[g]) return;
|
||||||
|
setState(() {
|
||||||
|
optionsValue[g] = key;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (options!.last != optionList) {
|
||||||
children.add(const SizedBox(height: 8));
|
children.add(const SizedBox(height: 8));
|
||||||
}
|
}
|
||||||
|
group++;
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@@ -2,7 +2,7 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.4.6+146
|
version: 1.5.0+150
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
|
Reference in New Issue
Block a user