You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
11 KiB
Dart

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 = 140;
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();
Get.put(c);
}
@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
? 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),
),
),
),
);
}
}