categories page

This commit is contained in:
nyne
2024-10-02 10:50:46 +08:00
parent 16857185fc
commit 9a194e8394
4 changed files with 401 additions and 4 deletions

View File

@@ -335,7 +335,7 @@ class _FilledTabBarState extends State<FilledTabBar> {
static const tabPadding = EdgeInsets.symmetric(horizontal: 6, vertical: 6); static const tabPadding = EdgeInsets.symmetric(horizontal: 6, vertical: 6);
static const tabRadius = 12.0; static const tabRadius = 8.0;
_IndicatorPainter? painter; _IndicatorPainter? painter;

View File

@@ -634,8 +634,8 @@ class _ComicListState extends State<ComicList> {
} else { } else {
setState(() { setState(() {
data[page] = res.data; data[page] = res.data;
if (res.subData?['maxPage'] != null) { if (res.subData != null && res.subData is int) {
maxPage = res.subData['maxPage']; maxPage = res.subData;
} }
}); });
} }

View File

@@ -1,10 +1,287 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:venera/components/components.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/state_controller.dart';
import 'package:venera/utils/translations.dart';
import 'category_comics_page.dart';
class CategoriesPage extends StatelessWidget { class CategoriesPage extends StatelessWidget {
const CategoriesPage({super.key}); const CategoriesPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Placeholder(); return StateBuilder<SimpleController>(
tag: "category",
init: SimpleController(),
builder: (controller) {
var categories = List.from(appdata.settings["categories"]);
var allCategories = ComicSource.all()
.map((e) => e.categoryData?.key)
.where((element) => element != null)
.map((e) => e!)
.toList();
categories = categories
.where((element) => allCategories.contains(element))
.toList();
return Material(
child: DefaultTabController(
length: categories.length,
key: Key(categories.toString()),
child: Column(
children: [
FilledTabBar(
tabs: categories.map((e) {
String title = e;
try {
title = getCategoryDataWithKey(e).title;
} catch (e) {
//
}
return Tab(
text: title,
key: Key(e),
);
}).toList(),
),
Expanded(
child: TabBarView(
children:
categories.map((e) => _CategoryPage(e)).toList()),
)
],
),
),
);
},
);
} }
} }
typedef ClickTagCallback = void Function(String, String?);
class _CategoryPage extends StatelessWidget {
const _CategoryPage(this.category);
final String category;
CategoryData get data => getCategoryDataWithKey(category);
String findComicSourceKey() {
for (var source in ComicSource.all()) {
if (source.categoryData?.key == category) {
return source.key;
}
}
return "";
}
void handleClick(
String tag,
String? param,
String type,
String namespace,
String categoryKey,
) {
if (type == 'search') {
// TODO: Implement search
/*
App.mainNavigatorKey?.currentContext?.to(
() => SearchResultPage(
keyword: tag,
options: const [],
sourceKey: findComicSourceKey(),
),
);
*/
} else if (type == "search_with_namespace") {
/*
if (tag.contains(" ")) {
tag = '"$tag"';
}
App.mainNavigatorKey?.currentContext?.to(
() => SearchResultPage(
keyword: "$namespace:$tag",
options: const [],
sourceKey: findComicSourceKey(),
),
);
*/
} else if (type == "category") {
App.mainNavigatorKey!.currentContext!.to(
() => CategoryComicsPage(
category: tag,
categoryKey: categoryKey,
param: param,
),
);
}
}
@override
Widget build(BuildContext context) {
var children = <Widget>[];
if (data.enableRankingPage || data.buttons.isNotEmpty) {
children.add(buildTitle(data.title));
children.add(Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
child: Wrap(
children: [
if (data.enableRankingPage)
buildTag("Ranking".tl, (p0, p1) {
// TODO: Implement ranking
/*
context.to(() => RankingPage(sourceKey: findComicSourceKey()));
*/
}),
for (var buttonData in data.buttons)
buildTag(buttonData.label.tl, (p0, p1) => buttonData.onTap())
],
),
));
}
for (var part in data.categories) {
if (part.enableRandom) {
children.add(StatefulBuilder(builder: (context, updater) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitleWithRefresh(part.title, () => updater(() {})),
buildTagsWithParams(
part.categories,
part.categoryParams,
part.title,
(key, param) => handleClick(
key,
param,
part.categoryType,
part.title,
category,
),
)
],
);
}));
} else {
children.add(buildTitle(part.title));
children.add(
buildTagsWithParams(
part.categories,
part.categoryParams,
part.title,
(tag, param) => handleClick(
tag,
param,
part.categoryType,
part.title,
data.key,
),
),
);
}
}
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
);
}
Widget buildTitle(String title) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 5, 10),
child: Text(title.tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
);
}
Widget buildTitleWithRefresh(String title, void Function() onRefresh) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 5, 10),
child: Row(
children: [
Text(
title.tl,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
IconButton(onPressed: onRefresh, icon: const Icon(Icons.refresh))
],
),
);
}
Widget buildTagsWithParams(
List<String> tags,
List<String>? params,
String? namespace,
ClickTagCallback onClick,
) {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 16),
child: Wrap(
children: List<Widget>.generate(
tags.length,
(index) => buildTag(
tags[index],
onClick,
namespace,
params?.elementAtOrNull(index),
),
),
),
);
}
Widget buildTag(String tag, ClickTagCallback onClick,
[String? namespace, String? param]) {
String translateTag(String tag) {
/*
// TODO: Implement translation
if (enableTranslation) {
if (namespace != null) {
tag = TagsTranslation.translationTagWithNamespace(tag, namespace);
} else {
tag = tag.translateTagsToCN;
}
}
*/
return tag;
}
return Padding(
padding: const EdgeInsets.fromLTRB(8, 6, 8, 6),
child: Builder(
builder: (context) {
return Material(
elevation: 0.6,
borderRadius: const BorderRadius.all(Radius.circular(4)),
color: context.colorScheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent,
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(4)),
onTap: () => onClick(tag, param),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: Text(translateTag(tag)),
),
),
);
},
),
);
}
bool get enableTranslation => App.locale.languageCode == 'zh';
}

