修改路由配置,增加收藏相关的API
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);
|
||||
}
|
||||
}
|
@ -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
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,16 +1,19 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:isekai_wiki/components/isekai_page_scaffold.dart';
|
||||
|
||||
class DiscoverTab extends StatelessWidget {
|
||||
const DiscoverTab({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const CustomScrollView(
|
||||
return const IsekaiPageScaffold(
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
CupertinoSliverNavigationBar(
|
||||
largeTitle: 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),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue