完成页面信息的样式

main
落雨楓 2 years ago
parent 1aab54a6d8
commit 23264dfd7e

@ -5,15 +5,40 @@ var titleList = [];
var currentTitle = '_top';
var titleOffset = 10;
window.MugenApp = {
emit: function() {
window.flutter_inappwebview.callHandler.apply(this, arguments);
},
scrollToTitle: function (anchor) {
var el = document.getElementById(anchor);
if (el) {
var scrollTop = window.scrollY + el.getBoundingClientRect().top;
if (navigator.safeArea) {
scrollTop -= navigator.safeArea.top + titleOffset;
}
scrollTop = Math.max(0, scrollTop);
enableScrollSpy = false;
window.scrollTo({
top: scrollTop,
});
setTimeout(function() {
enableScrollSpy = true;
}, 50);
}
},
};
function onReadyStateChange() {
if (_InAppWebViewReady && document.readyState === "complete") {
titleList = document.querySelectorAll('.mw-parser-output h1, .mw-parser-output h2, .mw-parser-output h3, .mw-parser-output h4, .mw-parser-output h5, .mw-parser-output h6');
window.flutter_inappwebview.callHandler('pageLoaded', true);
window.MugenApp.emit('pageLoaded', true);
}
}
window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
_InAppWebViewReady = true;
window.flutter_inappwebview.callHandler('bridgeConnected', true);
window.MugenApp.emit('bridgeConnected', true);
onReadyStateChange();
});
@ -67,28 +92,6 @@ document.addEventListener('scroll', debouce(function() {
if (newCurrentTitle && newCurrentTitle !== currentTitle) {
currentTitle = newCurrentTitle;
window.flutter_inappwebview.callHandler('sectionChange', newCurrentTitle);
window.MugenApp.emit('sectionChange', newCurrentTitle);
}
}, 200), { passive: true });
window.MugenApp = {
scrollToTitle: function (anchor) {
var el = document.getElementById(anchor);
if (el) {
var scrollTop = window.scrollY + el.getBoundingClientRect().top;
if (navigator.safeArea) {
scrollTop -= navigator.safeArea.top + titleOffset;
}
scrollTop = Math.max(0, scrollTop);
enableScrollSpy = false;
window.scrollTo({
top: scrollTop,
});
setTimeout(function() {
enableScrollSpy = true;
}, 50);
}
},
};

@ -7,6 +7,8 @@ PODS:
- flutter_inappwebview/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- flutter_share (0.0.1):
- Flutter
- flutter_web_browser (0.17.1):
- Flutter
- fluttertoast (0.0.2):
@ -18,7 +20,10 @@ PODS:
- OrderedSet (5.0.0)
- package_info_plus (0.4.5):
- Flutter
- path_provider_ios (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- share_plus (0.0.1):
- Flutter
- sqflite (0.0.2):
- Flutter
@ -36,10 +41,12 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- flutter_share (from `.symlinks/plugins/flutter_share/ios`)
- flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
@ -57,14 +64,18 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
flutter_share:
:path: ".symlinks/plugins/flutter_share/ios"
flutter_web_browser:
:path: ".symlinks/plugins/flutter_web_browser/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios:
@ -79,15 +90,17 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_share: 4be0208963c60b537e6255ed2ce1faae61cd9ac2
flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -217,6 +217,7 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -253,6 +254,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);

