[Comic Source] New model PageJumpTarget. All page jump operations now use PageJumpTarget.

This commit is contained in:
2025-04-04 22:47:43 +08:00
parent 971fc1da92
commit 463ad5b5bc
12 changed files with 266 additions and 226 deletions

View File

@@ -34,24 +34,28 @@ class CategoryButtonData {
});
}
class CategoryItem {
final String label;
final PageJumpTarget target;
const CategoryItem(this.label, this.target);
}
abstract class BaseCategoryPart {
String get title;
List<String> get categories;
List<String>? get categoryParams => null;
List<CategoryItem> get categories;
bool get enableRandom;
String get categoryType;
/// Data class for building a part of category page.
const BaseCategoryPart();
}
class FixedCategoryPart extends BaseCategoryPart {
@override
final List<String> categories;
final List<CategoryItem> categories;
@override
bool get enableRandom => false;
@@ -59,19 +63,12 @@ class FixedCategoryPart extends BaseCategoryPart {
@override
final String title;
@override
final String categoryType;
@override
final List<String>? categoryParams;
/// A [BaseCategoryPart] that show fixed tags on category page.
const FixedCategoryPart(this.title, this.categories, this.categoryType,
[this.categoryParams]);
const FixedCategoryPart(this.title, this.categories);
}
class RandomCategoryPart extends BaseCategoryPart {
final List<String> tags;
final List<CategoryItem> all;
final int randomNumber;
@@ -81,67 +78,23 @@ class RandomCategoryPart extends BaseCategoryPart {
@override
bool get enableRandom => true;
@override
final String categoryType;
List<String> _categories() {
if (randomNumber >= tags.length) {
return tags;
List<CategoryItem> _categories() {
if (randomNumber >= all.length) {
return all;
}
var start = math.Random().nextInt(tags.length - randomNumber);
return tags.sublist(start, start + randomNumber);
var start = math.Random().nextInt(all.length - randomNumber);
return all.sublist(start, start + randomNumber);
}
@override
List<String> get categories => _categories();
List<CategoryItem> get categories => _categories();
/// A [BaseCategoryPart] that show random tags on category page.
/// A [BaseCategoryPart] that show a part of random tags on category page.
const RandomCategoryPart(
this.title, this.tags, this.randomNumber, this.categoryType);
}
class RandomCategoryPartWithRuntimeData extends BaseCategoryPart {
final Iterable<String> Function() loadTags;
final int randomNumber;
@override
final String title;
@override
bool get enableRandom => true;
@override
final String categoryType;
static final random = math.Random();
List<String> _categories() {
var tags = loadTags();
if (randomNumber >= tags.length) {
return tags.toList();
}
final start = random.nextInt(tags.length - randomNumber);
var res = List.filled(randomNumber, '');
int index = -1;
for (var s in tags) {
index++;
if (start > index) {
continue;
} else if (index == start + randomNumber) {
break;
}
res[index - start] = s;
}
return res;
}
@override
List<String> get categories => _categories();
/// A [BaseCategoryPart] that show random tags on category page.
RandomCategoryPartWithRuntimeData(
this.title, this.loadTags, this.randomNumber, this.categoryType);
this.title,
this.all,
this.randomNumber,
);
}
CategoryData getCategoryDataWithKey(String key) {

View File

@@ -11,6 +11,8 @@ import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/comic_type.dart';
import 'package:venera/foundation/history.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/pages/category_comics_page.dart';
import 'package:venera/pages/search_result_page.dart';
import 'package:venera/utils/data_sync.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/init.dart';
@@ -349,7 +351,7 @@ class ExplorePagePart {
/// - category:categoryName
///
/// End with `@`+`param` if the category has a parameter.
final String? viewMore;
final PageJumpTarget? viewMore;
const ExplorePagePart(this.title, this.comics, this.viewMore);
}

View File

@@ -430,3 +430,110 @@ class ComicChapters {
}
}
}
class PageJumpTarget {
final String sourceKey;
final String page;
final Map<String, dynamic>? attributes;
const PageJumpTarget(this.sourceKey, this.page, this.attributes);
static PageJumpTarget parse(String sourceKey, dynamic value) {
if (value is Map) {
if (value['page'] != null) {
return PageJumpTarget(
sourceKey,
value["page"] ?? "search",
value["attributes"],
);
} else if (value["action"] != null) {
// old version `onClickTag`
var page = value["action"];
if (page == "search") {
return PageJumpTarget(
sourceKey,
"search",
{
"text": value["keyword"],
},
);
} else if (page == "category") {
return PageJumpTarget(
sourceKey,
"category",
{
"category": value["keyword"],
"param": value["param"],
},
);
} else {
return PageJumpTarget(sourceKey, page, null);
}
}
} else if (value is String) {
// old version string encoding. search: `search:keyword`, category: `category:keyword` or `category:keyword@param`
var segments = value.split(":");
var page = segments[0];
if (page == "search") {
return PageJumpTarget(
sourceKey,
"search",
{
"text": segments[1],
},
);
} else if (page == "category") {
var c = segments[1];
if (c.contains('@')) {
var parts = c.split('@');
return PageJumpTarget(
sourceKey,
"category",
{
"category": parts[0],
"param": parts[1],
},
);
} else {
return PageJumpTarget(
sourceKey,
"category",
{
"category": c,
},
);
}
} else {
return PageJumpTarget(sourceKey, page, null);
}
}
return PageJumpTarget(sourceKey, "Invalid Data", null);
}
void jump(BuildContext context) {
if (page == "search") {
context.to(
() => SearchResultPage(
text: attributes?["text"] ?? attributes?["keyword"] ?? "",
sourceKey: sourceKey,
options: List.from(attributes?["options"] ?? []),
),
);
} else if (page == "category") {
var key = ComicSource.find(sourceKey)!.categoryData!.key;
context.to(
() => CategoryComicsPage(
categoryKey: key,
category: attributes?["category"] ??
(throw ArgumentError("Category name is required")),
options: List.from(attributes?["options"] ?? []),
param: attributes?["param"],
),
);
} else {
Log.error("Page Jump", "Unknown page: $page");
}
}
}

View File

@@ -80,9 +80,8 @@ class ComicSourceParser {
Future<ComicSource> parse(String js, String filePath) async {
js = js.replaceAll("\r\n", "\n");
var line1 = js
.split('\n')
.firstWhereOrNull((e) => e.trim().startsWith("class "));
var line1 =
js.split('\n').firstWhereOrNull((e) => e.trim().startsWith("class "));
if (line1 == null ||
!line1.startsWith("class ") ||
!line1.contains("extends ComicSource")) {
@@ -336,7 +335,7 @@ class ComicSourceParser {
(e['comics'] as List).map((e) {
return Comic.fromJson(e, _key!);
}).toList(),
e['viewMore'],
PageJumpTarget.parse(_key!, e['viewMore']),
);
}),
),
@@ -404,21 +403,78 @@ class ComicSourceParser {
var categoryParts = <BaseCategoryPart>[];
for (var c in doc["parts"]) {
final String name = c["name"];
final String type = c["type"];
final List<String> tags = List.from(c["categories"]);
final String itemType = c["itemType"];
List<String>? categoryParams = ListOrNull.from(c["categoryParams"]);
final String? groupParam = c["groupParam"];
if (groupParam != null) {
categoryParams = List.filled(tags.length, groupParam);
if (c["categories"] is! List || c["categories"].isEmpty) {
continue;
}
if (type == "fixed") {
categoryParts
.add(FixedCategoryPart(name, tags, itemType, categoryParams));
} else if (type == "random") {
categoryParts.add(
RandomCategoryPart(name, tags, c["randomNumber"] ?? 1, itemType));
List categories = c["categories"];
if (categories[0] is Map) {
// new format
final String name = c["name"];
final String type = c["type"];
final cs = categories
.map(
(e) => CategoryItem(
e['label'],
PageJumpTarget.parse(_key!, e['target']),
),
)
.toList();
if (type == "fixed") {
categoryParts.add(FixedCategoryPart(name, cs));
} else if (type == "random") {
categoryParts
.add(RandomCategoryPart(name, cs, c["randomNumber"] ?? 1));
}
} else {
// old format
final String name = c["name"];
final String type = c["type"];
final List<String> tags = List.from(c["categories"]);
final String itemType = c["itemType"];
List<String>? categoryParams = ListOrNull.from(c["categoryParams"]);
final String? groupParam = c["groupParam"];
if (groupParam != null) {
categoryParams = List.filled(tags.length, groupParam);
}
var cs = <CategoryItem>[];
for (int i = 0; i < tags.length; i++) {
PageJumpTarget target;
if (itemType == 'category') {
target = PageJumpTarget(
_key!,
'category',
{
"category": tags[i],
"param": categoryParams?.elementAtOrNull(i),
},
);
} else if (itemType == 'search') {
target = PageJumpTarget(
_key!,
'search',
{
"keyword": tags[i],
},
);
} else if (itemType == 'search_with_namespace') {
target = PageJumpTarget(
_key!,
'search',
{
"keyword": "$name:$tags[i]",
},
);
} else {
target = PageJumpTarget(_key!, itemType, null);
}
cs.add(CategoryItem(tags[i], target));
}
if (type == "fixed") {
categoryParts.add(FixedCategoryPart(name, cs));
} else if (type == "random") {
categoryParts
.add(RandomCategoryPart(name, cs, c["randomNumber"] ?? 1));
}
}
}
@@ -620,7 +676,8 @@ class ComicSourceParser {
final bool multiFolder = _getValue("favorites.multiFolder");
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
final bool? singleFolderForSingleComic = _getValue("favorites.singleFolderForSingleComic");
final bool? singleFolderForSingleComic =
_getValue("favorites.singleFolderForSingleComic");
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
if (!ComicSource.find(_key!)!.isLogged) {