修改路由配置,增加收藏相关的API

main
落雨楓 2 years ago
parent da38eff56d
commit 4a0076d97d

@ -0,0 +1,23 @@
import 'package:isekai_wiki/api/response/watch.dart';
import 'mw_api.dart';
class MWApiWatch {
static Future<MWResponse<List<WatchActionResponseList>>> watchPage(List<String> titles,
{bool unwatch = false}) async {
var query = {"titles": titles.join("|")};
if (unwatch) {
query["unwatch"] = "1";
}
var mwRes = await MWApi.post("watch", params: query, withToken: "watch");
var data = WatchActionResponse.fromJson(mwRes.data);
return mwRes.replaceData(data.watch);
}
static Future<MWResponse<List<WatchActionResponseList>>> unwatchPage(List<String> titles) {
return watchPage(titles, unwatch: true);
}
}

@ -3,7 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'csrf_token.freezed.dart'; part 'csrf_token.freezed.dart';
part 'csrf_token.g.dart'; part 'csrf_token.g.dart';
@freezed @Freezed(copyWith: false)
class CSRFTokenResponse with _$CSRFTokenResponse { class CSRFTokenResponse with _$CSRFTokenResponse {
const factory CSRFTokenResponse({required Map<String, String> tokens}) = _CSRFTokenInfoResponse; const factory CSRFTokenResponse({required Map<String, String> tokens}) = _CSRFTokenInfoResponse;

@ -3,7 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'mugenapp.freezed.dart'; part 'mugenapp.freezed.dart';
part 'mugenapp.g.dart'; part 'mugenapp.g.dart';
@freezed @Freezed(copyWith: false)
class MugenAppStartAuthInfo with _$MugenAppStartAuthInfo { class MugenAppStartAuthInfo with _$MugenAppStartAuthInfo {
const factory MugenAppStartAuthInfo({ const factory MugenAppStartAuthInfo({
required String loginUrl, required String loginUrl,
@ -15,16 +15,16 @@ class MugenAppStartAuthInfo with _$MugenAppStartAuthInfo {
_$MugenAppStartAuthInfoFromJson(json); _$MugenAppStartAuthInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MugenAppStartAuthResponse with _$MugenAppStartAuthResponse { class MugenAppStartAuthResponse with _$MugenAppStartAuthResponse {
const factory MugenAppStartAuthResponse( const factory MugenAppStartAuthResponse({required MugenAppStartAuthInfo startauth}) =
{required MugenAppStartAuthInfo startauth}) = _MugenAppStartAuthResponse; _MugenAppStartAuthResponse;
factory MugenAppStartAuthResponse.fromJson(Map<String, dynamic> json) => factory MugenAppStartAuthResponse.fromJson(Map<String, dynamic> json) =>
_$MugenAppStartAuthResponseFromJson(json); _$MugenAppStartAuthResponseFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MugenAppAttemptAuthInfo with _$MugenAppAttemptAuthInfo { class MugenAppAttemptAuthInfo with _$MugenAppAttemptAuthInfo {
const factory MugenAppAttemptAuthInfo({ const factory MugenAppAttemptAuthInfo({
required String status, required String status,
@ -36,7 +36,7 @@ class MugenAppAttemptAuthInfo with _$MugenAppAttemptAuthInfo {
_$MugenAppAttemptAuthInfoFromJson(json); _$MugenAppAttemptAuthInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MugenAppAttemptAuthResponse with _$MugenAppAttemptAuthResponse { class MugenAppAttemptAuthResponse with _$MugenAppAttemptAuthResponse {
const factory MugenAppAttemptAuthResponse({ const factory MugenAppAttemptAuthResponse({
required MugenAppAttemptAuthInfo attemptauth, required MugenAppAttemptAuthInfo attemptauth,

@ -83,14 +83,14 @@ class PageInfo {
} }
} }
@freezed @Freezed(copyWith: false)
class PagesResponse with _$PagesResponse { class PagesResponse with _$PagesResponse {
factory PagesResponse({required List<PageInfo> pages}) = _PageResponse; 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 @Freezed(copyWith: false)
class PageImageInfo with _$PageImageInfo { class PageImageInfo with _$PageImageInfo {
factory PageImageInfo({required String source, int? width, int? height}) = _PageImageInfo; factory PageImageInfo({required String source, int? width, int? height}) = _PageImageInfo;

@ -3,7 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'parse.freezed.dart'; part 'parse.freezed.dart';
part 'parse.g.dart'; part 'parse.g.dart';
@freezed @Freezed(copyWith: false)
class MWParseCategoryInfo with _$MWParseCategoryInfo { class MWParseCategoryInfo with _$MWParseCategoryInfo {
factory MWParseCategoryInfo({ factory MWParseCategoryInfo({
required String category, required String category,
@ -14,7 +14,7 @@ class MWParseCategoryInfo with _$MWParseCategoryInfo {
_$MWParseCategoryInfoFromJson(json); _$MWParseCategoryInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MWParseLangLinkInfo with _$MWParseLangLinkInfo { class MWParseLangLinkInfo with _$MWParseLangLinkInfo {
factory MWParseLangLinkInfo({ factory MWParseLangLinkInfo({
required String lang, required String lang,
@ -28,7 +28,7 @@ class MWParseLangLinkInfo with _$MWParseLangLinkInfo {
_$MWParseLangLinkInfoFromJson(json); _$MWParseLangLinkInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MWParsePageLinkInfo with _$MWParsePageLinkInfo { class MWParsePageLinkInfo with _$MWParsePageLinkInfo {
factory MWParsePageLinkInfo({ factory MWParsePageLinkInfo({
required int ns, required int ns,
@ -40,7 +40,7 @@ class MWParsePageLinkInfo with _$MWParsePageLinkInfo {
_$MWParsePageLinkInfoFromJson(json); _$MWParsePageLinkInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MWParseSectionInfo with _$MWParseSectionInfo { class MWParseSectionInfo with _$MWParseSectionInfo {
factory MWParseSectionInfo({ factory MWParseSectionInfo({
required int toclevel, required int toclevel,
@ -57,7 +57,7 @@ class MWParseSectionInfo with _$MWParseSectionInfo {
_$MWParseSectionInfoFromJson(json); _$MWParseSectionInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MWParseInfo with _$MWParseInfo { class MWParseInfo with _$MWParseInfo {
factory MWParseInfo({ factory MWParseInfo({
required String title, required String title,

@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'recent_changes.freezed.dart'; part 'recent_changes.freezed.dart';
part 'recent_changes.g.dart'; part 'recent_changes.g.dart';
@freezed @Freezed(copyWith: false)
class RecentChangesItem with _$RecentChangesItem { class RecentChangesItem with _$RecentChangesItem {
factory RecentChangesItem({ factory RecentChangesItem({
String? type, String? type,
@ -22,7 +22,7 @@ class RecentChangesItem with _$RecentChangesItem {
_$RecentChangesItemFromJson(json); _$RecentChangesItemFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class RecentChangesResponse with _$RecentChangesResponse { class RecentChangesResponse with _$RecentChangesResponse {
factory RecentChangesResponse({required List<RecentChangesItem> recentchanges}) = factory RecentChangesResponse({required List<RecentChangesItem> recentchanges}) =
_RecentChangesResponse; _RecentChangesResponse;

@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'userinfo.freezed.dart'; part 'userinfo.freezed.dart';
part 'userinfo.g.dart'; part 'userinfo.g.dart';
@freezed @Freezed(copyWith: false)
class UserGroupMembership with _$UserGroupMembership { class UserGroupMembership with _$UserGroupMembership {
factory UserGroupMembership({ factory UserGroupMembership({
required String group, required String group,
@ -16,7 +16,7 @@ class UserGroupMembership with _$UserGroupMembership {
_$UserGroupMembershipFromJson(json); _$UserGroupMembershipFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class UserAcceptLang with _$UserAcceptLang { class UserAcceptLang with _$UserAcceptLang {
factory UserAcceptLang({ factory UserAcceptLang({
required double q, required double q,
@ -26,7 +26,7 @@ class UserAcceptLang with _$UserAcceptLang {
factory UserAcceptLang.fromJson(Map<String, dynamic> json) => _$UserAcceptLangFromJson(json); factory UserAcceptLang.fromJson(Map<String, dynamic> json) => _$UserAcceptLangFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MetaUserInfo with _$MetaUserInfo { class MetaUserInfo with _$MetaUserInfo {
factory MetaUserInfo({ factory MetaUserInfo({
required int id, required int id,
@ -52,7 +52,7 @@ class MetaUserInfo with _$MetaUserInfo {
factory MetaUserInfo.fromJson(Map<String, dynamic> json) => _$MetaUserInfoFromJson(json); factory MetaUserInfo.fromJson(Map<String, dynamic> json) => _$MetaUserInfoFromJson(json);
} }
@freezed @Freezed(copyWith: false)
class MetaUserInfoResponse with _$MetaUserInfoResponse { class MetaUserInfoResponse with _$MetaUserInfoResponse {
factory MetaUserInfoResponse({ factory MetaUserInfoResponse({
required MetaUserInfo userinfo, required MetaUserInfo userinfo,

@ -0,0 +1,33 @@
// ignore_for_file: unused_element
import 'package:freezed_annotation/freezed_annotation.dart';
part 'watch.freezed.dart';
part 'watch.g.dart';
@Freezed(copyWith: false)
class WatchActionResponseList with _$WatchActionResponseList {
WatchActionResponseList._();
factory WatchActionResponseList(
{required int ns,
required String title,
String? watch,
String? unwatch}) = _WatchActionResponseList;
bool get isWatch {
return watch != null;
}
factory WatchActionResponseList.fromJson(Map<String, dynamic> json) =>
_$WatchActionResponseListFromJson(json);
}
@Freezed(copyWith: false)
class WatchActionResponse with _$WatchActionResponse {
factory WatchActionResponse({required List<WatchActionResponseList> watch}) =
_WatchActionResponse;
factory WatchActionResponse.fromJson(Map<String, dynamic> json) =>
_$WatchActionResponseFromJson(json);
}

File diff suppressed because it is too large Load Diff

@ -5,7 +5,6 @@ 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/flutter_scale_tap/flutter_scale_tap.dart';
import 'package:isekai_wiki/components/utils.dart'; import 'package:isekai_wiki/components/utils.dart';
import 'package:isekai_wiki/pages/article.dart'; import 'package:isekai_wiki/pages/article.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
import 'package:like_button/like_button.dart'; import 'package:like_button/like_button.dart';
import 'package:pull_down_button/pull_down_button.dart'; import 'package:pull_down_button/pull_down_button.dart';
import 'package:skeletons/skeletons.dart'; import 'package:skeletons/skeletons.dart';
@ -24,53 +23,59 @@ class PageCardStyles {
static const double footerButtonInnerSize = 26; static const double footerButtonInnerSize = 26;
} }
class PageCardController extends GetxController { class PageCard extends StatelessWidget {
var isLoading = false.obs; final bool isLoading;
final PageInfo? pageInfo;
var pageInfo = Rx<PageInfo?>(null); final bool isFavorite;
final AddFavoriteCallback? onSetFavorite;
var isFavorite = false.obs; final PageInfoCallback? onShare;
AddFavoriteCallback? onSetFavorite; const PageCard({
PageInfoCallback? onShare; super.key,
this.isLoading = false,
this.pageInfo,
this.isFavorite = false,
this.onSetFavorite,
this.onShare,
});
Future<bool> handleFavoriteClick(bool localIsFavorite) async { Future<bool> handleFavoriteClick(bool localIsFavorite) async {
if (pageInfo.value != null && onSetFavorite != null) { if (pageInfo != null && onSetFavorite != null) {
return await onSetFavorite!.call(pageInfo.value!, localIsFavorite, false); return await onSetFavorite!.call(pageInfo!, localIsFavorite, false);
} else { } else {
return false; return false;
} }
} }
Future<void> handleAddFavoriteMenuItemClick() async { Future<void> handleAddFavoriteMenuItemClick() async {
if (pageInfo.value != null && onSetFavorite != null) { if (pageInfo != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo.value!, true, true); await onSetFavorite!.call(pageInfo!, true, true);
} }
} }
Future<void> handleRemoveFavoriteMenuItemClick() async { Future<void> handleRemoveFavoriteMenuItemClick() async {
if (pageInfo.value != null && onSetFavorite != null) { if (pageInfo != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo.value!, false, true); await onSetFavorite!.call(pageInfo!, false, true);
} }
} }
handleShareClick() async { handleShareClick() async {
if (pageInfo.value != null && onShare != null) { if (pageInfo != null && onShare != null) {
await onShare!.call(pageInfo.value!); await onShare!.call(pageInfo!);
} }
} }
void handlePageInfoClick() { void handlePageInfoClick() {
if (pageInfo.value != null) {} if (pageInfo != null) {}
} }
Future<void> handleCardClick() async { Future<void> handleCardClick() async {
if (isLoading.value) { if (isLoading) {
return; return;
} }
if (pageInfo.value != null) { if (pageInfo != null) {
var cPageInfo = pageInfo.value!; var cPageInfo = pageInfo!;
await Navigator.of(Get.context!).push( await Navigator.of(Get.context!).push(
CupertinoPageRoute( CupertinoPageRoute(
builder: (_) => ArticlePage( builder: (_) => ArticlePage(
@ -86,45 +91,6 @@ class PageCardController extends GetxController {
); );
} }
} }
}
class PageCard extends StatefulWidget {
final bool isLoading;
final PageInfo? pageInfo;
final bool isFavorite;
final AddFavoriteCallback? onSetFavorite;
final PageInfoCallback? onShare;
const PageCard({
super.key,
this.isLoading = false,
this.pageInfo,
this.isFavorite = false,
this.onSetFavorite,
this.onShare,
});
@override
State<StatefulWidget> createState() => _PageCardState();
}
class _PageCardState extends ReactiveState<PageCard> {
var c = PageCardController();
double textScale = 1;
@override
void initState() {
super.initState();
}
@override
void receiveProps() {
c.isLoading.value = widget.isLoading;
c.pageInfo.value = widget.pageInfo;
c.isFavorite.value = widget.isFavorite;
c.onSetFavorite = widget.onSetFavorite;
c.onShare = widget.onShare;
}
Widget _actionButtonSkeleton(bool isLoading, Widget child) { Widget _actionButtonSkeleton(bool isLoading, Widget child) {
return Skeleton( return Skeleton(
@ -138,20 +104,24 @@ class _PageCardState extends ReactiveState<PageCard> {
} }
Widget _buildCardHeader(BuildContext context) { Widget _buildCardHeader(BuildContext context) {
var textScale = MediaQuery.of(context).textScaleFactor;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: Skeleton( child: Skeleton(
isLoading: c.isLoading.value, isLoading: isLoading,
skeleton: SkeletonLine( skeleton: SkeletonLine(
style: SkeletonLineStyle( 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(pageInfo?.mainTitle ?? "页面信息丢失", style: Styles.pageCardTitle),
), ),
); );
} }
Widget _buildCardBody(BuildContext context) { Widget _buildCardBody(BuildContext context) {
var textScale = MediaQuery.of(context).textScaleFactor;
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
@ -161,7 +131,7 @@ class _PageCardState extends ReactiveState<PageCard> {
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 1,
child: c.isLoading.value child: isLoading
? ClipRect( ? ClipRect(
child: SkeletonParagraph( child: SkeletonParagraph(
style: SkeletonParagraphStyle( style: SkeletonParagraphStyle(
@ -173,12 +143,12 @@ class _PageCardState extends ReactiveState<PageCard> {
), ),
), ),
) )
: Text(c.pageInfo.value?.description ?? "没有简介", : Text(pageInfo?.description ?? "没有简介",
overflow: TextOverflow.fade, style: Styles.pageCardDescription), overflow: TextOverflow.fade, style: Styles.pageCardDescription),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Skeleton( Skeleton(
isLoading: c.isLoading.value, isLoading: isLoading,
skeleton: const SkeletonAvatar( skeleton: const SkeletonAvatar(
style: SkeletonAvatarStyle(width: 114, height: 114), style: SkeletonAvatarStyle(width: 114, height: 114),
), ),
@ -197,49 +167,45 @@ class _PageCardState extends ReactiveState<PageCard> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// //
c.pageInfo.value?.mainCategory != null pageInfo?.mainCategory != null
? Chip( ? Chip(
backgroundColor: const Color.fromARGB(1, 238, 238, 238), backgroundColor: const Color.fromARGB(1, 238, 238, 238),
label: Text(c.pageInfo.value!.mainCategory!, label:
style: const TextStyle(color: Colors.black54)), Text(pageInfo!.mainCategory!, style: const TextStyle(color: Colors.black54)),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap) materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)
: const SizedBox(), : const SizedBox(),
c.pageInfo.value?.mainCategory != null ? const SizedBox(width: 10) : const SizedBox(), pageInfo?.mainCategory != null ? const SizedBox(width: 10) : const SizedBox(),
// //
Skeleton( Skeleton(
isLoading: c.isLoading.value, isLoading: isLoading,
skeleton: const SkeletonLine( skeleton: const SkeletonLine(
style: SkeletonLineStyle(width: 100), style: SkeletonLineStyle(width: 100),
), ),
child: Text( child: Text(
c.pageInfo.value?.updatedTime != null pageInfo?.updatedTime != null ? Utils.getFriendDate(pageInfo!.updatedTime!) : "",
? Utils.getFriendDate(c.pageInfo.value!.updatedTime!)
: "",
style: Styles.pageCardDescription), style: Styles.pageCardDescription),
), ),
const Spacer(), const Spacer(),
// //
Obx( _actionButtonSkeleton(
() => _actionButtonSkeleton( isLoading,
c.isLoading.value, LikeButton(
LikeButton( size: PageCardStyles.footerButtonInnerSize,
size: PageCardStyles.footerButtonInnerSize, isLiked: isFavorite,
isLiked: c.isFavorite.value, onTap: handleFavoriteClick,
onTap: c.handleFavoriteClick, likeBuilder: (bool isLiked) {
likeBuilder: (bool isLiked) { return Icon(
return Icon( isLiked ? Icons.favorite : Icons.favorite_border,
isLiked ? Icons.favorite : Icons.favorite_border, color: isLiked ? Colors.red : Colors.grey,
color: isLiked ? Colors.red : Colors.grey, size: PageCardStyles.footerButtonInnerSize,
size: PageCardStyles.footerButtonInnerSize, );
); },
},
),
), ),
), ),
const SizedBox(width: 18), const SizedBox(width: 18),
// //
_actionButtonSkeleton( _actionButtonSkeleton(
c.isLoading.value, isLoading,
SizedBox( SizedBox(
height: PageCardStyles.footerButtonSize, height: PageCardStyles.footerButtonSize,
width: PageCardStyles.footerButtonSize, width: PageCardStyles.footerButtonSize,
@ -252,39 +218,7 @@ class _PageCardState extends ReactiveState<PageCard> {
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
), ),
itemBuilder: (context) => [ itemBuilder: _buildMenuItem,
c.isFavorite.value
? PullDownMenuItem(
title: '取消收藏',
icon: CupertinoIcons.heart_fill,
onTap: c.handleRemoveFavoriteMenuItemClick,
)
: PullDownMenuItem(
title: '收藏',
icon: CupertinoIcons.heart,
onTap: c.handleRemoveFavoriteMenuItemClick,
),
const PullDownMenuDivider(),
PullDownMenuItem(
title: '分享',
icon: CupertinoIcons.share,
onTap: c.handleShareClick,
),
const PullDownMenuDivider.large(),
/*
PullDownMenuItem(
title: '相似推荐',
onTap: () {},
icon: CupertinoIcons.ellipsis_circle,
),
const PullDownMenuDivider.large(),
*/
PullDownMenuItem(
title: '页面详情',
onTap: c.handlePageInfoClick,
icon: CupertinoIcons.info_circle,
),
],
position: PullDownMenuPosition.under, position: PullDownMenuPosition.under,
buttonBuilder: (context, showMenu) => IconButton( buttonBuilder: (context, showMenu) => IconButton(
onPressed: showMenu, onPressed: showMenu,
@ -304,7 +238,45 @@ class _PageCardState extends ReactiveState<PageCard> {
); );
} }
List<PullDownMenuEntry> _buildMenuItem(BuildContext context) {
return [
isFavorite
? PullDownMenuItem(
title: '取消收藏',
icon: CupertinoIcons.heart_fill,
onTap: handleRemoveFavoriteMenuItemClick,
)
: PullDownMenuItem(
title: '收藏',
icon: CupertinoIcons.heart,
onTap: handleRemoveFavoriteMenuItemClick,
),
const PullDownMenuDivider(),
PullDownMenuItem(
title: '分享',
icon: CupertinoIcons.share,
onTap: handleShareClick,
),
const PullDownMenuDivider.large(),
/*
PullDownMenuItem(
title: '相似推荐',
onTap: () {},
icon: CupertinoIcons.ellipsis_circle,
),
const PullDownMenuDivider.large(),
*/
PullDownMenuItem(
title: '页面详情',
onTap: handlePageInfoClick,
icon: CupertinoIcons.info_circle,
),
];
}
Widget _buildCard(BuildContext context) { Widget _buildCard(BuildContext context) {
var textScale = MediaQuery.of(context).textScaleFactor;
return Card( return Card(
elevation: 4.0, elevation: 4.0,
// //
@ -335,20 +307,13 @@ class _PageCardState extends ReactiveState<PageCard> {
} }
@override @override
Widget render(BuildContext context) { Widget build(BuildContext context) {
textScale = MediaQuery.of(context).textScaleFactor;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Obx( child: ScaleTap(
() => ScaleTap( enableFeedback: isLoading,
enableFeedback: c.isLoading.value, onPressed: handleCardClick,
onPressed: c.handleCardClick, child: _buildCard(context),
child: GetBuilder<PageCardController>(
init: c,
builder: (GetxController c) => _buildCard(context),
),
),
), ),
); );
} }

@ -0,0 +1,355 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
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/reactive/reactive.dart';
import 'package:like_button/like_button.dart';
import 'package:pull_down_button/pull_down_button.dart';
import 'package:skeletons/skeletons.dart';
import '../styles.dart';
typedef AddFavoriteCallback = Future<bool> Function(
PageInfo pageInfo, bool localIsFavorite, bool showToast);
typedef PageInfoCallback = Future<void> Function(PageInfo pageInfo);
class PageCardStyles {
static const double cardInnerHeight = 150;
static const cardInnerPadding = EdgeInsets.only(top: 16, left: 20, right: 20, bottom: 12);
static const double footerButtonSize = 30;
static const double footerButtonInnerSize = 26;
}
class PageCardController extends GetxController {
var isLoading = false.obs;
var pageInfo = Rx<PageInfo?>(null);
var isFavorite = false.obs;
AddFavoriteCallback? onSetFavorite;
PageInfoCallback? onShare;
Future<bool> handleFavoriteClick(bool localIsFavorite) async {
if (pageInfo.value != null && onSetFavorite != null) {
return await onSetFavorite!.call(pageInfo.value!, localIsFavorite, false);
} else {
return false;
}
}
Future<void> handleAddFavoriteMenuItemClick() async {
if (pageInfo.value != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo.value!, true, true);
}
}
Future<void> handleRemoveFavoriteMenuItemClick() async {
if (pageInfo.value != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo.value!, false, true);
}
}
handleShareClick() async {
if (pageInfo.value != null && onShare != null) {
await onShare!.call(pageInfo.value!);
}
}
void handlePageInfoClick() {
if (pageInfo.value != null) {}
}
Future<void> handleCardClick() async {
if (isLoading.value) {
return;
}
if (pageInfo.value != null) {
var cPageInfo = pageInfo.value!;
await Navigator.of(Get.context!).push(
CupertinoPageRoute(
builder: (_) => ArticlePage(
targetPage: cPageInfo.title,
initialArticleData: MinimumArticleData(
title: cPageInfo.mainTitle,
description: cPageInfo.description,
mainCategory: cPageInfo.mainCategory,
updateTime: cPageInfo.updatedTime,
),
),
),
);
}
}
}
class PageCard extends StatefulWidget {
final bool isLoading;
final PageInfo? pageInfo;
final bool isFavorite;
final AddFavoriteCallback? onSetFavorite;
final PageInfoCallback? onShare;
const PageCard({
super.key,
this.isLoading = false,
this.pageInfo,
this.isFavorite = false,
this.onSetFavorite,
this.onShare,
});
@override
State<StatefulWidget> createState() => _PageCardState();
}
class _PageCardState extends ReactiveState<PageCard> {
var c = PageCardController();
double textScale = 1;
@override
void initState() {
super.initState();
}
@override
void receiveProps() {
c.isLoading.value = widget.isLoading;
c.pageInfo.value = widget.pageInfo;
c.isFavorite.value = widget.isFavorite;
c.onSetFavorite = widget.onSetFavorite;
c.onShare = widget.onShare;
}
Widget _actionButtonSkeleton(bool isLoading, Widget child) {
return Skeleton(
isLoading: isLoading,
skeleton: const SkeletonLine(
style: SkeletonLineStyle(
width: PageCardStyles.footerButtonSize, height: PageCardStyles.footerButtonSize),
),
child: child,
);
}
Widget _buildCardHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Skeleton(
isLoading: c.isLoading.value,
skeleton: SkeletonLine(
style: SkeletonLineStyle(
height: (Styles.pageCardTitle.fontSize! + 4) * textScale, randomLength: true),
),
child: Text(c.pageInfo.value?.mainTitle ?? "页面信息丢失", style: Styles.pageCardTitle),
),
);
}
Widget _buildCardBody(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
flex: 1,
child: c.isLoading.value
? ClipRect(
child: SkeletonParagraph(
style: SkeletonParagraphStyle(
lines: 3,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 0),
lineStyle: SkeletonLineStyle(
randomLength: true,
height: Styles.pageCardDescription.fontSize! * textScale),
),
),
)
: Text(c.pageInfo.value?.description ?? "没有简介",
overflow: TextOverflow.fade, style: Styles.pageCardDescription),
),
const SizedBox(width: 10),
Skeleton(
isLoading: c.isLoading.value,
skeleton: const SkeletonAvatar(
style: SkeletonAvatarStyle(width: 114, height: 114),
),
child: Container(),
),
],
),
),
),
);
}
Widget _buildCardFooter(BuildContext context) {
return ScaleTapIgnore(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//
c.pageInfo.value?.mainCategory != null
? Chip(
backgroundColor: const Color.fromARGB(1, 238, 238, 238),
label: Text(c.pageInfo.value!.mainCategory!,
style: const TextStyle(color: Colors.black54)),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)
: const SizedBox(),
c.pageInfo.value?.mainCategory != null ? const SizedBox(width: 10) : const SizedBox(),
//
Skeleton(
isLoading: c.isLoading.value,
skeleton: const SkeletonLine(
style: SkeletonLineStyle(width: 100),
),
child: Text(
c.pageInfo.value?.updatedTime != null
? Utils.getFriendDate(c.pageInfo.value!.updatedTime!)
: "",
style: Styles.pageCardDescription),
),
const Spacer(),
//
Obx(
() => _actionButtonSkeleton(
c.isLoading.value,
LikeButton(
size: PageCardStyles.footerButtonInnerSize,
isLiked: c.isFavorite.value,
onTap: c.handleFavoriteClick,
likeBuilder: (bool isLiked) {
return Icon(
isLiked ? Icons.favorite : Icons.favorite_border,
color: isLiked ? Colors.red : Colors.grey,
size: PageCardStyles.footerButtonInnerSize,
);
},
),
),
),
const SizedBox(width: 18),
//
_actionButtonSkeleton(
c.isLoading.value,
SizedBox(
height: PageCardStyles.footerButtonSize,
width: PageCardStyles.footerButtonSize,
child: PullDownButton(
routeTheme: PullDownMenuRouteTheme(
endShadow: BoxShadow(
color: Colors.grey.withOpacity(0.6),
spreadRadius: 1,
blurRadius: 20,
offset: const Offset(0, 2),
),
),
itemBuilder: (context) => [
c.isFavorite.value
? PullDownMenuItem(
title: '取消收藏',
icon: CupertinoIcons.heart_fill,
onTap: c.handleRemoveFavoriteMenuItemClick,
)
: PullDownMenuItem(
title: '收藏',
icon: CupertinoIcons.heart,
onTap: c.handleRemoveFavoriteMenuItemClick,
),
const PullDownMenuDivider(),
PullDownMenuItem(
title: '分享',
icon: CupertinoIcons.share,
onTap: c.handleShareClick,
),
const PullDownMenuDivider.large(),
/*
PullDownMenuItem(
title: '相似推荐',
onTap: () {},
icon: CupertinoIcons.ellipsis_circle,
),
const PullDownMenuDivider.large(),
*/
PullDownMenuItem(
title: '页面详情',
onTap: c.handlePageInfoClick,
icon: CupertinoIcons.info_circle,
),
],
position: PullDownMenuPosition.under,
buttonBuilder: (context, showMenu) => IconButton(
onPressed: showMenu,
padding: const EdgeInsets.all(0.0),
splashRadius: PageCardStyles.footerButtonInnerSize - 4,
iconSize: PageCardStyles.footerButtonInnerSize,
icon: const Icon(
Icons.more_horiz,
color: Colors.grey,
),
),
),
),
),
],
),
);
}
Widget _buildCard(BuildContext context) {
return Card(
elevation: 4.0,
//
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(Styles.isXs ? 0 : 14.0)),
),
// 齿
clipBehavior: Clip.antiAlias,
semanticContainer: false,
child: Padding(
padding: PageCardStyles.cardInnerPadding,
child: SizedBox(
height: PageCardStyles.cardInnerHeight * textScale,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
_buildCardHeader(context),
//
_buildCardBody(context),
// Footer
_buildCardFooter(context),
],
),
),
),
);
}
@override
Widget render(BuildContext context) {
textScale = MediaQuery.of(context).textScaleFactor;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Obx(
() => ScaleTap(
enableFeedback: c.isLoading.value,
onPressed: c.handleCardClick,
child: GetBuilder<PageCardController>(
init: c,
builder: (GetxController c) => _buildCard(context),
),
),
),
);
}
}

@ -93,7 +93,7 @@ class RecentPageListController extends GetxController {
} }
hasNextPage.value = rcListRes.continueInfo != null; hasNextPage.value = rcListRes.continueInfo != null;
continueInfo.value = rcListRes.continueInfo ?? {}; continueInfo.value = rcListRes.continueInfo ?? {};
} catch (err, stack) { } catch (err) {
hasNextPage.value = false; hasNextPage.value = false;
if (shouldRefresh) { if (shouldRefresh) {
pageList.clear(); pageList.clear();

@ -19,8 +19,7 @@ class FavoriteListController extends GetxController {
} }
} }
var newPageIds = var newPageIds = pageIds.where((pageId) => !removeList.contains(pageId)).toList();
pageIds.where((pageId) => !removeList.contains(pageId)).toList();
for (var pageId in addList) { for (var pageId in addList) {
if (!newPageIds.contains(pageId)) { if (!newPageIds.contains(pageId)) {
newPageIds.add(pageId); newPageIds.add(pageId);
@ -47,16 +46,19 @@ class FavoriteListController extends GetxController {
return pageIds.contains(pageInfo.pageid); return pageIds.contains(pageInfo.pageid);
} }
Future<bool> setFavorite( Future<bool> setFavorite(PageInfo pageInfo, bool isFavorite, bool showToast) async {
PageInfo pageInfo, bool isFavorite, bool showToast) async {
// //
var uc = Get.find<UserController>(); var uc = Get.find<UserController>();
if (!uc.isLoggedIn) { if (!uc.isLoggedIn) {
var result = await confirm(Get.overlayContext!, "使用收藏功能需要登录", alert(Get.overlayContext!, "使用收藏功能需要登录", title: "提示");
title: "提示", positiveText: "登录"); return isFavorite;
return false;
} }
return false; if (isFavorite) {
//
} else {
//
}
return !isFavorite;
} }
} }

@ -25,7 +25,6 @@ class AboutPage extends StatelessWidget {
return IsekaiPageScaffold( return IsekaiPageScaffold(
navigationBar: const IsekaiNavigationBar( navigationBar: const IsekaiNavigationBar(
middle: Text('关于'), middle: Text('关于'),
previousPageTitle: "我的",
), ),
child: ListView( child: ListView(
children: [ children: [

@ -1,16 +1,19 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
class DiscoverTab extends StatelessWidget { class DiscoverTab extends StatelessWidget {
const DiscoverTab({super.key}); const DiscoverTab({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const CustomScrollView( return const IsekaiPageScaffold(
slivers: <Widget>[ child: CustomScrollView(
CupertinoSliverNavigationBar( slivers: <Widget>[
largeTitle: Text('发现'), CupertinoSliverNavigationBar(
), largeTitle: Text('发现'),
], ),
],
),
); );
} }
} }

@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.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/recent_page_list.dart';
import 'package:isekai_wiki/models/user.dart'; import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/pages/tab_page.dart'; import 'package:isekai_wiki/pages/tab_page.dart';
@ -127,106 +128,108 @@ class HomeTab extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final c = Get.put(HomeController()); final c = Get.put(HomeController());
return WebSmoothScroll( return IsekaiPageScaffold(
controller: c.scrollController, child: WebSmoothScroll(
child: CustomScrollView(
controller: c.scrollController, controller: c.scrollController,
physics: const BouncingScrollPhysics( child: CustomScrollView(
parent: AlwaysScrollableScrollPhysics(), controller: c.scrollController,
), physics: const BouncingScrollPhysics(
slivers: <Widget>[ parent: AlwaysScrollableScrollPhysics(),
IsekaiSliverNavigationBar(
leading: _buildSearchIconButton(),
backgroundColor: Styles.themeMainColor,
brightness: Brightness.dark,
largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)),
border: Border.all(style: BorderStyle.none),
trailing: _buildNotificationIconButton(),
), ),
SliverPersistentHeader( slivers: <Widget>[
delegate: _SliverAppBarDelegate( IsekaiSliverNavigationBar(
minHeight: 48, leading: _buildSearchIconButton(),
maxHeight: 48, backgroundColor: Styles.themeMainColor,
child: Container( brightness: Brightness.dark,
decoration: const BoxDecoration( largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)),
color: Styles.themeMainColor, border: Border.all(style: BorderStyle.none),
boxShadow: [ trailing: _buildNotificationIconButton(),
BoxShadow( ),
color: Styles.themeMainColor, SliverPersistentHeader(
blurRadius: 0.0, delegate: _SliverAppBarDelegate(
spreadRadius: 0.0, minHeight: 48,
offset: Offset(0, -2), maxHeight: 48,
), child: Container(
], decoration: const BoxDecoration(
), color: Styles.themeMainColor,
padding: const EdgeInsets.only(bottom: 10, left: 12, right: 12), boxShadow: [
child: CupertinoButton( BoxShadow(
color: Colors.white, color: Styles.themeMainColor,
padding: const EdgeInsets.all(0), blurRadius: 0.0,
onPressed: () { spreadRadius: 0.0,
onSearchClick?.call(); offset: Offset(0, -2),
},
child: Wrap(
spacing: 14,
alignment: WrapAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.all(1),
child: const Icon(CupertinoIcons.search, color: Colors.black54),
), ),
const Text("搜索页面...",
textAlign: TextAlign.center, style: TextStyle(color: Colors.black54))
], ],
), ),
padding: const EdgeInsets.only(bottom: 10, left: 12, right: 12),
child: CupertinoButton(
color: Colors.white,
padding: const EdgeInsets.all(0),
onPressed: () {
onSearchClick?.call();
},
child: Wrap(
spacing: 14,
alignment: WrapAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.all(1),
child: const Icon(CupertinoIcons.search, color: Colors.black54),
),
const Text("搜索页面...",
textAlign: TextAlign.center, style: TextStyle(color: Colors.black54))
],
),
),
), ),
), ),
), ),
), SliverPersistentHeader(
SliverPersistentHeader( pinned: true,
pinned: true, delegate: _SliverAppBarDelegate(
delegate: _SliverAppBarDelegate( minHeight: 40.0,
minHeight: 40.0, maxHeight: 40.0,
maxHeight: 40.0, child: Container(
child: Container( decoration: BoxDecoration(color: Colors.white, boxShadow: [
decoration: BoxDecoration(color: Colors.white, boxShadow: [ BoxShadow(
BoxShadow( color: Colors.grey.withOpacity(0.2),
color: Colors.grey.withOpacity(0.2), spreadRadius: 2,
spreadRadius: 2, blurRadius: 4,
blurRadius: 4, offset: const Offset(0, 2),
offset: const Offset(0, 2), )
) ]),
]), child: Row(
child: Row( children: [
children: [ SizedBox(
SizedBox( child: TabBar(
child: TabBar( isScrollable: true,
isScrollable: true, controller: c.tabController,
controller: c.tabController, indicatorColor: Styles.themeMainColor,
indicatorColor: Styles.themeMainColor, labelColor: Styles.themeMainColor,
labelColor: Styles.themeMainColor, unselectedLabelColor: Colors.black45,
unselectedLabelColor: Colors.black45, tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')],
tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')], onTap: (int selected) {},
onTap: (int selected) {}, ),
), ),
), const Expanded(child: Text('')),
const Expanded(child: Text('')), ],
], ),
), ),
), ),
), ),
), CupertinoSliverRefreshControl(
CupertinoSliverRefreshControl( onRefresh: c.handleRefresh,
onRefresh: c.handleRefresh,
),
SliverSafeArea(
top: false,
bottom: false,
minimum: const EdgeInsets.only(top: 12, bottom: 12),
sliver: RecentPageList(
scrollController: c.scrollController,
), ),
), SliverSafeArea(
], top: false,
bottom: false,
minimum: const EdgeInsets.only(top: 12, bottom: 12),
sliver: RecentPageList(
scrollController: c.scrollController,
),
),
],
),
), ),
); );
} }

@ -2,11 +2,12 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:cupertino_lists/cupertino_lists.dart'; import 'package:cupertino_lists/cupertino_lists.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart'; import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/models/user.dart'; import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/pages/about.dart'; import 'package:isekai_wiki/pages/about.dart';
import 'package:isekai_wiki/pages/settings/list.dart';
import 'package:isekai_wiki/styles.dart'; import 'package:isekai_wiki/styles.dart';
import 'package:isekai_wiki/utils/dialog.dart'; import 'package:isekai_wiki/utils/dialog.dart';
@ -52,6 +53,24 @@ class OwnProfileController extends GetxController {
Future<void> handleLogout() async { Future<void> handleLogout() async {
await uc.logout(); await uc.logout();
} }
Future<void> handleSettingsClick(BuildContext context) async {
await Navigator.of(context, rootNavigator: false).push(
CupertinoPageRoute(
title: "设置",
builder: (_) => const SettingsListPage(),
),
);
}
Future<void> handleAboutClick(BuildContext context) async {
await Navigator.of(context, rootNavigator: false).push(
CupertinoPageRoute(
title: "关于",
builder: (_) => const AboutPage(),
),
);
}
} }
class OwnProfileTab extends StatelessWidget { class OwnProfileTab extends StatelessWidget {
@ -83,8 +102,7 @@ class OwnProfileTab extends StatelessWidget {
}); });
} }
Widget _buildUserSection(BuildContext context) { Widget _buildUserSection(BuildContext context, OwnProfileController c) {
var c = Get.find<OwnProfileController>();
var uc = Get.find<UserController>(); var uc = Get.find<UserController>();
return FollowTextScale( return FollowTextScale(
@ -138,7 +156,7 @@ class OwnProfileTab extends StatelessWidget {
); );
} }
Widget _buildArticleListsSection(BuildContext context) { Widget _buildArticleListsSection(BuildContext context, OwnProfileController c) {
return FollowTextScale( return FollowTextScale(
child: CupertinoListSection.insetGrouped( child: CupertinoListSection.insetGrouped(
backgroundColor: Styles.themePageBackgroundColor, backgroundColor: Styles.themePageBackgroundColor,
@ -174,7 +192,7 @@ class OwnProfileTab extends StatelessWidget {
)); ));
} }
Widget _buildSettingsSection(BuildContext context) { Widget _buildSettingsSection(BuildContext context, OwnProfileController c) {
return FollowTextScale( return FollowTextScale(
child: CupertinoListSection.insetGrouped( child: CupertinoListSection.insetGrouped(
backgroundColor: Styles.themePageBackgroundColor, backgroundColor: Styles.themePageBackgroundColor,
@ -186,7 +204,9 @@ class OwnProfileTab extends StatelessWidget {
icon: CupertinoIcons.settings, icon: CupertinoIcons.settings,
), ),
trailing: const CupertinoListTileChevron(), trailing: const CupertinoListTileChevron(),
onTap: () {}, onTap: () async {
await c.handleSettingsClick(context);
},
), ),
CupertinoListTile.notched( CupertinoListTile.notched(
title: const Text('关于'), title: const Text('关于'),
@ -196,11 +216,7 @@ class OwnProfileTab extends StatelessWidget {
), ),
trailing: const CupertinoListTileChevron(), trailing: const CupertinoListTileChevron(),
onTap: () async { onTap: () async {
await Navigator.of(context, rootNavigator: false).push( await c.handleAboutClick(context);
CupertinoPageRoute(
builder: (_) => const AboutPage(),
),
);
}, },
), ),
if (kDebugMode) if (kDebugMode)
@ -217,41 +233,34 @@ class OwnProfileTab extends StatelessWidget {
)); ));
} }
SliverChildBuilderDelegate _buildSliverChildBuilderDelegate(BuildContext context) { SliverChildListDelegate _buildSliverChildBuilderDelegate(
return SliverChildBuilderDelegate( BuildContext context, OwnProfileController c) {
(context, index) { return SliverChildListDelegate([
switch (index) { _buildUserSection(context, c),
case 0: _buildArticleListsSection(context, c),
return _buildUserSection(context); _buildSettingsSection(context, c),
case 1: ]);
return _buildArticleListsSection(context);
case 2:
return _buildSettingsSection(context);
default:
// Do nothing. For now.
}
return null;
},
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(OwnProfileController()); var c = Get.put(OwnProfileController());
return CustomScrollView( return IsekaiPageScaffold(
slivers: <Widget>[ child: CustomScrollView(
const IsekaiSliverNavigationBar( slivers: <Widget>[
largeTitle: Text('我的'), const IsekaiSliverNavigationBar(
), largeTitle: Text('我的'),
SliverSafeArea(
top: false,
minimum: const EdgeInsets.only(top: 4),
sliver: SliverList(
delegate: _buildSliverChildBuilderDelegate(context),
), ),
) SliverSafeArea(
], top: false,
minimum: const EdgeInsets.only(top: 4),
sliver: SliverList(
delegate: _buildSliverChildBuilderDelegate(context, c),
),
)
],
),
); );
} }
} }

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/components/state_test.dart'; import 'package:isekai_wiki/components/state_test.dart';
import 'package:isekai_wiki/styles.dart'; import 'package:isekai_wiki/styles.dart';
@ -15,7 +16,7 @@ class SearchTab extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var c = Get.put(SearchController()); var c = Get.put(SearchController());
return CupertinoPageScaffold( return IsekaiPageScaffold(
navigationBar: const CupertinoNavigationBar( navigationBar: const CupertinoNavigationBar(
middle: Text('搜索'), middle: Text('搜索'),
), ),

@ -0,0 +1,85 @@
import 'package:cupertino_lists/cupertino_lists.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/components/dummy_icon.dart';
import 'package:isekai_wiki/components/follow_scale.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/styles.dart';
class SettingsListController extends GetxController {
VoidFutureCallback? onSelectionChange;
}
class SettingsList extends StatefulWidget {
final String selected;
final VoidFutureCallback? onSelectionChange;
const SettingsList({super.key, this.selected = "", this.onSelectionChange});
@override
State<StatefulWidget> createState() {
return _SettingsListState();
}
}
class _SettingsListState extends ReactiveState<SettingsList> {
var c = SettingsListController();
@override
void receiveProps() {
c.onSelectionChange = widget.onSelectionChange;
}
@override
Widget build(BuildContext context) {
return FollowTextScale(
child: CupertinoListSection.insetGrouped(
backgroundColor: Styles.themePageBackgroundColor,
children: <CupertinoListTile>[
CupertinoListTile.notched(
title: const Text('阅读设置'),
leading: const DummyIcon(
color: CupertinoColors.systemGrey,
icon: CupertinoIcons.textformat,
),
trailing: const CupertinoListTileChevron(),
onTap: () {},
),
],
),
);
}
}
class SettingsListPage extends StatelessWidget {
const SettingsListPage({super.key});
SliverChildListDelegate _buildSliverChildBuilderDelegate(BuildContext context) {
return SliverChildListDelegate([
const SettingsList(),
]);
}
@override
Widget build(BuildContext context) {
return IsekaiPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
const IsekaiSliverNavigationBar(
largeTitle: Text('设置'),
),
SliverSafeArea(
top: false,
minimum: const EdgeInsets.only(top: 4),
sliver: SliverList(
delegate: _buildSliverChildBuilderDelegate(context),
),
)
],
),
);
}
}

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
import 'package:isekai_wiki/pages/discover.dart'; import 'package:isekai_wiki/pages/discover.dart';
import 'package:isekai_wiki/pages/home.dart'; import 'package:isekai_wiki/pages/home.dart';
import 'package:isekai_wiki/pages/search.dart'; import 'package:isekai_wiki/pages/search.dart';
@ -60,8 +61,7 @@ class IsekaiWikiTabsPage extends StatelessWidget {
tabBar: CupertinoTabBar( tabBar: CupertinoTabBar(
backgroundColor: Styles.themeBottomColor, backgroundColor: Styles.themeBottomColor,
activeColor: Styles.themeMainColor, activeColor: Styles.themeMainColor,
border: const Border( border: const Border(top: BorderSide(color: CupertinoColors.systemGrey5, width: 2)),
top: BorderSide(color: CupertinoColors.systemGrey5, width: 2)),
height: 56, height: 56,
onTap: c.handleTapTab, onTap: c.handleTapTab,
items: const <BottomNavigationBarItem>[ items: const <BottomNavigationBarItem>[
@ -86,37 +86,35 @@ class IsekaiWikiTabsPage extends StatelessWidget {
tabBuilder: (context, index) { tabBuilder: (context, index) {
switch (index) { switch (index) {
case 0: case 0:
return CupertinoTabView(builder: (context) { return CupertinoTabView(
return CupertinoPageScaffold( defaultTitle: "首页",
child: HomeTab( builder: (context) => HomeTab(
onSearchClick: c.toSearchPage, onSearchClick: c.toSearchPage,
), ),
); );
});
case 1: case 1:
return CupertinoTabView(builder: (context) { return CupertinoTabView(
return const CupertinoPageScaffold( defaultTitle: "发现",
child: DiscoverTab(), builder: (context) => const DiscoverTab(),
); );
});
case 2: case 2:
return CupertinoTabView(builder: (context) { return CupertinoTabView(
return const CupertinoPageScaffold( defaultTitle: "搜索",
child: SearchTab(), builder: (context) => const SearchTab(),
); );
});
case 3: case 3:
return CupertinoTabView(builder: (context) { return CupertinoTabView(
return const CupertinoPageScaffold( defaultTitle: "我的",
child: OwnProfileTab(), builder: (context) => const OwnProfileTab(),
); );
});
} }
return CupertinoTabView(builder: (context) {
return const CupertinoPageScaffold( return CupertinoTabView(
child: HomeTab(), defaultTitle: "页面不存在",
); builder: (context) => const IsekaiPageScaffold(
}); child: SizedBox(),
),
);
}, },
), ),
); );

Loading…
Cancel
Save