View File

@@ -0,0 +1,120 @@
import "package:flutter/material.dart";
import "package:venera/components/components.dart";
import "package:venera/foundation/app.dart";
import "package:venera/foundation/comic_source/comic_source.dart";
import "package:venera/utils/translations.dart";
class CategoryComicsPage extends StatefulWidget {
const CategoryComicsPage({
required this.category,
this.param,
required this.categoryKey,
super.key,
});
final String category;
final String? param;
final String categoryKey;
@override
State<CategoryComicsPage> createState() => _CategoryComicsPageState();
}
class _CategoryComicsPageState extends State<CategoryComicsPage> {
late final CategoryComicsData data;
late final List<CategoryComicsOptions> options;
late List<String> optionsValue;
void findData() {
for (final source in ComicSource.all()) {
if (source.categoryData?.key == widget.categoryKey) {
data = source.categoryComicsData!;
options = data.options.where((element) {
if (element.notShowWhen.contains(widget.category)) {
return false;
} else if (element.showWhen != null) {
return element.showWhen!.contains(widget.category);
}
return true;
}).toList();
optionsValue = options.map((e) => e.options.keys.first).toList();
return;
}
}
throw "${widget.categoryKey} Not found";
}
@override
void initState() {
findData();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: Appbar(
title: Text(widget.category),
),
body: Column(
children: [
Expanded(
child: ComicList(
loadPage: (i) => data.load(
widget.category,
widget.param,
optionsValue,
i,
),
),
),
],
),
);
}
Widget buildOptionItem(
String text, String value, int group, BuildContext context) {
return OptionChip(
text: text,
isSelected: value == optionsValue[group],
onTap: () {
if (value == optionsValue[group]) return;
setState(() {
optionsValue[group] = value;
});
},
);
}
Widget buildOptions() {
List<Widget> children = [];
for (var optionList in options) {
children.add(Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (var option in optionList.options.entries)
buildOptionItem(
option.value.tl,
option.key,
options.indexOf(optionList),
context,
)
],
));
if (options.last != optionList) {
children.add(const SizedBox(height: 8));
}
}
return SliverToBoxAdapter(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [...children, const Divider()],
).paddingLeft(8).paddingRight(8),
);
}
}