You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
7.9 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_slider_drawer/flutter_slider_drawer.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/api/mw/list.dart';
import 'package:isekai_wiki/api/mw/mw_api.dart';
import 'package:isekai_wiki/api/mw/parse.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/api/response/parse.dart';
import 'package:isekai_wiki/components/bottom_nav_bar.dart';
import 'package:isekai_wiki/components/nav_bar_button.dart';
import 'package:isekai_wiki/components/safearea_builder.dart';
import 'package:isekai_wiki/components/toc.dart';
import 'package:isekai_wiki/components/wikipage_parser.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/global.dart';
import 'package:isekai_wiki/models/favorite_list.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
import 'package:isekai_wiki/utils/dialog.dart';
import 'package:isekai_wiki/utils/error.dart';
import 'package:isekai_wiki/utils/utils.dart';
import 'package:share_plus/share_plus.dart';
class MinimumArticleData {
final String title;
final String? description;
final String? mainCategory;
final DateTime? updateTime;
MinimumArticleData({required this.title, this.description, this.mainCategory, this.updateTime});
}
class ArticleCategoryData {
final String name;
final String identity;
ArticleCategoryData({required this.name, required this.identity});
}
class WikiViewPageController extends GetxController {
WikiPageParserController? parserController;
var loading = true.obs;
var menuSlider = GlobalKey<SliderDrawerState>();
var pageTitle = "".obs;
var pageId = 0.obs;
var pageInfo = Rx<PageInfo?>(null);
var parseInfo = Rx<MWParseInfo?>(null);
var displayTitle = "".obs;
var activedSection = "_firstSection".obs;
bool get hasFirstSection {
if (parseInfo.value == null) return false;
if (parseInfo.value!.sections.isEmpty) return false;
return parseInfo.value!.sections.first.byteoffset == 0;
}
@override
void onInit() {
super.onInit();
}
Future<void> loadPageContent() async {
if (pageTitle.isNotEmpty || pageId.value != 0) {
try {
// 加载页面信息
loading.value = true;
MWResponse<List<PageInfo>> pageInfoRes;
if (pageId.value != 0) {
pageInfoRes = await MWApiList.getPageInfoList(pageids: [pageId.value]);
} else {
pageInfoRes = await MWApiList.getPageInfoList(titles: [pageTitle.value]);
}
if (pageInfoRes.data.isEmpty) {
throw MWApiErrorException(code: 'no-page', info: "页面信息丢失");
}
pageInfo.value = pageInfoRes.data[0];
displayTitle.value = pageInfo.value!.mainTitle;
pageId.value = pageInfo.value!.pageid;
pageTitle.value = pageInfo.value!.title;
// 获取页面HTML
var parseRes = await MWApiParse.parse(pageId: pageId.value);
parseInfo.value = parseRes.data;
} catch (err, stack) {
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), title: "错误");
if (kDebugMode) {
print("Exception in page: $err");
stack.printError();
}
loading.value = false;
}
}
}
void handleFlowButtonClick() {
alert(Get.overlayContext!, "评论功能暂未完成");
}
void handleShareButtonClick() {
if (pageInfo.value != null) {
var pageUrl = pageInfo.value!.fullurl ?? getPageUrl(pageInfo.value!.title);
var shareTitle = "${pageInfo.value!.title} - ${Global.siteTitle}";
var shareText = "$shareTitle\n$pageUrl";
Share.share(shareText, subject: shareTitle);
} else {
alert(Get.overlayContext!, "页面还未加载完成");
}
}
void handleTOCButtonClick() {
menuSlider.currentState?.openSlider();
}
void handleSwitchTitle(String anchor) {
menuSlider.currentState?.closeSlider();
Future.delayed(const Duration(milliseconds: 360)).then((_) {
activedSection.value = anchor;
});
parserController?.scrollToAnchor(anchor);
}
void handleSectionChange(String anchor) {
activedSection.value = anchor;
}
}
class WikiViewPage extends StatefulWidget {
final MinimumArticleData? initialArticleData;
final String? targetPage;
final int? targetPageId;
const WikiViewPage({super.key, this.targetPage, this.targetPageId, this.initialArticleData});
@override
State<StatefulWidget> createState() => _WikiViewPageState();
}
class _WikiViewPageState extends ReactiveState<WikiViewPage> {
var c = WikiViewPageController();
@override
void initState() {
super.initState();
Get.put(c);
c.loadPageContent();
}
@override
void dispose() {
super.dispose();
Get.delete<WikiViewPageController>();
}
@override
void receiveProps() {
c.pageTitle.value = widget.targetPage ?? "";
c.pageId.value = widget.targetPageId ?? 0;
c.displayTitle.value = widget.initialArticleData?.title ?? "加载中";
}
Widget renderTOC(BuildContext context) {
return Obx(
() => TOCList(
sections: c.parseInfo.value?.sections ?? const [],
activedItem: c.activedSection.value,
hasFirstSection: c.hasFirstSection,
onSwitchTitle: c.handleSwitchTitle,
),
);
}
@override
Widget render(BuildContext context) {
final flc = Get.find<FavoriteListController>();
return SliderDrawer(
key: c.menuSlider,
isCupertino: true,
appBar: null,
showCover: true,
slideDirection: SlideDirection.RIGHT_TO_LEFT,
slider: renderTOC(context),
animationDuration: 350,
child: IsekaiPageScaffold(
navigationBar: IsekaiNavigationBar(
middle: Obx(() => Text(c.displayTitle.value)),
),
bottomNavigationBar: BottomNavigationBar(
child: Padding(
padding: const EdgeInsets.all(0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
NavBarButton(
icon: CupertinoIcons.chat_bubble_2,
onPressed: c.handleFlowButtonClick,
semanticLabel: "讨论",
),
Obx(() {
if (c.pageInfo.value != null && flc.isFavorite(c.pageInfo.value!)) {
return NavBarButton(
icon: CupertinoIcons.heart_fill,
onPressed: () {},
semanticLabel: "取消收藏",
);
} else {
return NavBarButton(
icon: CupertinoIcons.heart,
onPressed: () {},
semanticLabel: "收藏",
);
}
}),
NavBarButton(
icon: CupertinoIcons.share,
onPressed: c.handleShareButtonClick,
semanticLabel: "分享",
),
NavBarButton(
icon: CupertinoIcons.textformat_alt,
onPressed: c.handleFlowButtonClick,
semanticLabel: "阅读设置",
),
NavBarButton(
icon: CupertinoIcons.list_bullet,
onPressed: c.handleTOCButtonClick,
semanticLabel: "目录",
),
],
),
),
),
child: SafeAreaBuilder(
builder: (context, padding) => Obx(
() => WikiPageParser(
padding: padding,
pageInfo: c.pageInfo.value,
parseInfo: c.parseInfo.value,
onSectionChange: c.handleSectionChange,
ref: (controller) {
c.parserController = controller;
},
),
),
),
),
);
}
}