import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get_utils/get_utils.dart'; import 'package:intl/intl.dart'; import 'package:solian/models/post.dart'; import 'package:solian/router.dart'; import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_profile_popup.dart'; import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/posts/post_quick_action.dart'; import 'package:timeago/timeago.dart' show format; import 'package:url_launcher/url_launcher_string.dart'; class PostItem extends StatefulWidget { final Post item; final bool isClickable; final bool isCompact; final bool isReactable; final bool isShowReply; final bool isShowEmbed; final bool isFullDate; final String? overrideAttachmentParent; const PostItem({ super.key, required this.item, this.isClickable = false, this.isCompact = false, this.isReactable = true, this.isShowReply = true, this.isShowEmbed = true, this.isFullDate = false, this.overrideAttachmentParent, }); @override State createState() => _PostItemState(); } class _PostItemState extends State { late final Post item; @override void initState() { item = widget.item; super.initState(); } Widget buildDate() { if (widget.isFullDate) { return Text(DateFormat('y/M/d H:m').format(item.createdAt.toLocal())); } else { return Text(format(item.createdAt.toLocal(), locale: 'en_short')); } } Widget buildHeader() { return Row( children: [ Text( item.author.nick, style: const TextStyle(fontWeight: FontWeight.bold), ).paddingOnly(left: 12), buildDate().paddingOnly(left: 4), ], ); } Widget buildBody() { return Markdown( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), data: item.content, padding: const EdgeInsets.all(0), onTapLink: (text, href, title) async { if (href == null) return; await launchUrlString( href, mode: LaunchMode.externalApplication, ); }, ); } Widget buildFooter() { List labels = List.empty(growable: true); if (widget.item.createdAt != widget.item.updatedAt) { labels.add('postEdited'.trParams({ 'date': DateFormat('yy/M/d H:m').format(item.updatedAt.toLocal()), })); } if (widget.item.realm != null) { labels.add('postInRealm'.trParams({ 'realm': '#${widget.item.realm!.alias}', })); } if (labels.isNotEmpty) { return Text( labels.join(" ยท "), textAlign: TextAlign.left, style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75), ), ).paddingOnly(top: 8); } else { return const SizedBox(); } } Widget buildReply(BuildContext context) { return Column( children: [ Row( children: [ FaIcon( FontAwesomeIcons.reply, size: 16, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75), ), Text( 'postRepliedNotify'.trParams( {'username': '@${widget.item.replyTo!.author.name}'}, ), style: TextStyle( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75), ), ).paddingOnly(left: 6), ], ).paddingOnly(left: 12), Card( elevation: 1, child: PostItem( item: widget.item.replyTo!, isCompact: true, overrideAttachmentParent: widget.item.alias, ).paddingSymmetric(vertical: 8), ), ], ); } Widget buildRepost(BuildContext context) { return Column( children: [ Row( children: [ FaIcon( FontAwesomeIcons.retweet, size: 16, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75), ), Text( 'postRepostedNotify'.trParams( {'username': '@${widget.item.repostTo!.author.name}'}, ), style: TextStyle( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75), ), ).paddingOnly(left: 6), ], ).paddingOnly(left: 12), Card( elevation: 1, child: PostItem( item: widget.item.repostTo!, isCompact: true, overrideAttachmentParent: widget.item.alias, ).paddingSymmetric(vertical: 8), ), ], ); } @override Widget build(BuildContext context) { final hasAttachment = item.attachments?.isNotEmpty ?? false; if (widget.isCompact) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildHeader().paddingSymmetric(horizontal: 12), buildBody().paddingOnly( left: 16, right: 12, top: 2, bottom: hasAttachment ? 4 : 0, ), buildFooter().paddingOnly(left: 16), AttachmentList( parentId: widget.overrideAttachmentParent ?? widget.item.alias, attachmentsId: item.attachments ?? List.empty(), divided: true, ), ], ); } return Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( child: AccountAvatar(content: item.author.avatar.toString()), onTap: () { showModalBottomSheet( useRootNavigator: true, isScrollControlled: true, backgroundColor: Theme.of(context).colorScheme.surface, context: context, builder: (context) => AccountProfilePopup( account: item.author, ), ); }, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildHeader(), buildBody().paddingOnly(left: 12, right: 8), if (widget.item.replyTo != null && widget.isShowEmbed) GestureDetector( child: buildReply(context).paddingOnly(top: 4), onTap: () { if (!widget.isClickable) return; AppRouter.instance.pushNamed( 'postDetail', pathParameters: { 'alias': widget.item.replyTo!.alias, }, ); }, ), if (widget.item.repostTo != null && widget.isShowEmbed) GestureDetector( child: buildRepost(context).paddingOnly(top: 4), onTap: () { if (!widget.isClickable) return; AppRouter.instance.pushNamed( 'postDetail', pathParameters: { 'alias': widget.item.repostTo!.alias, }, ); }, ), buildFooter().paddingOnly(left: 12), ], ), ) ], ).paddingOnly( top: 10, bottom: hasAttachment ? 10 : 0, right: 16, left: 16, ), AttachmentList( parentId: widget.item.alias, attachmentsId: item.attachments ?? List.empty(), divided: true, ), PostQuickAction( isShowReply: widget.isShowReply, isReactable: widget.isReactable, item: widget.item, onReact: (symbol, changes) { setState(() { item.reactionList[symbol] = (item.reactionList[symbol] ?? 0) + changes; }); }, ).paddingOnly( top: hasAttachment ? 10 : 6, left: hasAttachment ? 16 : 60, right: 16, bottom: 10, ), ], ); } }