From 949752dbd1f22a132910deb41d4bb7d0486330a3 Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Thu, 5 Jan 2023 17:32:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E9=A1=B5=E9=9D=A2=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=BB=8EFlutter=E6=B8=B2=E6=9F=93=E6=94=B9=E4=B8=BAWebview?= =?UTF-8?q?=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 2 +- lib/api/mw/parse.dart | 1 + lib/api/response/page_info.g.dart | 11 +- lib/api/response/parse.dart | 147 ++++++++++++++ lib/api/response/parse.g.dart | 168 ++++++++++++++++ lib/app.dart | 5 + lib/components/page_card.dart | 25 +-- lib/components/recent_page_list.dart | 49 +++-- lib/components/safearea_builder.dart | 25 +++ lib/components/wikipage_parser.dart | 145 ++++++++++++++ lib/global.dart | 13 +- lib/main.dart | 19 +- lib/models/model.dart | 3 - lib/models/settings.dart | 75 +++++++ lib/models/settings.g.dart | 16 ++ lib/models/site_config.dart | 81 ++++++++ lib/models/site_config.g.dart | 26 +++ lib/models/user.dart | 42 ++-- lib/pages/about.dart | 11 +- lib/pages/article.dart | 36 +--- lib/pages/home.dart | 27 +-- pubspec.lock | 282 +++++++++++++++------------ pubspec.yaml | 2 + web/index.html | 2 + 24 files changed, 947 insertions(+), 266 deletions(-) create mode 100644 lib/api/mw/parse.dart create mode 100644 lib/api/response/parse.dart create mode 100644 lib/api/response/parse.g.dart create mode 100644 lib/components/safearea_builder.dart create mode 100644 lib/components/wikipage_parser.dart create mode 100644 lib/models/settings.dart create mode 100644 lib/models/settings.g.dart create mode 100644 lib/models/site_config.dart create mode 100644 lib/models/site_config.g.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 38f7053..250c340 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -47,7 +47,7 @@ android { applicationId "cn.isekai.wiki.isekai_wiki" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/lib/api/mw/parse.dart b/lib/api/mw/parse.dart new file mode 100644 index 0000000..ac7340a --- /dev/null +++ b/lib/api/mw/parse.dart @@ -0,0 +1 @@ +class MWApiParse {} diff --git a/lib/api/response/page_info.g.dart b/lib/api/response/page_info.g.dart index 77ba46d..914215f 100644 --- a/lib/api/response/page_info.g.dart +++ b/lib/api/response/page_info.g.dart @@ -11,11 +11,13 @@ PageInfo _$PageInfoFromJson(Map json) => PageInfo( ns: json['ns'] as int, title: json['title'] as String, subtitle: json['subtitle'] as String?, + displayTitle: json['displayTitle'] as String?, description: json['extract'] as String?, contentmodel: json['contentmodel'] as String?, pagelanguage: json['pagelanguage'] as String?, pagelanguagehtmlcode: json['pagelanguagehtmlcode'] as String?, pagelanguagedir: json['pagelanguagedir'] as String?, + inwatchlist: json['inwatchlist'] as bool?, updatedTime: json['touched'] == null ? null : DateTime.parse(json['touched'] as String), @@ -24,12 +26,9 @@ PageInfo _$PageInfoFromJson(Map json) => PageInfo( fullurl: json['fullurl'] as String?, editurl: json['editurl'] as String?, canonicalurl: json['canonicalurl'] as String?, - ) - ..displayTitle = json['displayTitle'] as String? - ..inwatchlist = json['inwatchlist'] as bool? - ..thumbnail = json['thumbnail'] == null - ? null - : PageImageInfo.fromJson(json['thumbnail'] as Map); + )..thumbnail = json['thumbnail'] == null + ? null + : PageImageInfo.fromJson(json['thumbnail'] as Map); Map _$PageInfoToJson(PageInfo instance) => { 'pageid': instance.pageid, diff --git a/lib/api/response/parse.dart b/lib/api/response/parse.dart new file mode 100644 index 0000000..f2f8774 --- /dev/null +++ b/lib/api/response/parse.dart @@ -0,0 +1,147 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'parse.g.dart'; + +@JsonSerializable() +class MWParseCategoryInfo { + String sortkey; + String category; + + MWParseCategoryInfo({ + this.sortkey = "", + this.category = "", + }); + + factory MWParseCategoryInfo.fromJson(Map json) => + _$MWParseCategoryInfoFromJson(json); + + Map toJson() => _$MWParseCategoryInfoToJson(this); +} + +@JsonSerializable() +class MWParseLangLinkInfo { + String lang; + String url; + String langname; + String autonym; + String title; + + MWParseLangLinkInfo({ + this.lang = "", + this.url = "", + this.langname = "", + this.autonym = "", + this.title = "", + }); + + factory MWParseLangLinkInfo.fromJson(Map json) => + _$MWParseLangLinkInfoFromJson(json); + + Map toJson() => _$MWParseLangLinkInfoToJson(this); +} + +@JsonSerializable() +class MWParsePageLinkInfo { + int ns; + String title; + bool exists; + + MWParsePageLinkInfo({ + this.ns = -1, + this.title = "", + this.exists = false, + }); + + factory MWParsePageLinkInfo.fromJson(Map json) => + _$MWParsePageLinkInfoFromJson(json); + + Map toJson() => _$MWParsePageLinkInfoToJson(this); +} + +@JsonSerializable() +class MWParseSectionInfo { + int toclevel; + int level; + String line; + String number; + String index; + String fromtitle; + int? byteoffset; + String anchor; + + MWParseSectionInfo({ + this.toclevel = -1, + this.level = -1, + this.line = "", + this.number = "", + this.index = "", + this.fromtitle = "", + this.byteoffset, + this.anchor = "", + }); + + factory MWParseSectionInfo.fromJson(Map json) => + _$MWParseSectionInfoFromJson(json); + + Map toJson() => _$MWParseSectionInfoToJson(this); +} + +@JsonSerializable() +class MWParseInfo { + String title; + int pageid; + int revid; + String text; + List langlink; + List categories; + List links; + List templates; + List images; + List externallinks; + List sections; + bool showtoc; + String displaytitle; + List modules; + List modulescripts; + List modulestyles; + Map jsconfigvars; + Map iwlinks; + Map properties; + + MWParseInfo({ + this.title = "", + this.pageid = -1, + this.revid = -1, + this.text = "", + this.langlink = const [], + this.categories = const [], + this.links = const [], + this.templates = const [], + this.images = const [], + this.externallinks = const [], + this.sections = const [], + this.showtoc = true, + this.displaytitle = "", + this.modules = const [], + this.modulescripts = const [], + this.modulestyles = const [], + this.jsconfigvars = const {}, + this.iwlinks = const {}, + this.properties = const {}, + }); + + factory MWParseInfo.fromJson(Map json) => _$MWParseInfoFromJson(json); + + Map toJson() => _$MWParseInfoToJson(this); +} + +@JsonSerializable() +class MWParseResponse { + MWParseInfo parse; + + MWParseResponse({required this.parse}); + + factory MWParseResponse.fromJson(Map json) => _$MWParseResponseFromJson(json); + + Map toJson() => _$MWParseResponseToJson(this); +} diff --git a/lib/api/response/parse.g.dart b/lib/api/response/parse.g.dart new file mode 100644 index 0000000..54c3dd1 --- /dev/null +++ b/lib/api/response/parse.g.dart @@ -0,0 +1,168 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MWParseCategoryInfo _$MWParseCategoryInfoFromJson(Map json) => + MWParseCategoryInfo( + sortkey: json['sortkey'] as String? ?? "", + category: json['category'] as String? ?? "", + ); + +Map _$MWParseCategoryInfoToJson( + MWParseCategoryInfo instance) => + { + 'sortkey': instance.sortkey, + 'category': instance.category, + }; + +MWParseLangLinkInfo _$MWParseLangLinkInfoFromJson(Map json) => + MWParseLangLinkInfo( + lang: json['lang'] as String? ?? "", + url: json['url'] as String? ?? "", + langname: json['langname'] as String? ?? "", + autonym: json['autonym'] as String? ?? "", + title: json['title'] as String? ?? "", + ); + +Map _$MWParseLangLinkInfoToJson( + MWParseLangLinkInfo instance) => + { + 'lang': instance.lang, + 'url': instance.url, + 'langname': instance.langname, + 'autonym': instance.autonym, + 'title': instance.title, + }; + +MWParsePageLinkInfo _$MWParsePageLinkInfoFromJson(Map json) => + MWParsePageLinkInfo( + ns: json['ns'] as int? ?? -1, + title: json['title'] as String? ?? "", + exists: json['exists'] as bool? ?? false, + ); + +Map _$MWParsePageLinkInfoToJson( + MWParsePageLinkInfo instance) => + { + 'ns': instance.ns, + 'title': instance.title, + 'exists': instance.exists, + }; + +MWParseSectionInfo _$MWParseSectionInfoFromJson(Map json) => + MWParseSectionInfo( + toclevel: json['toclevel'] as int? ?? -1, + level: json['level'] as int? ?? -1, + line: json['line'] as String? ?? "", + number: json['number'] as String? ?? "", + index: json['index'] as String? ?? "", + fromtitle: json['fromtitle'] as String? ?? "", + byteoffset: json['byteoffset'] as int?, + anchor: json['anchor'] as String? ?? "", + ); + +Map _$MWParseSectionInfoToJson(MWParseSectionInfo instance) => + { + 'toclevel': instance.toclevel, + 'level': instance.level, + 'line': instance.line, + 'number': instance.number, + 'index': instance.index, + 'fromtitle': instance.fromtitle, + 'byteoffset': instance.byteoffset, + 'anchor': instance.anchor, + }; + +MWParseInfo _$MWParseInfoFromJson(Map json) => MWParseInfo( + title: json['title'] as String? ?? "", + pageid: json['pageid'] as int? ?? -1, + revid: json['revid'] as int? ?? -1, + text: json['text'] as String? ?? "", + langlink: (json['langlink'] as List?) + ?.map((e) => + MWParseLangLinkInfo.fromJson(e as Map)) + .toList() ?? + const [], + categories: (json['categories'] as List?) + ?.map((e) => + MWParseCategoryInfo.fromJson(e as Map)) + .toList() ?? + const [], + links: (json['links'] as List?) + ?.map((e) => + MWParsePageLinkInfo.fromJson(e as Map)) + .toList() ?? + const [], + templates: (json['templates'] as List?) + ?.map((e) => + MWParsePageLinkInfo.fromJson(e as Map)) + .toList() ?? + const [], + images: (json['images'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + externallinks: (json['externallinks'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + sections: (json['sections'] as List?) + ?.map( + (e) => MWParseSectionInfo.fromJson(e as Map)) + .toList() ?? + const [], + showtoc: json['showtoc'] as bool? ?? true, + displaytitle: json['displaytitle'] as String? ?? "", + modules: (json['modules'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + modulescripts: (json['modulescripts'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + modulestyles: (json['modulestyles'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + jsconfigvars: json['jsconfigvars'] as Map? ?? const {}, + iwlinks: json['iwlinks'] as Map? ?? const {}, + properties: json['properties'] as Map? ?? const {}, + ); + +Map _$MWParseInfoToJson(MWParseInfo instance) => + { + 'title': instance.title, + 'pageid': instance.pageid, + 'revid': instance.revid, + 'text': instance.text, + 'langlink': instance.langlink, + 'categories': instance.categories, + 'links': instance.links, + 'templates': instance.templates, + 'images': instance.images, + 'externallinks': instance.externallinks, + 'sections': instance.sections, + 'showtoc': instance.showtoc, + 'displaytitle': instance.displaytitle, + 'modules': instance.modules, + 'modulescripts': instance.modulescripts, + 'modulestyles': instance.modulestyles, + 'jsconfigvars': instance.jsconfigvars, + 'iwlinks': instance.iwlinks, + 'properties': instance.properties, + }; + +MWParseResponse _$MWParseResponseFromJson(Map json) => + MWParseResponse( + parse: MWParseInfo.fromJson(json['parse'] as Map), + ); + +Map _$MWParseResponseToJson(MWParseResponse instance) => + { + 'parse': instance.parse, + }; diff --git a/lib/app.dart b/lib/app.dart index c2bd01b..d8b004d 100755 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:isekai_wiki/models/settings.dart'; +import 'package:isekai_wiki/models/user.dart'; import 'models/model.dart'; import 'pages/tab_page.dart'; import 'styles.dart'; @@ -11,6 +13,9 @@ class IsekaiWikiApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { + Get.put(UserController()); + Get.put(AppSettingsController()); + return Material( child: GetCupertinoApp( title: '异世界百科', diff --git a/lib/components/page_card.dart b/lib/components/page_card.dart index 02640b9..e494dc7 100755 --- a/lib/components/page_card.dart +++ b/lib/components/page_card.dart @@ -19,8 +19,7 @@ typedef PageInfoCallback = Future Function(PageInfo pageInfo); class PageCardStyles { static const double cardInnerHeight = 140; - static const cardInnerPadding = - EdgeInsets.only(top: 16, left: 20, right: 20, bottom: 12); + static const cardInnerPadding = EdgeInsets.only(top: 16, left: 20, right: 20, bottom: 12); static const double footerButtonSize = 30; static const double footerButtonInnerSize = 26; } @@ -133,8 +132,7 @@ class _PageCardState extends ReactiveState { isLoading: isLoading, skeleton: const SkeletonLine( style: SkeletonLineStyle( - width: PageCardStyles.footerButtonSize, - height: PageCardStyles.footerButtonSize), + width: PageCardStyles.footerButtonSize, height: PageCardStyles.footerButtonSize), ), child: child, ); @@ -147,11 +145,9 @@ class _PageCardState extends ReactiveState { isLoading: c.isLoading.value, skeleton: SkeletonLine( style: SkeletonLineStyle( - height: (Styles.pageCardTitle.fontSize! + 4) * textScale, - randomLength: true), + height: (Styles.pageCardTitle.fontSize! + 4) * textScale, randomLength: true), ), - child: Text(c.pageInfo.value?.mainTitle ?? "页面信息丢失", - style: Styles.pageCardTitle), + child: Text(c.pageInfo.value?.mainTitle ?? "页面信息丢失", style: Styles.pageCardTitle), ), ); } @@ -170,17 +166,14 @@ class _PageCardState extends ReactiveState { ? SkeletonParagraph( style: SkeletonParagraphStyle( lines: 3, - padding: const EdgeInsets.symmetric( - vertical: 4, horizontal: 0), + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 0), lineStyle: SkeletonLineStyle( randomLength: true, - height: Styles.pageCardDescription.fontSize! * - textScale), + height: Styles.pageCardDescription.fontSize! * textScale), ), ) : Text(c.pageInfo.value?.description ?? "没有简介", - overflow: TextOverflow.fade, - style: Styles.pageCardDescription), + overflow: TextOverflow.fade, style: Styles.pageCardDescription), ), const SizedBox(width: 10), Skeleton( @@ -210,9 +203,7 @@ class _PageCardState extends ReactiveState { style: const TextStyle(color: Colors.black54)), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap) : const SizedBox(), - c.pageInfo.value?.mainCategory != null - ? const SizedBox(width: 10) - : const SizedBox(), + c.pageInfo.value?.mainCategory != null ? const SizedBox(width: 10) : const SizedBox(), // 发布日期 Skeleton( isLoading: c.isLoading.value, diff --git a/lib/components/recent_page_list.dart b/lib/components/recent_page_list.dart index f2c9842..850b1b2 100755 --- a/lib/components/recent_page_list.dart +++ b/lib/components/recent_page_list.dart @@ -28,8 +28,7 @@ class RecentPageListController extends GetxController { @override void onInit() { scrollController?.addListener(() { - if (scrollController!.position.pixels > - scrollController!.position.maxScrollExtent - 10) { + if (scrollController!.position.pixels > scrollController!.position.maxScrollExtent - 10) { // 滚动到底部 if (hasNextPage.value && !isLoading.value) { loadNextPages(); @@ -139,15 +138,15 @@ class RecentPageList extends StatelessWidget { return Column( key: key, children: [ - for (var i = 0; i < 6; i++) PageCard(key: ValueKey(i), isLoading: true), + for (var i = 0; i < 6; i++) PageCard(key: ValueKey("rpl-card-$i"), isLoading: true), ], ); } - Widget _buildPageCard(int index, PageInfo pageInfo, - RecentPageListController c, FavoriteListController fc) { + Widget _buildPageCard( + int index, PageInfo pageInfo, RecentPageListController c, FavoriteListController fc) { return PageCard( - key: ValueKey(index), + key: ValueKey("rpl-card-$index"), pageInfo: c.pageList[index], isFavorite: fc.isFavorite(c.pageList[index]), onSetFavorite: fc.setFavorite, @@ -163,13 +162,11 @@ class RecentPageList extends StatelessWidget { (context, index) { if (index == 0) { // 第一页数据,直接加载 - return Obx(() { - if (c.pageList.isEmpty) { - return _buildSkeletonList(); - } else { - return _buildPageCard(index, c.pageList[index], c, flc); - } - }); + if (c.pageList.isEmpty) { + return _buildSkeletonList(); + } else { + return _buildPageCard(index, c.pageList[index], c, flc); + } } // 后续页数据 @@ -180,16 +177,14 @@ class RecentPageList extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Center( - child: Obx( - () => c.isLoading.value - ? const CupertinoActivityIndicator( - radius: 14, - ) - : const SizedBox( - width: 28, - height: 28, - ), - ), + child: c.isLoading.value + ? const CupertinoActivityIndicator( + radius: 14, + ) + : const SizedBox( + width: 28, + height: 28, + ), ), ); } @@ -203,8 +198,10 @@ class RecentPageList extends StatelessWidget { Widget build(BuildContext context) { Get.put(RecentPageListController(scrollController: scrollController)); - return Obx(() => SliverList( - delegate: _buildPageListDelegate(), - )); + return Obx( + () => SliverList( + delegate: _buildPageListDelegate(), + ), + ); } } diff --git a/lib/components/safearea_builder.dart b/lib/components/safearea_builder.dart new file mode 100644 index 0000000..dfd4984 --- /dev/null +++ b/lib/components/safearea_builder.dart @@ -0,0 +1,25 @@ +import 'package:flutter/widgets.dart'; + +class SafeAreaBuilder extends StatelessWidget { + final Widget Function(BuildContext context, EdgeInsets padding) builder; + final bool maintainBottomViewPadding; + + const SafeAreaBuilder({ + super.key, + required this.builder, + this.maintainBottomViewPadding = false, + }); + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMediaQuery(context)); + final MediaQueryData data = MediaQuery.of(context); + EdgeInsets padding = data.padding; + // Bottom padding has been consumed - i.e. by the keyboard + if (maintainBottomViewPadding) { + padding = padding.copyWith(bottom: data.viewPadding.bottom); + } + + return builder(context, padding); + } +} diff --git a/lib/components/wikipage_parser.dart b/lib/components/wikipage_parser.dart new file mode 100644 index 0000000..3eadeea --- /dev/null +++ b/lib/components/wikipage_parser.dart @@ -0,0 +1,145 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:get/get.dart'; +import 'package:isekai_wiki/global.dart'; +import 'package:isekai_wiki/models/settings.dart'; +import 'package:isekai_wiki/reactive/reactive.dart'; +import 'package:isekai_wiki/styles.dart'; + +class WikiPageParserController extends GetxController { + InAppWebViewController? webviewCotroller; + + var contentHtml = "".obs; + var safeAreaPadding = const EdgeInsets.all(0).obs; + + var loading = true.obs; + + @override + void onInit() { + super.onInit(); + + ever(contentHtml, (_) { + loading.value = true; + + if (contentHtml.value.isNotEmpty) { + webviewCotroller?.loadData( + data: contentHtml.value, + baseUrl: Uri.parse(Global.wikiHomeUrl), + ); + } + }); + } + + void onWebViewCreated(InAppWebViewController controller) { + webviewCotroller = controller; + + webviewCotroller?.loadData( + data: contentHtml.value, + baseUrl: Uri.parse(Global.wikiHomeUrl), + ); + } + + void onPageCommitVisible(InAppWebViewController controller, Uri? uri) { + debugPrint("loaded"); + controller.injectCSSCode(source: """ +body { + padding-top: ${safeAreaPadding.value.top}px; + padding-bottom: ${safeAreaPadding.value.bottom}px; + padding-left: ${safeAreaPadding.value.left}px; + padding-right: ${safeAreaPadding.value.right}px; +} +"""); + controller.evaluateJavascript(source: """ +var metaEl = document.createElement("meta"); +metaEl.name = "viewport"; +metaEl.content = "width=device-width, initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0"; +document.head.appendChild(metaEl); +"""); + + if (contentHtml.value.isNotEmpty) { + Future.delayed(const Duration(milliseconds: 100)).then((value) { + loading.value = false; + }); + } + } +} + +class WikiPageParser extends StatefulWidget { + final String? contentHtml; + final EdgeInsets? padding; + + const WikiPageParser({super.key, this.contentHtml, this.padding}); + + @override + State createState() { + return _WikiParserState(); + } +} + +class _WikiParserState extends ReactiveState { + var c = WikiPageParserController(); + + @override + void initState() { + super.initState(); + c = Get.put(c); + } + + @override + void receiveProps() { + c.contentHtml.value = widget.contentHtml ?? ""; + c.safeAreaPadding.value = widget.padding ?? const EdgeInsets.all(0); + } + + Widget _buildRender() { + return ListView( + children: [ + Container( + color: Styles.panelBackgroundColor, + child: SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Html( + data: c.contentHtml.value, + ), + ], + ), + ), + ), + ), + ], + ); + } + + Widget _buildWebview() { + return Obx( + () => Stack( + children: [ + Opacity( + opacity: c.loading.value ? 0 : 1, + child: InAppWebView( + onWebViewCreated: c.onWebViewCreated, + onPageCommitVisible: c.onPageCommitVisible, + ), + ), + if (c.loading.value) + const Center( + child: CupertinoActivityIndicator(radius: 20), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + var sc = Get.find(); + return Obx(() => sc.betaPageRender.value ? _buildRender() : _buildWebview()); + } +} diff --git a/lib/global.dart b/lib/global.dart index 174d4ef..63796f1 100755 --- a/lib/global.dart +++ b/lib/global.dart @@ -1,14 +1,25 @@ import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; typedef VoidFutureCallback = Future Function(); typedef BoolFutureCallback = Future Function(); class Global { - static String isekaiWikiHomeUrl = "https://www.isekai.cn/"; + static const String wikiApiUrl = "https://www.isekai.cn/api.php"; + + static String wikiHomeUrl = "https://www.isekai.cn/"; + + static String restfulApiUrl = "https://www.isekai.cn/api/rest_v1"; + + static String pageUrl = "https://www.isekai.cn/index.php?title={{title}}"; + + static const String renderThemeFallback = "vector"; static PackageInfo? packageInfo; static String wikiLang = "zh-cn"; static String? webOrigin; + + static SharedPreferences? sharedPreferences; } diff --git a/lib/main.dart b/lib/main.dart index cbfc788..3f8b744 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,10 +2,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; import 'package:isekai_wiki/global.dart'; import 'package:isekai_wiki/models/lifecycle.dart'; +import 'package:isekai_wiki/models/user.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'app.dart'; @@ -20,10 +23,14 @@ Future init() async { // 设置web origin Global.webOrigin = Uri.base.origin; } + + Global.sharedPreferences = await SharedPreferences.getInstance(); } Future postInit() async { - if (!kIsWeb) { + Global.packageInfo = await PackageInfo.fromPlatform(); + + if (!kIsWeb && GetPlatform.isAndroid) { // 启用高刷 try { if (kDebugMode) { @@ -43,13 +50,17 @@ Future postInit() async { } } - Global.packageInfo = await PackageInfo.fromPlatform(); + WidgetsFlutterBinding.ensureInitialized(); + + if (GetPlatform.isAndroid) { + await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true); + } } -void main() { +Future main() async { Get.put(LifeCycleController()); - init(); + await init(); runApp(const IsekaiWikiApp()); diff --git a/lib/models/model.dart b/lib/models/model.dart index 7f17b52..d1a69ca 100644 --- a/lib/models/model.dart +++ b/lib/models/model.dart @@ -1,13 +1,10 @@ import 'package:get/get.dart'; import 'package:isekai_wiki/models/favorite_list.dart'; import 'package:isekai_wiki/models/history_list.dart'; -import 'package:isekai_wiki/models/user.dart'; class InitialBinding extends Bindings { @override void dependencies() { - Get.put(UserController()); - Get.lazyPut(() => FavoriteListController()); Get.lazyPut(() => HistoryListController()); } diff --git a/lib/models/settings.dart b/lib/models/settings.dart new file mode 100644 index 0000000..5969125 --- /dev/null +++ b/lib/models/settings.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../global.dart'; + +part 'settings.g.dart'; + +@JsonSerializable() +class AppSettings { + bool? betaPageRender; + + AppSettings({this.betaPageRender}); + + factory AppSettings.fromJson(Map json) => _$AppSettingsFromJson(json); + + Map toJson() => _$AppSettingsToJson(this); +} + +class AppSettingsController extends GetxController { + bool _ignoreSave = false; + + var betaPageRender = false.obs; + + @override + void onInit() { + super.onInit(); + + loadFromStorage(); + + everAll([betaPageRender], (_) { + saveToStorage(); + }); + } + + /// 从本地存储读取 + Future loadFromStorage() async { + try { + final prefs = await SharedPreferences.getInstance(); + var settingsJson = prefs.getString("settings"); + if (settingsJson == null) return; + + var settingsObject = jsonDecode(settingsJson); + if (settingsObject == null) return; + + var settingsData = AppSettings.fromJson(settingsObject); + + _ignoreSave = true; + betaPageRender.value = settingsData.betaPageRender ?? betaPageRender.value; + _ignoreSave = false; + } catch (ex) { + if (kDebugMode) { + print(ex); + } + } finally { + _ignoreSave = false; + } + } + + /// 保存到本地存储 + void saveToStorage() { + if (_ignoreSave) return; + + final prefs = Global.sharedPreferences!; + + var settingsData = AppSettings(betaPageRender: betaPageRender.value); + + var settingsJson = jsonEncode(settingsData.toJson()); + + prefs.setString("settings", settingsJson); + } +} diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart new file mode 100644 index 0000000..bbdb8a7 --- /dev/null +++ b/lib/models/settings.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'settings.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppSettings _$AppSettingsFromJson(Map json) => AppSettings( + betaPageRender: json['betaPageRender'] as bool?, + ); + +Map _$AppSettingsToJson(AppSettings instance) => + { + 'betaPageRender': instance.betaPageRender, + }; diff --git a/lib/models/site_config.dart b/lib/models/site_config.dart new file mode 100644 index 0000000..eae4927 --- /dev/null +++ b/lib/models/site_config.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../global.dart'; + +part 'site_config.g.dart'; + +@JsonSerializable() +class SiteConfig { + List moduleStyles; + List moduleScripts; + String renderTheme; + + SiteConfig({ + this.moduleStyles = const [], + this.moduleScripts = const [], + this.renderTheme = Global.renderThemeFallback, + }); + + factory SiteConfig.fromJson(Map json) => _$SiteConfigFromJson(json); + + Map toJson() => _$SiteConfigToJson(this); +} + +class AppSettingsController extends GetxController { + bool _ignoreSave = false; + + var betaPageRender = false.obs; + + @override + void onInit() { + super.onInit(); + + loadFromStorage(); + + everAll([betaPageRender], (_) { + saveToStorage(); + }); + } + + /// 从本地存储读取 + Future loadFromStorage() async { + try { + final prefs = await SharedPreferences.getInstance(); + var settingsJson = prefs.getString("siteConfigCache"); + if (settingsJson == null) return; + + var settingsObject = jsonDecode(settingsJson); + if (settingsObject == null) return; + + var settingsData = SiteConfig.fromJson(settingsObject); + + _ignoreSave = true; + // betaPageRender.value = settingsData.betaPageRender ?? betaPageRender.value; + _ignoreSave = false; + } catch (ex) { + if (kDebugMode) { + print(ex); + } + } finally { + _ignoreSave = false; + } + } + + /// 保存到本地存储 + void saveToStorage() { + if (_ignoreSave) return; + + final prefs = Global.sharedPreferences!; + + var settingsData = SiteConfig(); + + var settingsJson = jsonEncode(settingsData.toJson()); + + prefs.setString("siteConfigCache", settingsJson); + } +} diff --git a/lib/models/site_config.g.dart b/lib/models/site_config.g.dart new file mode 100644 index 0000000..a2c3021 --- /dev/null +++ b/lib/models/site_config.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'site_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SiteConfig _$SiteConfigFromJson(Map json) => SiteConfig( + moduleStyles: (json['moduleStyles'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + moduleScripts: (json['moduleScripts'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + renderTheme: json['renderTheme'] as String? ?? Global.renderThemeFallback, + ); + +Map _$SiteConfigToJson(SiteConfig instance) => + { + 'moduleStyles': instance.moduleStyles, + 'moduleScripts': instance.moduleScripts, + 'renderTheme': instance.renderTheme, + }; diff --git a/lib/models/user.dart b/lib/models/user.dart index 2a8ed58..c225d00 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; +import 'package:isekai_wiki/global.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; part 'user.g.dart'; @@ -14,20 +14,14 @@ class UserInfo { String? nickName; String? avatarUrl; - UserInfo( - {required this.userId, - required this.userName, - this.nickName, - this.avatarUrl}); + UserInfo({required this.userId, required this.userName, this.nickName, this.avatarUrl}); - factory UserInfo.fromJson(Map json) => - _$UserInfoFromJson(json); + factory UserInfo.fromJson(Map json) => _$UserInfoFromJson(json); Map toJson() => _$UserInfoToJson(this); } class UserController extends GetxController { - bool _isReady = false; bool _ignoreSave = false; var loginRequestToken = "".obs; @@ -46,34 +40,24 @@ class UserController extends GetxController { return nickName.isNotEmpty ? nickName.string : userName.string; } - Future initialize() async { - if (!_isReady) { - await loadFromStorage(); - _isReady = true; + @override + void onInit() async { + super.onInit(); - postInit().catchError((err) { - if (kDebugMode) { - print(err); - } - }); - } + loadFromStorage(); ever(loginRequestToken, (String token) { saveToStorage(); }); } - Future postInit() async { - await updateProfile(); - } - /// 更新用户资料,并检测登录状态 Future updateProfile() async {} /// 从本地存储读取 - Future loadFromStorage() async { + void loadFromStorage() { try { - final prefs = await SharedPreferences.getInstance(); + final prefs = Global.sharedPreferences!; var userInfoJson = prefs.getString("userInfo"); if (userInfoJson == null) return; @@ -82,10 +66,12 @@ class UserController extends GetxController { var userInfo = UserInfo.fromJson(userInfoObject); + _ignoreSave = true; userId.value = userInfo.userId; userName.value = userInfo.userName; nickName.value = userInfo.nickName ?? ""; avatarUrl.value = userInfo.avatarUrl ?? ""; + _ignoreSave = false; var savedLoginRequestToken = prefs.getString("loginRequestToken"); if (savedLoginRequestToken != null) { @@ -97,14 +83,16 @@ class UserController extends GetxController { if (kDebugMode) { print(ex); } + } finally { + _ignoreSave = false; } } /// 保存到本地存储 - Future saveToStorage() async { + void saveToStorage() { if (_ignoreSave) return; - final prefs = await SharedPreferences.getInstance(); + final prefs = Global.sharedPreferences!; var userInfo = UserInfo( userId: userId.value, diff --git a/lib/pages/about.dart b/lib/pages/about.dart index 3f54532..392200b 100755 --- a/lib/pages/about.dart +++ b/lib/pages/about.dart @@ -14,7 +14,7 @@ class AboutPageController extends GetxController { Future handleMainPageLinkClick() async { if (GetPlatform.isAndroid || GetPlatform.isIOS) { await FlutterWebBrowser.openWebPage( - url: Global.isekaiWikiHomeUrl, + url: Global.wikiHomeUrl, customTabsOptions: const CustomTabsOptions( defaultColorSchemeParams: CustomTabsColorSchemeParams( toolbarColor: Colors.black87, @@ -33,8 +33,7 @@ class AboutPage extends StatelessWidget { var c = Get.put(AboutPageController()); return IsekaiPageScaffold( - navigationBar: const IsekaiNavigationBar( - middle: Text('关于'), previousPageTitle: "设置"), + navigationBar: const IsekaiNavigationBar(middle: Text('关于'), previousPageTitle: "设置"), child: ListView( children: [ const SizedBox(height: 18), @@ -44,8 +43,7 @@ class AboutPage extends StatelessWidget { top: false, bottom: false, child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), child: Column( children: const [ Text("异世界百科APP", style: Styles.articleTitle), @@ -61,8 +59,7 @@ class AboutPage extends StatelessWidget { backgroundColor: Styles.themePageBackgroundColor, children: [ CupertinoListTile.notched( - title: const Text('异世界百科', - style: TextStyle(color: Styles.linkColor)), + title: const Text('异世界百科', style: TextStyle(color: Styles.linkColor)), leading: const DummyIcon( color: CupertinoColors.systemBlue, icon: CupertinoIcons.globe, diff --git a/lib/pages/article.dart b/lib/pages/article.dart index 74bdf95..8671982 100755 --- a/lib/pages/article.dart +++ b/lib/pages/article.dart @@ -1,6 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:isekai_wiki/api/restbase/page.dart'; +import 'package:isekai_wiki/components/safearea_builder.dart'; +import 'package:isekai_wiki/components/wikipage_parser.dart'; import '../components/isekai_nav_bar.dart'; import '../components/isekai_page_scaffold.dart'; @@ -12,11 +14,7 @@ class MinimumArticleData { final String? mainCategory; final DateTime? updateTime; - MinimumArticleData( - {required this.title, - this.description, - this.mainCategory, - this.updateTime}); + MinimumArticleData({required this.title, this.description, this.mainCategory, this.updateTime}); } class ArticleCategoryData { @@ -72,29 +70,11 @@ class _ArticlePageState extends State { navigationBar: IsekaiNavigationBar( middle: Text(title), ), - child: ListView( - children: [ - Container( - color: Styles.panelBackgroundColor, - child: SafeArea( - top: false, - bottom: false, - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (contentHtml == "") Text(description), - Html( - data: contentHtml, - ), - ], - ), - ), - ), - ), - ], + child: SafeAreaBuilder( + builder: (context, padding) => WikiPageParser( + contentHtml: contentHtml, + padding: padding, + ), ), ); } diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 31af675..176bf54 100755 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -15,8 +15,7 @@ import '../styles.dart'; enum HomeTabs { newest, followed } -class HomeController extends GetxController - with GetSingleTickerProviderStateMixin { +class HomeController extends GetxController with GetSingleTickerProviderStateMixin { var showNavSearchButton = false.obs; var isScrolling = false.obs; @@ -94,8 +93,7 @@ class HomeTab extends StatelessWidget { duration: const Duration(milliseconds: 100), child: CupertinoButton( padding: EdgeInsets.zero, - child: const Icon(CupertinoIcons.search, - size: 26, color: Styles.themeNavTitleColor), + child: const Icon(CupertinoIcons.search, size: 26, color: Styles.themeNavTitleColor), onPressed: () { onSearchClick?.call(); }, @@ -113,8 +111,7 @@ class HomeTab extends StatelessWidget { duration: const Duration(milliseconds: 100), child: CupertinoButton( padding: EdgeInsets.zero, - child: const Icon(CupertinoIcons.search, - size: 26, color: Styles.themeNavTitleColor), + child: const Icon(CupertinoIcons.search, size: 26, color: Styles.themeNavTitleColor), onPressed: () { onSearchClick?.call(); }, @@ -139,8 +136,7 @@ class HomeTab extends StatelessWidget { leading: _buildNotificationIconButton(), backgroundColor: Styles.themeMainColor, brightness: Brightness.dark, - largeTitle: const Text('首页', - style: TextStyle(color: Styles.themeNavTitleColor)), + largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)), border: Border.all(style: BorderStyle.none), trailing: _buildSearchIconButton(), ), @@ -173,12 +169,10 @@ class HomeTab extends StatelessWidget { children: [ Container( padding: const EdgeInsets.all(1), - child: const Icon(CupertinoIcons.search, - color: Colors.black54), + child: const Icon(CupertinoIcons.search, color: Colors.black54), ), const Text("搜索页面...", - textAlign: TextAlign.center, - style: TextStyle(color: Colors.black54)) + textAlign: TextAlign.center, style: TextStyle(color: Colors.black54)) ], ), ), @@ -208,10 +202,7 @@ class HomeTab extends StatelessWidget { indicatorColor: Styles.themeMainColor, labelColor: Styles.themeMainColor, unselectedLabelColor: Colors.black45, - tabs: const [ - CollapsedTabText('最新'), - CollapsedTabText('关注') - ], + tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')], onTap: (int selected) {}, ), ), @@ -229,7 +220,6 @@ class HomeTab extends StatelessWidget { bottom: false, minimum: const EdgeInsets.only(top: 12, bottom: 12), sliver: RecentPageList( - key: const Key("rpl"), scrollController: c.scrollController, ), ), @@ -257,8 +247,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { double get maxExtent => max(maxHeight, minHeight); @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox.expand(child: child); } diff --git a/pubspec.lock b/pubspec.lock index 54b8c96..a3d6db8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,217 +5,224 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "50.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.2.0" animations: dependency: "direct main" description: name: animations - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.7" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" async: dependency: "direct main" description: name: async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.9.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.3" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "7.2.7" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "8.4.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" chewie: dependency: transitive description: name: chewie - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.6" chewie_audio: dependency: transitive description: name: chewie_audio - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.4.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.16.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" cupertino_lists: dependency: "direct main" description: name: cupertino_lists - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.4" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.4" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" flutter: @@ -227,28 +234,42 @@ packages: dependency: "direct main" description: name: flutter_displaymode - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.2" + flutter_hooks: + dependency: transitive + description: + name: flutter_hooks + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.18.5+1" flutter_html: dependency: "direct main" description: name: flutter_html - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.7.2+3" flutter_layout_grid: dependency: transitive description: name: flutter_layout_grid - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.6" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" flutter_localizations: @@ -260,21 +281,21 @@ packages: dependency: transitive description: name: flutter_math_fork - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.5.0" flutter_scale_tap: dependency: "direct main" description: name: flutter_scale_tap - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" flutter_svg: dependency: transitive description: name: flutter_svg - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.23.0+1" flutter_test: @@ -286,7 +307,7 @@ packages: dependency: "direct main" description: name: flutter_web_browser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.1" flutter_web_plugins: @@ -298,371 +319,378 @@ packages: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.0" get: dependency: "direct main" description: name: get - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.6.5" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" html: dependency: transitive description: name: html - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.15.1" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.2" intl: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.4" json_annotation: dependency: "direct main" description: name: json_annotation - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.7.0" json_serializable: dependency: "direct main" description: name: json_serializable - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.5.4" like_button: dependency: "direct main" description: name: like_button - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.5" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.5" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" modal_bottom_sheet: dependency: "direct main" description: name: modal_bottom_sheet - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" numerus: dependency: transitive description: name: numerus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.2" path_drawing: dependency: transitive description: name: path_drawing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.5.1+1" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.7" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.5" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.2.4" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.5" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" pull_down_button: dependency: "direct main" description: name: pull_down_button - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.1" quiver: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.1" + ruby_text: + dependency: "direct main" + description: + name: ruby_text + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.14" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" skeletons: dependency: "direct main" description: name: skeletons - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.3" sky_engine: @@ -674,238 +702,238 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.6" source_helper: dependency: transitive description: name: source_helper - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.12" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" tuple: dependency: transitive description: name: tuple - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" video_player: dependency: transitive description: name: video_player - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.10" video_player_android: dependency: transitive description: name: video_player_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.10" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.8" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.1" video_player_web: dependency: transitive description: name: video_player_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.13" wakelock: dependency: transitive description: name: wakelock - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.2" wakelock_macos: dependency: transitive description: name: wakelock_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.0" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.0" wakelock_web: dependency: transitive description: name: wakelock_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.0" wakelock_windows: dependency: transitive description: name: wakelock_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.1" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" web_smooth_scroll: dependency: "direct main" description: name: web_smooth_scroll - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" webview_flutter: dependency: transitive description: name: webview_flutter - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.8.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.10.4" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.5" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.9.5" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0+2" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.4.1" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" sdks: diff --git a/pubspec.yaml b/pubspec.yaml index 67854f5..81b126c 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,10 +49,12 @@ dependencies: skeletons: ^0.0.3 modal_bottom_sheet: ^2.1.2 flutter_web_browser: ^0.17.1 + flutter_inappwebview: ^5.7.2+3 web_smooth_scroll: ^1.0.0 json_serializable: ^6.5.4 json_annotation: ^4.7.0 flutter_html: ^2.2.1 + ruby_text: ^3.0.1 package_info_plus: ^3.0.2 pull_down_button: ^0.4.1 http: ^0.13.5 diff --git a/web/index.html b/web/index.html index 4729397..9dcfe17 100755 --- a/web/index.html +++ b/web/index.html @@ -31,6 +31,8 @@ isekai_wiki_app + +