将页面模式从Flutter渲染改为Webview渲染

main
落雨楓 2 years ago
parent fcd6df92f8
commit 949752dbd1

@ -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

@ -0,0 +1 @@
class MWApiParse {}

@ -11,11 +11,13 @@ PageInfo _$PageInfoFromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic>);
)..thumbnail = json['thumbnail'] == null
? null
: PageImageInfo.fromJson(json['thumbnail'] as Map<String, dynamic>);
Map<String, dynamic> _$PageInfoToJson(PageInfo instance) => <String, dynamic>{
'pageid': instance.pageid,

@ -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<String, dynamic> json) =>
_$MWParseCategoryInfoFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) =>
_$MWParseLangLinkInfoFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) =>
_$MWParsePageLinkInfoFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) =>
_$MWParseSectionInfoFromJson(json);
Map<String, dynamic> toJson() => _$MWParseSectionInfoToJson(this);
}
@JsonSerializable()
class MWParseInfo {
String title;
int pageid;
int revid;
String text;
List<MWParseLangLinkInfo> langlink;
List<MWParseCategoryInfo> categories;
List<MWParsePageLinkInfo> links;
List<MWParsePageLinkInfo> templates;
List<String> images;
List<String> externallinks;
List<MWParseSectionInfo> sections;
bool showtoc;
String displaytitle;
List<String> modules;
List<String> modulescripts;
List<String> modulestyles;
Map<String, dynamic> jsconfigvars;
Map<String, dynamic> iwlinks;
Map<String, dynamic> 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<String, dynamic> json) => _$MWParseInfoFromJson(json);
Map<String, dynamic> toJson() => _$MWParseInfoToJson(this);
}
@JsonSerializable()
class MWParseResponse {
MWParseInfo parse;
MWParseResponse({required this.parse});
factory MWParseResponse.fromJson(Map<String, dynamic> json) => _$MWParseResponseFromJson(json);
Map<String, dynamic> toJson() => _$MWParseResponseToJson(this);
}

