mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
categories page
This commit is contained in:
@@ -1,10 +1,287 @@
|
||||
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 {
|
||||
const CategoriesPage({super.key});
|
||||
|
||||
@override
|
||||
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';
|
||||
}
|
||||
|
120
lib/pages/category_comics_page.dart
Normal file
120
lib/pages/category_comics_page.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user