@ -6,6 +6,18 @@ import 'package:isekai_wiki/utils/utils.dart';
part 'page_info.freezed.dart';
part 'page_info.g.dart';
@freezed
class MWPageProtectionInfo with _$MWPageProtectionInfo {
factory MWPageProtectionInfo({
required String type,
required String level,
required String expiry,
}) = _MWPageProtectionInfo;
factory MWPageProtectionInfo.fromJson(Map<String, dynamic> json) =>
_$MWPageProtectionInfoFromJson(json);
}
@JsonSerializable()
class PageInfo {
int pageid;
@ -34,6 +46,12 @@ class PageInfo {
String? editurl;
String? canonicalurl;
int? watchers;
int? visitingwatchers;
MWPageProtectionInfo? protection;
List<String>? restrictiontypes;
PageInfo({
required this.pageid,
required this.ns,
@ -52,6 +70,10 @@ class PageInfo {
this.fullurl,
this.editurl,
this.canonicalurl,
this.watchers,
this.visitingwatchers,
this.protection,
this.restrictiontypes,
});
String get mainTitle {
@ -62,7 +84,8 @@ class PageInfo {
return null;
}
factory PageInfo.fromJson(Map<String, dynamic> json) => _$PageInfoFromJson(json);
factory PageInfo.fromJson(Map<String, dynamic> json) =>
_$PageInfoFromJson(json);
Map<String, dynamic> toJson() => _$PageInfoToJson(this);
@ -87,12 +110,15 @@ class PageInfo {
class PagesResponse with _$PagesResponse {
factory PagesResponse({required List<PageInfo> pages}) = _PageResponse;
factory PagesResponse.fromJson(Map<String, dynamic> json) => _$PagesResponseFromJson(json);
factory PagesResponse.fromJson(Map<String, dynamic> json) =>
_$PagesResponseFromJson(json);
}
@Freezed(copyWith: false)
class PageImageInfo with _$PageImageInfo {
factory PageImageInfo({required String source, int? width, int? height}) = _PageImageInfo;
factory PageImageInfo({required String source, int? width, int? height}) =
_PageImageInfo;
factory PageImageInfo.fromJson(Map<String, dynamic> json) => _$PageImageInfoFromJson(json);
factory PageImageInfo.fromJson(Map<String, dynamic> json) =>
_$PageImageInfoFromJson(json);
}

@ -79,8 +79,8 @@ class IsekaiWikiApp extends StatelessWidget {
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en'),
Locale('zh', 'CN'),
],
initialBinding: InitialBinding(),
home: _buildApp(context),
@ -97,7 +97,9 @@ class IsekaiWikiApp extends StatelessWidget {
? Styles.materialLightTheme
: Styles.materialDarkTheme,
child: CupertinoTheme(
data: Styles.cupertinoTheme.copyWith(brightness: brightness), child: child),
data:
Styles.cupertinoTheme.copyWith(brightness: brightness),
child: child),
),
);
}

@ -0,0 +1,111 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
class AutoWrap extends StatefulWidget {
final List<Widget> children;
const AutoWrap({super.key, required this.children});
@override
State<StatefulWidget> createState() => _AutoWrapState();
}
class _AutoWrapState extends State<AutoWrap> {
var _computed = false;
var _isFirstBuild = true;
final Map<int, double> _childWidth = {};
var _isRow = true;
void handleSizeChange(Size size, int id) {
_childWidth[id] = size.width;
}
@override
void didUpdateWidget(covariant AutoWrap oldWidget) {
super.didUpdateWidget(oldWidget);
if (_isFirstBuild) {
_isFirstBuild = false;
var sum = _childWidth.values.reduce((value, element) => value + element);
var width = context.size?.width ?? 0;
setState(() {
_computed = true;
_isRow = sum < width;
});
}
}
@override
Widget build(BuildContext context) {
if (!_computed) {
List<Widget> stackChildren = [];
for (var i = 0; i < widget.children.length; i++) {
// Only first child is visible
stackChildren.add(
Opacity(
opacity: i == 0 ? 1 : 0,
child: WidgetSizeOffsetWrapper(
id: i,
onSizeChange: handleSizeChange,
child: widget.children[i],
),
),
);
}
return Stack(
children: stackChildren,
);
} else {
if (_isRow) {
return Row(children: widget.children);
} else {
return Column(children: widget.children);
}
}
}
}
typedef OnWidgetSizeChange = void Function(Size size, int id);
class WidgetSizeRenderObject extends RenderProxyBox {
final OnWidgetSizeChange onSizeChange;
final int id;
Size? currentSize;
WidgetSizeRenderObject(this.onSizeChange, this.id);
@override
void performLayout() {
super.performLayout();
try {
Size? newSize = child?.size;
if (newSize != null && currentSize != newSize) {
currentSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) {
onSizeChange(newSize, id);
});
}
} catch (e) {
print(e);
}
}
}
class WidgetSizeOffsetWrapper extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onSizeChange;
final int id;
const WidgetSizeOffsetWrapper({
Key? key,
required this.onSizeChange,
required this.id,
required Widget child,
}) : super(key: key, child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return WidgetSizeRenderObject(onSizeChange, id);
}
}

