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 Function( PageInfo pageInfo, bool localIsFavorite, bool showToast); typedef PageInfoCallback = Future 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(null); var isFavorite = false.obs; AddFavoriteCallback? onSetFavorite; PageInfoCallback? onShare; Future handleFavoriteClick(bool localIsFavorite) async { if (pageInfo.value != null && onSetFavorite != null) { return await onSetFavorite!.call(pageInfo.value!, localIsFavorite, false); } else { return false; } } Future handleAddFavoriteMenuItemClick() async { if (pageInfo.value != null && onSetFavorite != null) { await onSetFavorite!.call(pageInfo.value!, true, true); } } Future 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 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 createState() => _PageCardState(); } class _PageCardState extends ReactiveState { 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 ? 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( init: c, builder: (GetxController c) => _buildCard(context), ), ), ), ); } }