@ -0,0 +1,168 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'parse.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MWParseCategoryInfo _$MWParseCategoryInfoFromJson(Map<String, dynamic> json) =>
MWParseCategoryInfo(
sortkey: json['sortkey'] as String? ?? "",
category: json['category'] as String? ?? "",
);
Map<String, dynamic> _$MWParseCategoryInfoToJson(
MWParseCategoryInfo instance) =>
<String, dynamic>{
'sortkey': instance.sortkey,
'category': instance.category,
};
MWParseLangLinkInfo _$MWParseLangLinkInfoFromJson(Map<String, dynamic> 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<String, dynamic> _$MWParseLangLinkInfoToJson(
MWParseLangLinkInfo instance) =>
<String, dynamic>{
'lang': instance.lang,
'url': instance.url,
'langname': instance.langname,
'autonym': instance.autonym,
'title': instance.title,
};
MWParsePageLinkInfo _$MWParsePageLinkInfoFromJson(Map<String, dynamic> json) =>
MWParsePageLinkInfo(
ns: json['ns'] as int? ?? -1,
title: json['title'] as String? ?? "",
exists: json['exists'] as bool? ?? false,
);
Map<String, dynamic> _$MWParsePageLinkInfoToJson(
MWParsePageLinkInfo instance) =>
<String, dynamic>{
'ns': instance.ns,
'title': instance.title,
'exists': instance.exists,
};
MWParseSectionInfo _$MWParseSectionInfoFromJson(Map<String, dynamic> 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<String, dynamic> _$MWParseSectionInfoToJson(MWParseSectionInfo instance) =>
<String, dynamic>{
'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<String, dynamic> 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<dynamic>?)
?.map((e) =>
MWParseLangLinkInfo.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
categories: (json['categories'] as List<dynamic>?)
?.map((e) =>
MWParseCategoryInfo.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
links: (json['links'] as List<dynamic>?)
?.map((e) =>
MWParsePageLinkInfo.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
templates: (json['templates'] as List<dynamic>?)
?.map((e) =>
MWParsePageLinkInfo.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
images: (json['images'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
externallinks: (json['externallinks'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
sections: (json['sections'] as List<dynamic>?)
?.map(
(e) => MWParseSectionInfo.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
showtoc: json['showtoc'] as bool? ?? true,
displaytitle: json['displaytitle'] as String? ?? "",
modules: (json['modules'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
modulescripts: (json['modulescripts'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
modulestyles: (json['modulestyles'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
jsconfigvars: json['jsconfigvars'] as Map<String, dynamic>? ?? const {},
iwlinks: json['iwlinks'] as Map<String, dynamic>? ?? const {},
properties: json['properties'] as Map<String, dynamic>? ?? const {},
);
Map<String, dynamic> _$MWParseInfoToJson(MWParseInfo instance) =>
<String, dynamic>{
'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<String, dynamic> json) =>
MWParseResponse(
parse: MWParseInfo.fromJson(json['parse'] as Map<String, dynamic>),
);
Map<String, dynamic> _$MWParseResponseToJson(MWParseResponse instance) =>
<String, dynamic>{
'parse': instance.parse,
};

@ -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: '异世界百科',

@ -19,8 +19,7 @@ typedef PageInfoCallback = Future<void> 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<PageCard> {
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<PageCard> {
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<PageCard> {
? 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<PageCard> {
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,

@ -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(),
),
);
}
}

@ -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);
}
}

@ -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<StatefulWidget> createState() {
return _WikiParserState();
}
}
class _WikiParserState extends ReactiveState<WikiPageParser> {
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: <Widget>[
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<AppSettingsController>();
return Obx(() => sc.betaPageRender.value ? _buildRender() : _buildWebview());
}
}

@ -1,14 +1,25 @@
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
typedef VoidFutureCallback = Future<void> Function();
typedef BoolFutureCallback = Future<bool> 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;
}

@ -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<void> init() async {
// web origin
Global.webOrigin = Uri.base.origin;
}
Global.sharedPreferences = await SharedPreferences.getInstance();
}
Future<void> postInit() async {
if (!kIsWeb) {
Global.packageInfo = await PackageInfo.fromPlatform();
if (!kIsWeb && GetPlatform.isAndroid) {
//
try {
if (kDebugMode) {
@ -43,13 +50,17 @@ Future<void> postInit() async {
}
}
Global.packageInfo = await PackageInfo.fromPlatform();
WidgetsFlutterBinding.ensureInitialized();
if (GetPlatform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
}
}
void main() {
Future<void> main() async {
Get.put(LifeCycleController());
init();
await init();
runApp(const IsekaiWikiApp());

@ -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>(UserController());
Get.lazyPut<FavoriteListController>(() => FavoriteListController());
Get.lazyPut<HistoryListController>(() => HistoryListController());
}

@ -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<String, dynamic> json) => _$AppSettingsFromJson(json);
Map<String, dynamic> toJson() => _$AppSettingsToJson(this);
}
class AppSettingsController extends GetxController {
bool _ignoreSave = false;
var betaPageRender = false.obs;
@override
void onInit() {
super.onInit();
loadFromStorage();
everAll([betaPageRender], (_) {
saveToStorage();
});
}
///
Future<void> 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);
}
}

@ -0,0 +1,16 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
betaPageRender: json['betaPageRender'] as bool?,
);
Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
<String, dynamic>{
'betaPageRender': instance.betaPageRender,
};

@ -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<String> moduleStyles;
List<String> moduleScripts;
String renderTheme;
SiteConfig({
this.moduleStyles = const [],
this.moduleScripts = const [],
this.renderTheme = Global.renderThemeFallback,
});
factory SiteConfig.fromJson(Map<String, dynamic> json) => _$SiteConfigFromJson(json);
Map<String, dynamic> toJson() => _$SiteConfigToJson(this);
}
class AppSettingsController extends GetxController {
bool _ignoreSave = false;
var betaPageRender = false.obs;
@override
void onInit() {
super.onInit();
loadFromStorage();
everAll([betaPageRender], (_) {
saveToStorage();
});
}
///
Future<void> 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);
}
}

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'site_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SiteConfig _$SiteConfigFromJson(Map<String, dynamic> json) => SiteConfig(
moduleStyles: (json['moduleStyles'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
moduleScripts: (json['moduleScripts'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
renderTheme: json['renderTheme'] as String? ?? Global.renderThemeFallback,
);
Map<String, dynamic> _$SiteConfigToJson(SiteConfig instance) =>
<String, dynamic>{
'moduleStyles': instance.moduleStyles,
'moduleScripts': instance.moduleScripts,
'renderTheme': instance.renderTheme,
};

@ -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<String, dynamic> json) =>
_$UserInfoFromJson(json);
factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
Map<String, dynamic> 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<void> 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<void> postInit() async {
await updateProfile();
}
///
Future<void> updateProfile() async {}
///
Future<void> 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<void> saveToStorage() async {
void saveToStorage() {
if (_ignoreSave) return;
final prefs = await SharedPreferences.getInstance();
final prefs = Global.sharedPreferences!;
var userInfo = UserInfo(
userId: userId.value,

@ -14,7 +14,7 @@ class AboutPageController extends GetxController {
Future<void> 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 <Widget>[
Text("异世界百科APP", style: Styles.articleTitle),
@ -61,8 +59,7 @@ class AboutPage extends StatelessWidget {
backgroundColor: Styles.themePageBackgroundColor,
children: <CupertinoListTile>[
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,

@ -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<ArticlePage> {
navigationBar: IsekaiNavigationBar(
middle: Text(title),
),
child: ListView(
children: <Widget>[
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,
),
),
);
}

@ -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);
}

File diff suppressed because it is too large Load Diff

@ -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

@ -32,6 +32,8 @@
<title>isekai_wiki_app</title>
<link rel="manifest" href="manifest.json">
<script type="application/javascript" src="/assets/packages/flutter_inappwebview/assets/web/web_support.js" defer></script>
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;

Loading…
Cancel
Save