@ -7,22 +7,28 @@ import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/components/flutter_scale_tap/flutter_scale_tap.dart';
import 'package:isekai_wiki/components/isekai_text.dart';
import 'package:isekai_wiki/components/utils.dart';
import 'package:isekai_wiki/pages/article.dart';
import 'package:isekai_wiki/pages/wiki/info.dart';
import 'package:isekai_wiki/pages/wiki/view.dart';
import 'package:like_button/like_button.dart';
import 'package:pull_down_button/pull_down_button.dart';
import 'package:skeletons/skeletons.dart';
typedef AddFavoriteCallback = Future<bool> Function(PageInfo pageInfo, bool localIsFavorite);
typedef AddFavoriteCallback = Future<bool> Function(
PageInfo pageInfo, bool localIsFavorite);
typedef PageInfoCallback = Future<void> Function(PageInfo pageInfo);
class PageCardStyles {
static const cardContainerHeight = 100.0;
static const cardContentHeight = 100.0;
static const cardHeaderPadding = EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 16);
static const cardHeaderCompactPadding = EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 10);
static const cardContentPadding = EdgeInsets.only(top: 0, left: 20, right: 20, bottom: 14);
static const cardFooterPadding = EdgeInsets.only(top: 0, left: 20, right: 20, bottom: 14);
static const cardHeaderPadding =
EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 16);
static const cardHeaderCompactPadding =
EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 10);
static const cardContentPadding =
EdgeInsets.only(top: 0, left: 20, right: 20, bottom: 14);
static const cardFooterPadding =
EdgeInsets.only(top: 0, left: 20, right: 20, bottom: 14);
static const titleFontSize = 24.0;
static const subTitleFontSize = 12.0;
static const contentFontSize = 14.0;
@ -95,8 +101,18 @@ class PageCard extends StatelessWidget {
}
}
void handlePageInfoClick() {
if (pageInfo != null) {}
void handlePageInfoClick() async {
if (pageInfo != null) {
var cPageInfo = pageInfo!;
await Navigator.of(Get.context!).push(
CupertinoPageRoute(
builder: (_) => WikiInfoPage(
targetPage: cPageInfo.title,
targetPageId: cPageInfo.pageid,
),
),
);
}
}
Future<void> handleCardClick() async {
@ -108,8 +124,9 @@ class PageCard extends StatelessWidget {
var cPageInfo = pageInfo!;
await Navigator.of(Get.context!).push(
CupertinoPageRoute(
builder: (_) => ArticlePage(
builder: (_) => WikiViewPage(
targetPage: cPageInfo.title,
targetPageId: cPageInfo.pageid,
initialArticleData: MinimumArticleData(
title: cPageInfo.mainTitle,
description: cPageInfo.description,
@ -127,7 +144,8 @@ class PageCard extends StatelessWidget {
isLoading: isLoading,
skeleton: const SkeletonLine(
style: SkeletonLineStyle(
width: PageCardStyles.footerButtonSize, height: PageCardStyles.footerButtonSize),
width: PageCardStyles.footerButtonSize,
height: PageCardStyles.footerButtonSize),
),
child: child,
);
@ -154,7 +172,8 @@ class PageCard extends StatelessWidget {
isLoading: isLoading,
skeleton: SkeletonLine(
style: SkeletonLineStyle(
height: (PageCardStyles.titleFontSize * 1.1) * textScale, randomLength: true),
height: (PageCardStyles.titleFontSize * 1.1) * textScale,
randomLength: true),
),
child: IsekaiText(
pageInfo?.mainTitle ?? "页面信息丢失",
@ -186,12 +205,15 @@ class PageCard extends StatelessWidget {
style: SkeletonParagraphStyle(
lines: 3,
padding: EdgeInsets.symmetric(
vertical: PageCardStyles.contentFontSize * textScale * 0.2,
vertical: PageCardStyles.contentFontSize *
textScale *
0.2,
horizontal: 0,
),
lineStyle: SkeletonLineStyle(
randomLength: true,
height: PageCardStyles.contentFontSize * textScale),
height:
PageCardStyles.contentFontSize * textScale),
),
),
)
@ -232,7 +254,9 @@ class PageCard extends StatelessWidget {
style: const TextStyle(color: Colors.black54)),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)
: const SizedBox(),
pageInfo?.mainCategory != null ? const SizedBox(width: 10) : const SizedBox(),
pageInfo?.mainCategory != null
? const SizedBox(width: 10)
: const SizedBox(),
//
Skeleton(
isLoading: isLoading,
@ -240,7 +264,9 @@ class PageCard extends StatelessWidget {
style: SkeletonLineStyle(width: 100),
),
child: Text(
pageInfo?.updatedTime != null ? Utils.getFriendDate(pageInfo!.updatedTime!) : "",
pageInfo?.updatedTime != null
? Utils.getFriendDate(pageInfo!.updatedTime!)
: "",
overflow: TextOverflow.fade,
style: PageCardStyles.contentTextStyle,
textScaleFactor: max(1, MediaQuery.of(context).textScaleFactor),
@ -345,7 +371,8 @@ class PageCard extends StatelessWidget {
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Theme.of(context).cardTheme.shadowColor ?? Colors.transparent,
color:
Theme.of(context).cardTheme.shadowColor ?? Colors.transparent,
blurRadius: 12,
offset: const Offset(0, 2),
),
@ -353,7 +380,8 @@ class PageCard extends StatelessWidget {
),
child: SizedBox(
height: PageCardStyles.cardContainerHeight +
(PageCardStyles.cardContentHeight * max(1, MediaQuery.of(context).textScaleFactor)),
(PageCardStyles.cardContentHeight *
max(1, MediaQuery.of(context).textScaleFactor)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

@ -4,7 +4,7 @@ import 'package:get/get.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/components/flutter_scale_tap/flutter_scale_tap.dart';
import 'package:isekai_wiki/components/utils.dart';
import 'package:isekai_wiki/pages/article.dart';
import 'package:isekai_wiki/pages/wiki/view.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
import 'package:like_button/like_button.dart';
import 'package:pull_down_button/pull_down_button.dart';

@ -13,6 +13,7 @@ class RecentPageListController extends GetxController {
ScrollController? scrollController;
UserController? uc;
FavoriteListController? flc;
double insetBottom = 0;
var shouldRefresh = true;
@ -28,7 +29,8 @@ 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();
@ -72,7 +74,8 @@ class RecentPageListController extends GetxController {
try {
var rcListRes = await MWApiList.getMixedRecentChanges(
limit: 10, continueInfo: continueInfo.isNotEmpty ? continueInfo : null);
limit: 10,
continueInfo: continueInfo.isNotEmpty ? continueInfo : null);
var pageIds = rcListRes.data.map((rcInfo) => rcInfo.pageid).toList();
var pageListRes = await MWApiList.getPageInfoList(
@ -116,23 +119,23 @@ class RecentPageListController extends GetxController {
class RecentPageList extends StatelessWidget {
final ScrollController? scrollController;
final double insetBottom;
const RecentPageList({
super.key,
this.scrollController,
});
const RecentPageList(
{super.key, this.scrollController, this.insetBottom = 0});
Widget _buildSkeletonList() {
return Column(
key: key,
children: [
for (var i = 0; i < 6; i++) PageCard(key: ValueKey("rpl-card-$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 flc) {
Widget _buildPageCard(int index, PageInfo pageInfo,
RecentPageListController c, FavoriteListController flc) {
return PageCard(
key: ValueKey("rpl-card-$index"),
pageInfo: c.pageList[index],
@ -169,12 +172,15 @@ class RecentPageList extends StatelessWidget {
child: Center(
child: Obx(
() => c.isLoading.value
? const CupertinoActivityIndicator(
? Padding(
padding: EdgeInsets.only(bottom: insetBottom),
child: const CupertinoActivityIndicator(
radius: 14,
),
)
: const SizedBox(
: SizedBox(
width: 28,
height: 28,
height: 28 + insetBottom,
),
),
),

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/components/recent_page_list.dart';
import 'package:isekai_wiki/components/safearea_builder.dart';
import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/pages/tab_page.dart';
import 'package:web_smooth_scroll/web_smooth_scroll.dart';
@ -22,7 +23,8 @@ const Color _kDefaultTabBarBorderColor = CupertinoDynamicColor.withBrightness(
enum HomeTabs { newest, followed }
class HomeController extends GetxController with GetSingleTickerProviderStateMixin {
class HomeController extends GetxController
with GetSingleTickerProviderStateMixin {
double _navSearchButtonOffset = 90;
var showNavSearchButton = false.obs;
@ -41,12 +43,15 @@ class HomeController extends GetxController with GetSingleTickerProviderStateMix
void onInit() {
tabController = TabController(length: 2, vsync: this);
_navSearchButtonOffset = 48 * MediaQuery.of(Get.context!).textScaleFactor + 48;
_navSearchButtonOffset =
48 * MediaQuery.of(Get.context!).textScaleFactor + 48;
scrollController.addListener(() {
if (scrollController.offset >= _navSearchButtonOffset && !showNavSearchButton.value) {
if (scrollController.offset >= _navSearchButtonOffset &&
!showNavSearchButton.value) {
showNavSearchButton.value = true;
} else if (scrollController.offset < _navSearchButtonOffset && showNavSearchButton.value) {
} else if (scrollController.offset < _navSearchButtonOffset &&
showNavSearchButton.value) {
showNavSearchButton.value = false;
}
});
@ -104,7 +109,8 @@ class HomeTab extends StatelessWidget {
duration: const Duration(milliseconds: 100),
child: CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.bell, size: 26, color: Styles.themeNavTitleColor),
child: const Icon(CupertinoIcons.bell,
size: 26, color: Styles.themeNavTitleColor),
onPressed: () {},
),
),
@ -120,7 +126,8 @@ 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();
},
@ -134,7 +141,8 @@ class HomeTab extends StatelessWidget {
final c = Get.put(HomeController());
return IsekaiPageScaffold(
child: WebSmoothScroll(
child: SafeAreaBuilder(
builder: (_, safeArea) => WebSmoothScroll(
controller: c.scrollController,
child: CustomScrollView(
controller: c.scrollController,
@ -146,7 +154,8 @@ class HomeTab extends StatelessWidget {
leading: _buildSearchIconButton(),
backgroundColor: Styles.themeMainColor,
brightness: Brightness.dark,
largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)),
largeTitle: const Text('首页',
style: TextStyle(color: Styles.themeNavTitleColor)),
trailing: _buildNotificationIconButton(),
border: Border.all(style: BorderStyle.none),
),
@ -166,7 +175,8 @@ class HomeTab extends StatelessWidget {
),
],
),
padding: const EdgeInsets.only(bottom: 10, left: 12, right: 12),
padding:
const EdgeInsets.only(bottom: 10, left: 12, right: 12),
child: CupertinoButton(
color: Colors.white,
padding: const EdgeInsets.all(0),
@ -179,10 +189,12 @@ 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))
],
),
),
@ -196,7 +208,8 @@ class HomeTab extends StatelessWidget {
minHeight: 40.0,
maxHeight: 40.0,
child: Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [
decoration:
BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 2,
@ -213,7 +226,10 @@ class HomeTab extends StatelessWidget {
indicatorColor: Styles.themeMainColor,
labelColor: Styles.themeMainColor,
unselectedLabelColor: Colors.black45,
tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')],
tabs: const [
CollapsedTabText('最新'),
CollapsedTabText('关注')
],
onTap: (int selected) {},
),
),
@ -247,7 +263,6 @@ class HomeTab extends StatelessWidget {
),
SliverSafeArea(
top: false,
bottom: false,
minimum: const EdgeInsets.only(top: 12, bottom: 12),
sliver: RecentPageList(
scrollController: c.scrollController,
@ -256,6 +271,7 @@ class HomeTab extends StatelessWidget {
],
),
),
),
);
}
}
@ -278,7 +294,8 @@ 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);
}

@ -0,0 +1,213 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:isekai_wiki/api/mw/list.dart';
import 'package:isekai_wiki/api/mw/mw_api.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/components/auto_wrap.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/global.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
import 'package:isekai_wiki/utils/dialog.dart';
import 'package:isekai_wiki/utils/error.dart';
class WikiInfoPageController extends GetxController {
var pageTitle = "".obs;
var pageId = 0.obs;
var pageInfo = Rx<PageInfo?>(null);
var loadingPageInfo = true.obs;
@override
void onInit() {
super.onInit();
Future.delayed(const Duration(milliseconds: 150)).then((_) {
loadPageInfo();
});
}
Future<void> loadPageInfo() async {
if (pageTitle.isNotEmpty || pageId.value != 0) {
try {
//
loadingPageInfo.value = true;
MWResponse<List<PageInfo>> pageInfoRes;
if (pageId.value != 0) {
pageInfoRes = await MWApiList.getPageInfoList(pageids: [
pageId.value
], prop: [
"extracts",
"info",
"pageimages",
"pageviews"
], extraParams: {
"inprop":
"url|protection|watched|watchers|visitingwatchers|displaytitle",
});
} else {
pageInfoRes =
await MWApiList.getPageInfoList(titles: [pageTitle.value]);
}
if (pageInfoRes.data.isEmpty) {
throw MWApiErrorException(code: 'no-page', info: "页面信息丢失");
}
pageInfo.value = pageInfoRes.data[0];
pageId.value = pageInfo.value!.pageid;
pageTitle.value = pageInfo.value!.title;
} catch (err, stack) {
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err),
title: "错误");
if (kDebugMode) {
print("Exception in page: $err");
stack.printError();
}
} finally {
loadingPageInfo.value = false;
}
} else {
alert(Get.overlayContext!, "页面不存在");
}
}
}
class WikiInfoPage extends StatefulWidget {
final String? targetPage;
final int? targetPageId;
const WikiInfoPage({super.key, this.targetPage, this.targetPageId});
@override
State<StatefulWidget> createState() => _WikiInfoPageState();
}
class _WikiInfoPageState extends ReactiveState<WikiInfoPage> {
WikiInfoPageController c = WikiInfoPageController();
@override
void initState() {
super.initState();
c = Get.put(c);
}
@override
void dispose() {
c.dispose();
super.dispose();
}
@override
void receiveProps() {
c.pageId.value = widget.targetPageId ?? 0;
c.pageTitle.value = widget.targetPage ?? "";
}
Widget buildLoading(BuildContext context) {
return CupertinoListSection.insetGrouped(
header: Text("基本信息"),
backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor,
children: <CupertinoListTile>[
CupertinoListTile.notched(
title: Center(
child: CupertinoActivityIndicator(
radius: 10 * MediaQuery.of(context).textScaleFactor),
),
),
],
);
}
CupertinoListTile buildPageInfo(
BuildContext context, String label, dynamic value,
{VoidFutureCallback? onTap}) {
final Locale appLocale = Localizations.localeOf(context);
var valueStr = "未知";
if (value is String) {
valueStr = value;
}
if (value is int) {
valueStr =
NumberFormat.decimalPattern(appLocale.toLanguageTag()).format(value);
} else if (value is double) {
var pattern = NumberFormat.decimalPattern(appLocale.toLanguageTag());
pattern.maximumFractionDigits = 2;
valueStr = pattern.format(value);
} else if (value is DateTime) {
// ignore: prefer_interpolation_to_compose_strings
valueStr = DateFormat.yMMMd(appLocale.toLanguageTag()).format(value) +
" " +
DateFormat.Hms(appLocale.toLanguageTag()).format(value);
}
return CupertinoListTile.notched(
padding: const EdgeInsetsDirectional.fromSTEB(20.0, 10.0, 14.0, 10.0),
title: Container(
width: MediaQuery.of(context).size.width,
child: AutoWrap(
children: [
Text(label),
Text(
valueStr,
style: TextStyle(
color: CupertinoColors.systemGrey2.resolveFrom(context)),
),
],
),
),
trailing: onTap != null ? const CupertinoListTileChevron() : null,
onTap: onTap,
);
}
Widget buildPageInfoList(BuildContext context) {
var pageInfo = c.pageInfo.value;
return Column(
children: [
Obx(
() => CupertinoListSection.insetGrouped(
additionalDividerMargin: 6,
header: const Text("基本信息"),
backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor,
children: <CupertinoListTile>[
buildPageInfo(
context,
"显示标题",
pageInfo?.mainTitle,
onTap: () async {},
),
buildPageInfo(context, "页面长度(字节)", pageInfo?.length),
buildPageInfo(context, "页面ID", pageInfo?.pageid),
buildPageInfo(context, "页面内容语言", pageInfo?.pagelanguage),
buildPageInfo(context, "页面内容类型", pageInfo?.contentmodel),
buildPageInfo(context, "收藏者数", pageInfo?.watchers),
buildPageInfo(context, "最后修改于", pageInfo?.updatedTime),
buildPageInfo(context, "该页面的子页面数", null),
buildPageInfo(context, "过去30天的页面访问量", null),
],
),
),
],
);
}
@override
Widget render(BuildContext context) {
return IsekaiPageScaffold(
navigationBar: const IsekaiNavigationBar(
middle: Text("页面信息"),
),
child: SafeArea(
child: ListView(
children: [
Obx(() => c.loadingPageInfo.value
? buildLoading(context)
: buildPageInfoList(context))
],
),
),
);
}
}

@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_share/flutter_share.dart';
import 'package:flutter_slider_drawer/flutter_slider_drawer.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/api/mw/list.dart';
@ -13,6 +12,8 @@ import 'package:isekai_wiki/components/nav_bar_button.dart';
import 'package:isekai_wiki/components/safearea_builder.dart';
import 'package:isekai_wiki/components/toc.dart';
import 'package:isekai_wiki/components/wikipage_parser.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/global.dart';
import 'package:isekai_wiki/models/favorite_list.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
@ -21,16 +22,17 @@ import 'package:isekai_wiki/utils/error.dart';
import 'package:isekai_wiki/utils/utils.dart';
import 'package:share_plus/share_plus.dart';
import '../components/isekai_nav_bar.dart';
import '../components/isekai_page_scaffold.dart';
class MinimumArticleData {
final String title;
final String? description;
final String? mainCategory;
final DateTime? updateTime;
MinimumArticleData({required this.title, this.description, this.mainCategory, this.updateTime});
MinimumArticleData(
{required this.title,
this.description,
this.mainCategory,
this.updateTime});
}
class ArticleCategoryData {
@ -40,7 +42,7 @@ class ArticleCategoryData {
ArticleCategoryData({required this.name, required this.identity});
}
class ArticlePageController extends GetxController {
class WikiViewPageController extends GetxController {
WikiPageParserController? parserController;
var loading = true.obs;
@ -75,9 +77,11 @@ class ArticlePageController extends GetxController {
loading.value = true;
MWResponse<List<PageInfo>> pageInfoRes;
if (pageId.value != 0) {
pageInfoRes = await MWApiList.getPageInfoList(pageids: [pageId.value]);
pageInfoRes =
await MWApiList.getPageInfoList(pageids: [pageId.value]);
} else {
pageInfoRes = await MWApiList.getPageInfoList(titles: [pageTitle.value]);
pageInfoRes =
await MWApiList.getPageInfoList(titles: [pageTitle.value]);
}
if (pageInfoRes.data.isEmpty) {
throw MWApiErrorException(code: 'no-page', info: "页面信息丢失");
@ -92,7 +96,8 @@ class ArticlePageController extends GetxController {
var parseRes = await MWApiParse.parse(pageId: pageId.value);
parseInfo.value = parseRes.data;
} catch (err, stack) {
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), title: "错误");
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err),
title: "错误");
if (kDebugMode) {
print("Exception in page: $err");
stack.printError();
@ -109,12 +114,13 @@ class ArticlePageController extends GetxController {
void handleShareButtonClick() {
if (pageInfo.value != null) {
var pageUrl = pageInfo.value!.fullurl ?? getPageUrl(pageInfo.value!.title);
var pageUrl =
pageInfo.value!.fullurl ?? getPageUrl(pageInfo.value!.title);
var shareTitle = "${pageInfo.value!.title} - ${Global.siteTitle}";
var shareText = "$shareTitle\n$pageUrl";
FlutterShare.share(
title: shareTitle, text: shareTitle, linkUrl: pageUrl, chooserTitle: "分享到");
Share.share(shareText, subject: shareTitle);
} else {
alert(Get.overlayContext!, "页面还未加载完成");
}
@ -139,19 +145,20 @@ class ArticlePageController extends GetxController {
}
}
class ArticlePage extends StatefulWidget {
class WikiViewPage extends StatefulWidget {
final MinimumArticleData? initialArticleData;
final String? targetPage;
final int? targetPageId;
const ArticlePage({super.key, this.targetPage, this.targetPageId, this.initialArticleData});
const WikiViewPage(
{super.key, this.targetPage, this.targetPageId, this.initialArticleData});
@override
State<StatefulWidget> createState() => _ArticlePageState();
State<StatefulWidget> createState() => _WikiViewPageState();
}
class _ArticlePageState extends ReactiveState<ArticlePage> {
var c = ArticlePageController();
class _WikiViewPageState extends ReactiveState<WikiViewPage> {
var c = WikiViewPageController();
@override
void initState() {
@ -216,7 +223,8 @@ class _ArticlePageState extends ReactiveState<ArticlePage> {
semanticLabel: "讨论",
),
Obx(() {
if (c.pageInfo.value != null && flc.isFavorite(c.pageInfo.value!)) {
if (c.pageInfo.value != null &&
flc.isFavorite(c.pageInfo.value!)) {
return NavBarButton(
icon: CupertinoIcons.heart_fill,
onPressed: () {},

@ -2,5 +2,6 @@ import 'package:isekai_wiki/global.dart';
String getPageUrl(String title) {
var titleEncoded = Uri.encodeFull(title.replaceAll(RegExp(r" "), "_"));
return Global.siteConfig.pageUrlTemplate.replaceAll(RegExp(r"\$1"), titleEncoded);
return Global.siteConfig.pageUrlTemplate
.replaceAll(RegExp(r"\$1"), titleEncoded);
}

@ -1,4 +1,4 @@
platform :osx, '10.11'
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@ -273,6 +273,7 @@
};
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -404,7 +405,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@ -483,7 +484,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -530,7 +531,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save