💄 Redesigned post item
This commit is contained in:
		| @@ -837,5 +837,6 @@ | ||||
|   "fieldContactContent": "Contact method", | ||||
|   "accountContactMethodsPublicHint": "This contact method will be displayed publicly on your profile.", | ||||
|   "accountContactMethodsDelete": "Delete Contact Method", | ||||
|   "accountContactMethodsDeleteDescription": "Are you sure you want to delete contact method {}? This operation is irreversible." | ||||
|   "accountContactMethodsDeleteDescription": "Are you sure you want to delete contact method {}? This operation is irreversible.", | ||||
|   "postCommentAdd": "Write a comment" | ||||
| } | ||||
|   | ||||
| @@ -837,5 +837,6 @@ | ||||
|   "fieldContactContent": "联系方式", | ||||
|   "accountContactMethodsPublicHint": "这个联系方式公开地显示在个人资料中。", | ||||
|   "accountContactMethodsDelete": "删除联系方式", | ||||
|   "accountContactMethodsDeleteDescription": "你确定要删除联系方式 {} 吗?这个操作不可撤销。" | ||||
|   "accountContactMethodsDeleteDescription": "你确定要删除联系方式 {} 吗?这个操作不可撤销。", | ||||
|   "postCommentAdd": "撰写一条评论" | ||||
| } | ||||
|   | ||||
| @@ -837,5 +837,6 @@ | ||||
|   "fieldContactContent": "聯繫方式", | ||||
|   "accountContactMethodsPublicHint": "這個聯繫方式公開地顯示在個人資料中。", | ||||
|   "accountContactMethodsDelete": "刪除聯繫方式", | ||||
|   "accountContactMethodsDeleteDescription": "你確定要刪除聯繫方式 {} 嗎?這個操作不可撤銷。" | ||||
|   "accountContactMethodsDeleteDescription": "你確定要刪除聯繫方式 {} 嗎?這個操作不可撤銷。", | ||||
|   "postCommentAdd": "撰寫一條評論" | ||||
| } | ||||
|   | ||||
| @@ -837,5 +837,6 @@ | ||||
|   "fieldContactContent": "聯繫方式", | ||||
|   "accountContactMethodsPublicHint": "這個聯繫方式公開地顯示在個人資料中。", | ||||
|   "accountContactMethodsDelete": "刪除聯繫方式", | ||||
|   "accountContactMethodsDeleteDescription": "你確定要刪除聯繫方式 {} 嗎?這個操作不可撤銷。" | ||||
|   "accountContactMethodsDeleteDescription": "你確定要刪除聯繫方式 {} 嗎?這個操作不可撤銷。", | ||||
|   "postCommentAdd": "撰寫一條評論" | ||||
| } | ||||
|   | ||||
| @@ -551,12 +551,18 @@ class _PostListWidgetState extends State<_PostListWidget> { | ||||
|                   maxWidth: 640, | ||||
|                 ); | ||||
|               case 'reader.news': | ||||
|                 return NewsFeedEntry(data: ele); | ||||
|                 return Container( | ||||
|                   constraints: BoxConstraints(maxWidth: 640), | ||||
|                   child: NewsFeedEntry(data: ele), | ||||
|                 ); | ||||
|               default: | ||||
|                 return FeedUnknownEntry(data: ele); | ||||
|                 return Container( | ||||
|                   constraints: BoxConstraints(maxWidth: 640), | ||||
|                   child: FeedUnknownEntry(data: ele), | ||||
|                 ); | ||||
|             } | ||||
|           }, | ||||
|           separatorBuilder: (_, __) => const Gap(8), | ||||
|           separatorBuilder: (_, __) => const Divider().padding(vertical: 2), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import 'package:provider/provider.dart'; | ||||
| import 'package:responsive_framework/responsive_framework.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/post.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/types/post.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| @@ -14,14 +15,13 @@ import 'package:surface/widgets/post/post_item.dart'; | ||||
| import 'package:surface/widgets/post/post_mini_editor.dart'; | ||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||
|  | ||||
| import '../../providers/sn_network.dart'; | ||||
|  | ||||
| class PostCommentQuickAction extends StatelessWidget { | ||||
|   final double? maxWidth; | ||||
|   final SnPost parentPost; | ||||
|   final Function? onPosted; | ||||
|  | ||||
|   const PostCommentQuickAction({super.key, this.maxWidth, required this.parentPost, this.onPosted}); | ||||
|   const PostCommentQuickAction( | ||||
|       {super.key, this.maxWidth, required this.parentPost, this.onPosted}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -30,7 +30,9 @@ class PostCommentQuickAction extends StatelessWidget { | ||||
|     return Container( | ||||
|       height: 240, | ||||
|       constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), | ||||
|       margin: ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.symmetric(vertical: 8) : EdgeInsets.zero, | ||||
|       margin: ResponsiveBreakpoints.of(context).largerThan(MOBILE) | ||||
|           ? const EdgeInsets.symmetric(vertical: 8) | ||||
|           : EdgeInsets.zero, | ||||
|       decoration: BoxDecoration( | ||||
|         borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE) | ||||
|             ? const BorderRadius.all(Radius.circular(8)) | ||||
| @@ -99,7 +101,8 @@ class PostCommentSliverListState extends State<PostCommentSliverList> { | ||||
|   Future<void> _selectAnswer(SnPost answer) async { | ||||
|     try { | ||||
|       final sn = context.read<SnNetworkProvider>(); | ||||
|       await sn.client.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: { | ||||
|       await sn.client | ||||
|           .put('/cgi/co/questions/${widget.parentPost.id}/answer', data: { | ||||
|         'publisher': answer.publisherId, | ||||
|         'answer_id': answer.id, | ||||
|       }); | ||||
| @@ -135,7 +138,9 @@ class PostCommentSliverListState extends State<PostCommentSliverList> { | ||||
|           child: PostItem( | ||||
|             data: _posts[idx], | ||||
|             maxWidth: widget.maxWidth, | ||||
|             onSelectAnswer: widget.parentPost.type == 'question' ? () => _selectAnswer(_posts[idx]) : null, | ||||
|             onSelectAnswer: widget.parentPost.type == 'question' | ||||
|                 ? () => _selectAnswer(_posts[idx]) | ||||
|                 : null, | ||||
|             onChanged: (data) { | ||||
|               setState(() => _posts[idx] = data); | ||||
|             }, | ||||
| @@ -153,7 +158,8 @@ class PostCommentSliverListState extends State<PostCommentSliverList> { | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|       separatorBuilder: (context, index) => const Divider(height: 1), | ||||
|       separatorBuilder: (context, index) => | ||||
|           const Divider().padding(vertical: 2), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -188,7 +194,9 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> { | ||||
|           children: [ | ||||
|             const Icon(Symbols.comment, size: 24), | ||||
|             const Gap(16), | ||||
|             Text('postCommentsDetailed').plural(widget.commentCount).textStyle(Theme.of(context).textTheme.titleLarge!), | ||||
|             Text('postCommentsDetailed') | ||||
|                 .plural(widget.commentCount) | ||||
|                 .textStyle(Theme.of(context).textTheme.titleLarge!), | ||||
|           ], | ||||
|         ).padding(horizontal: 20, top: 16, bottom: 12), | ||||
|         Expanded( | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:file_saver/file_saver.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| @@ -72,7 +72,9 @@ class OpenablePostItem extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     final cfg = context.read<ConfigProvider>(); | ||||
|  | ||||
|     return Center( | ||||
|     return Container( | ||||
|       constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), | ||||
|       child: Center( | ||||
|         child: OpenContainer( | ||||
|           closedBuilder: (_, __) => Container( | ||||
|             constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), | ||||
| @@ -94,14 +96,15 @@ class OpenablePostItem extends StatelessWidget { | ||||
|           openColor: Colors.transparent, | ||||
|           openElevation: 0, | ||||
|           transitionType: ContainerTransitionType.fade, | ||||
|         closedColor: | ||||
|             Theme.of(context).colorScheme.surfaceContainerLow.withOpacity( | ||||
|           closedElevation: 0, | ||||
|           closedColor: Theme.of(context).colorScheme.surface.withOpacity( | ||||
|                 cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1, | ||||
|               ), | ||||
|           closedShape: const RoundedRectangleBorder( | ||||
|             borderRadius: BorderRadius.all(Radius.circular(16)), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -202,61 +205,6 @@ class PostItem extends StatelessWidget { | ||||
|     final ua = context.read<UserProvider>(); | ||||
|     final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id; | ||||
|  | ||||
|     // Video full view | ||||
|     if (showFullPost && | ||||
|         data.type == 'video' && | ||||
|         ResponsiveBreakpoints.of(context).largerThan(TABLET)) { | ||||
|       return Row( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           const Gap(16), | ||||
|           Expanded( | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 _PostContentHeader( | ||||
|                   data: data, | ||||
|                   isAuthor: isAuthor, | ||||
|                   isRelativeDate: !showFullPost, | ||||
|                   onShare: () => _doShare(context), | ||||
|                   onShareImage: () => _doShareViaPicture(context), | ||||
|                   onSelectAnswer: onSelectAnswer, | ||||
|                   onDeleted: () { | ||||
|                     onDeleted?.call(); | ||||
|                   }, | ||||
|                 ).padding(bottom: 8), | ||||
|                 if (data.preload?.video != null) | ||||
|                   _PostVideoPlayer(data: data).padding(bottom: 8), | ||||
|                 _PostHeadline(data: data).padding(horizontal: 4, bottom: 8), | ||||
|                 _PostFeaturedComment(data: data), | ||||
|                 _PostBottomAction( | ||||
|                   data: data, | ||||
|                   showComments: true, | ||||
|                   showReactions: showReactions, | ||||
|                   onShare: () => _doShare(context), | ||||
|                   onShareImage: () => _doShareViaPicture(context), | ||||
|                   onChanged: _onChanged, | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|           const Gap(4), | ||||
|           SizedBox( | ||||
|             width: 340, | ||||
|             child: CustomScrollView( | ||||
|               shrinkWrap: true, | ||||
|               slivers: [ | ||||
|                 PostCommentSliverList( | ||||
|                   parentPost: data, | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     // Article headline preview | ||||
|     if (!showFullPost && data.type == 'article') { | ||||
|       return Container( | ||||
|         constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), | ||||
| @@ -265,14 +213,7 @@ class PostItem extends StatelessWidget { | ||||
|           children: [ | ||||
|             _PostContentHeader( | ||||
|               data: data, | ||||
|               isAuthor: isAuthor, | ||||
|               isRelativeDate: !showFullPost, | ||||
|               onShare: () => _doShare(context), | ||||
|               onShareImage: () => _doShareViaPicture(context), | ||||
|               onSelectAnswer: onSelectAnswer, | ||||
|               onDeleted: () { | ||||
|                 onDeleted?.call(); | ||||
|               }, | ||||
|             ).padding(horizontal: 12, top: 8, bottom: 8), | ||||
|             if (data.preload?.video != null) | ||||
|               _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), | ||||
| @@ -325,14 +266,6 @@ class PostItem extends StatelessWidget { | ||||
|                 .padding(horizontal: 24, bottom: 8), | ||||
|             _PostFeaturedComment(data: data, maxWidth: maxWidth) | ||||
|                 .padding(horizontal: 12), | ||||
|             _PostBottomAction( | ||||
|               data: data, | ||||
|               showComments: showComments, | ||||
|               showReactions: showReactions, | ||||
|               onShare: () => _doShare(context), | ||||
|               onShareImage: () => _doShareViaPicture(context), | ||||
|               onChanged: _onChanged, | ||||
|             ).padding(left: 8, right: 14), | ||||
|           ], | ||||
|         ), | ||||
|       ).center(); | ||||
| @@ -345,6 +278,12 @@ class PostItem extends StatelessWidget { | ||||
|  | ||||
|     final cfg = context.read<ConfigProvider>(); | ||||
|  | ||||
|     var attachmentSize = math.min( | ||||
|         MediaQuery.of(context).size.width, maxWidth ?? double.infinity); | ||||
|     if ((data.preload?.attachments?.length ?? 0) > 1) { | ||||
|       attachmentSize -= 80; | ||||
|     } | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|       children: [ | ||||
| @@ -353,54 +292,85 @@ class PostItem extends StatelessWidget { | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               _PostContentHeader( | ||||
|                 isAuthor: isAuthor, | ||||
|                 isRelativeDate: !showFullPost, | ||||
|               Row( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   _PostAvatar( | ||||
|                     data: data, | ||||
|                 showMenu: showMenu, | ||||
|                     isCompact: false, | ||||
|                   ), | ||||
|                   const Gap(12), | ||||
|                   Expanded( | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         Row( | ||||
|                           children: [ | ||||
|                             Expanded( | ||||
|                               child: _PostContentHeader( | ||||
|                                 isRelativeDate: !showFullPost, | ||||
|                                 isCompact: true, | ||||
|                                 data: data, | ||||
|                               ), | ||||
|                             ), | ||||
|                             _PostActionPopup( | ||||
|                               data: data, | ||||
|                               isAuthor: isAuthor, | ||||
|                               onShare: () => _doShare(context), | ||||
|                               onShareImage: () => _doShareViaPicture(context), | ||||
|                               onSelectAnswer: onSelectAnswer, | ||||
|                               onDeleted: () { | ||||
|                                 onDeleted?.call(); | ||||
|                               }, | ||||
|               ).padding(horizontal: 12, vertical: 8), | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                         const Gap(8), | ||||
|                         if (data.preload?.video != null) | ||||
|                 _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8), | ||||
|                           _PostVideoPlayer(data: data).padding(bottom: 8), | ||||
|                         if (data.type == 'question') | ||||
|                 _PostQuestionHint(data: data) | ||||
|                     .padding(horizontal: 16, bottom: 8), | ||||
|                           _PostQuestionHint(data: data).padding(bottom: 8), | ||||
|                         if (data.body['title'] != null || | ||||
|                             data.body['description'] != null) | ||||
|                           _PostHeadline( | ||||
|                             data: data, | ||||
|                             isEnlarge: data.type == 'article' && showFullPost, | ||||
|                 ).padding(horizontal: 16, bottom: 8), | ||||
|                           ).padding(bottom: 8), | ||||
|                         if (data.body['content']?.isNotEmpty ?? false) | ||||
|                           _PostContentBody( | ||||
|                             data: data, | ||||
|                             isSelectable: showFullPost, | ||||
|                             isEnlarge: data.type == 'article' && showFullPost, | ||||
|                 ).padding(horizontal: 16, bottom: 6), | ||||
|                           ).padding(bottom: 6), | ||||
|                         if (data.repostTo != null) | ||||
|                           _PostQuoteContent(child: data.repostTo!).padding( | ||||
|                   horizontal: 12, | ||||
|                             bottom: | ||||
|                       data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0, | ||||
|                                 data.preload?.attachments?.isNotEmpty ?? false | ||||
|                                     ? 12 | ||||
|                                     : 0, | ||||
|                           ), | ||||
|                         if (data.visibility > 0) | ||||
|                           _PostVisibilityHint(data: data).padding( | ||||
|                   horizontal: 16, | ||||
|                             vertical: 4, | ||||
|                           ), | ||||
|                         if (data.body['content_truncated'] == true) | ||||
|                           _PostTruncatedHint(data: data).padding( | ||||
|                   horizontal: 16, | ||||
|                             vertical: 4, | ||||
|                           ), | ||||
|                         if (data.tags.isNotEmpty) | ||||
|                 _PostTagsList(data: data) | ||||
|                     .padding(horizontal: 16, top: 4, bottom: 6), | ||||
|                           _PostTagsList(data: data).padding(top: 4, bottom: 6), | ||||
|                         Row( | ||||
|                           children: [ | ||||
|                             Icon(Symbols.play_circle, size: 20), | ||||
|                             const Gap(4), | ||||
|                             Text('postViews').plural(data.totalViews), | ||||
|                           ], | ||||
|                         ).opacity(0.75).padding(vertical: 4), | ||||
|                       ], | ||||
|                     ), | ||||
|                   ) | ||||
|                 ], | ||||
|               ).padding(horizontal: 12, top: 8), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
| @@ -409,9 +379,10 @@ class PostItem extends StatelessWidget { | ||||
|             data: displayableAttachments!, | ||||
|             bordered: true, | ||||
|             maxHeight: showFullPost ? null : 480, | ||||
|             maxWidth: MediaQuery.of(context).size.width - 20, | ||||
|             minWidth: attachmentSize, | ||||
|             maxWidth: attachmentSize, | ||||
|             fit: showFullPost ? BoxFit.cover : BoxFit.contain, | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 12), | ||||
|             padding: const EdgeInsets.only(left: 60, right: 12), | ||||
|           ), | ||||
|         if (data.preload?.poll != null) | ||||
|           PostPoll(poll: data.preload!.poll!) | ||||
| @@ -420,22 +391,15 @@ class PostItem extends StatelessWidget { | ||||
|             (cfg.prefs.getBool(kAppExpandPostLink) ?? true)) | ||||
|           LinkPreviewWidget( | ||||
|             text: data.body['content'], | ||||
|           ).padding(horizontal: 4), | ||||
|           ).padding(left: 60, right: 4), | ||||
|         _PostFeaturedComment(data: data, maxWidth: maxWidth) | ||||
|             .padding(horizontal: 12), | ||||
|         Container( | ||||
|           constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               _PostBottomAction( | ||||
|             .padding(left: 60, right: 12), | ||||
|         Padding( | ||||
|           padding: const EdgeInsets.only(top: 4), | ||||
|           child: _PostReactionList( | ||||
|             data: data, | ||||
|                 showComments: showComments, | ||||
|                 showReactions: showReactions, | ||||
|                 onShare: () => _doShare(context), | ||||
|                 onShareImage: () => _doShareViaPicture(context), | ||||
|             padding: const EdgeInsets.only(left: 60, right: 12), | ||||
|             onChanged: _onChanged, | ||||
|               ).padding(left: 8, right: 14), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
| @@ -476,12 +440,7 @@ class PostShareImageWidget extends StatelessWidget { | ||||
|               ), | ||||
|             ).padding(bottom: 8), | ||||
|           _PostContentHeader( | ||||
|             isAuthor: false, | ||||
|             data: data, | ||||
|             onDeleted: () {}, | ||||
|             onShare: () {}, | ||||
|             onShareImage: () {}, | ||||
|             showMenu: false, | ||||
|             isRelativeDate: false, | ||||
|           ).padding(horizontal: 16, bottom: 8), | ||||
|           if (data.type == 'question') | ||||
| @@ -516,14 +475,6 @@ class PostShareImageWidget extends StatelessWidget { | ||||
|                 _PostTruncatedHint(data: data), | ||||
|             ], | ||||
|           ).padding(horizontal: 16), | ||||
|           _PostBottomAction( | ||||
|             data: data, | ||||
|             showComments: true, | ||||
|             showReactions: true, | ||||
|             onShare: () {}, | ||||
|             onShareImage: () {}, | ||||
|             onChanged: (SnPost data) {}, | ||||
|           ).padding(left: 8, right: 14), | ||||
|           const Divider(height: 1), | ||||
|           const Gap(12), | ||||
|           SizedBox( | ||||
| @@ -622,50 +573,90 @@ class _PostQuestionHint extends StatelessWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostBottomAction extends StatelessWidget { | ||||
| class _PostReactionList extends StatefulWidget { | ||||
|   final SnPost data; | ||||
|   final bool showComments; | ||||
|   final bool showReactions; | ||||
|   final Function(SnPost data) onChanged; | ||||
|   final Function() onShare, onShareImage; | ||||
|  | ||||
|   const _PostBottomAction({ | ||||
|   final EdgeInsets? padding; | ||||
|   final Function(SnPost) onChanged; | ||||
|   const _PostReactionList({ | ||||
|     required this.data, | ||||
|     required this.showComments, | ||||
|     required this.showReactions, | ||||
|     this.padding, | ||||
|     required this.onChanged, | ||||
|     required this.onShare, | ||||
|     required this.onShareImage, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final iconColor = Theme.of(context).colorScheme.onSurface.withAlpha( | ||||
|           (255 * 0.8).round(), | ||||
|         ); | ||||
|   State<_PostReactionList> createState() => _PostReactionListState(); | ||||
| } | ||||
|  | ||||
|     final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty | ||||
|         ? data.metric.reactionList.entries | ||||
| class _PostReactionListState extends State<_PostReactionList> { | ||||
|   bool _isSubmitting = false; | ||||
|   late int _totalUpvote = widget.data.totalUpvote; | ||||
|   late int _totalDownvote = widget.data.totalDownvote; | ||||
|   late Map<String, int> _reactions = Map.from(widget.data.metric.reactionList); | ||||
|  | ||||
|   Future<void> _reactPost(String symbol, int attitude) async { | ||||
|     if (_isSubmitting) return; | ||||
|  | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     try { | ||||
|       setState(() => _isSubmitting = true); | ||||
|       final resp = await sn.client.post( | ||||
|         '/cgi/co/posts/${widget.data.id}/react', | ||||
|         data: { | ||||
|           'symbol': symbol, | ||||
|           'attitude': attitude, | ||||
|         }, | ||||
|       ); | ||||
|       if (resp.statusCode == 201) { | ||||
|         _reactions[symbol] = (_reactions[symbol] ?? 0) + 1; | ||||
|         widget.onChanged( | ||||
|           widget.data.copyWith( | ||||
|             metric: widget.data.metric.copyWith(reactionList: _reactions), | ||||
|           ), | ||||
|         ); | ||||
|       } else if (resp.statusCode == 204) { | ||||
|         _reactions[symbol] = (_reactions[symbol] ?? 0) - 1; | ||||
|         widget.onChanged( | ||||
|           widget.data.copyWith( | ||||
|             metric: widget.data.metric.copyWith(reactionList: _reactions), | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
|       if (attitude == 1) { | ||||
|         setState(() => _totalUpvote += 1); | ||||
|       } else if (attitude == 2) { | ||||
|         setState(() => _totalDownvote += 1); | ||||
|       } | ||||
|       HapticFeedback.heavyImpact(); | ||||
|     } catch (err) { | ||||
|       if (mounted) context.showErrorDialog(err); | ||||
|     } finally { | ||||
|       setState(() => _isSubmitting = false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SizedBox( | ||||
|       height: 40, | ||||
|       child: ListView.separated( | ||||
|         padding: widget.padding, | ||||
|         itemCount: widget.data.metric.reactionList.length + 1, | ||||
|         scrollDirection: Axis.horizontal, | ||||
|         itemBuilder: (context, index) { | ||||
|           if (index == 0) { | ||||
|             final String? mostTypicalReaction = _reactions.isNotEmpty | ||||
|                 ? _reactions.entries | ||||
|                     .reduce((a, b) => a.value > b.value ? a : b) | ||||
|                     .key | ||||
|                 : null; | ||||
|  | ||||
|             return Row( | ||||
|       mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|               children: [ | ||||
|         if (showReactions || showComments) | ||||
|           Row( | ||||
|             spacing: 8, | ||||
|             children: [ | ||||
|               if (showReactions) | ||||
|                 InkWell( | ||||
|                   child: Row( | ||||
|                     children: [ | ||||
|                       if (mostTypicalReaction == null || | ||||
|                           kTemplateReactions[mostTypicalReaction] == null) | ||||
|                         Icon(Symbols.add_reaction, size: 20, color: iconColor) | ||||
|                       else | ||||
|                         Text( | ||||
|                 ActionChip( | ||||
|                   avatar: (kTemplateReactions[mostTypicalReaction] == null) | ||||
|                       ? Icon(Symbols.add_reaction, size: 20) | ||||
|                       : Text( | ||||
|                           kTemplateReactions[mostTypicalReaction]!.icon, | ||||
|                           style: TextStyle( | ||||
|                             fontSize: 16, | ||||
| @@ -673,81 +664,73 @@ class _PostBottomAction extends StatelessWidget { | ||||
|                             letterSpacing: 0, | ||||
|                           ), | ||||
|                         ), | ||||
|                       const Gap(8), | ||||
|                       if (data.totalUpvote > 0 && | ||||
|                           data.totalUpvote >= data.totalDownvote) | ||||
|                         Text('postReactionUpvote').plural( | ||||
|                           data.totalUpvote, | ||||
|                         ) | ||||
|                       else if (data.totalDownvote > 0) | ||||
|                         Text('postReactionDownvote').plural( | ||||
|                           data.totalDownvote, | ||||
|                         ) | ||||
|                       else | ||||
|                         Text('postReact').tr(), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 8, vertical: 8), | ||||
|                   onTap: () { | ||||
|                   label: (_totalUpvote > 0 && _totalUpvote >= _totalDownvote) | ||||
|                       ? Text('postReactionUpvote').plural(_totalUpvote) | ||||
|                       : (_totalDownvote > 0) | ||||
|                           ? Text('postReactionDownvote').plural(_totalDownvote) | ||||
|                           : Text('postReact').tr(), | ||||
|                   visualDensity: VisualDensity(horizontal: -4, vertical: -4), | ||||
|                   onPressed: () { | ||||
|                     showModalBottomSheet( | ||||
|                       context: context, | ||||
|                       builder: (context) => PostReactionPopup( | ||||
|                         data: data, | ||||
|                         data: widget.data, | ||||
|                         onChanged: (value, attr, delta) { | ||||
|                           onChanged(data.copyWith( | ||||
|                           final metric = | ||||
|                               widget.data.metric.copyWith(reactionList: value); | ||||
|                           if (attr == 1) { | ||||
|                             _totalUpvote += delta; | ||||
|                           } else if (attr == 2) { | ||||
|                             _totalDownvote += delta; | ||||
|                           } | ||||
|                           _reactions = Map.from(metric.reactionList); | ||||
|                           widget.onChanged(widget.data.copyWith( | ||||
|                             totalUpvote: attr == 1 | ||||
|                                 ? data.totalUpvote + delta | ||||
|                                 : data.totalUpvote, | ||||
|                                 ? widget.data.totalUpvote + delta | ||||
|                                 : widget.data.totalUpvote, | ||||
|                             totalDownvote: attr == 2 | ||||
|                                 ? data.totalDownvote + delta | ||||
|                                 : data.totalDownvote, | ||||
|                             metric: data.metric.copyWith(reactionList: value), | ||||
|                                 ? widget.data.totalDownvote + delta | ||||
|                                 : widget.data.totalDownvote, | ||||
|                             metric: metric, | ||||
|                           )); | ||||
|                         }, | ||||
|                       ), | ||||
|                     ); | ||||
|                   }, | ||||
|                 ), | ||||
|               if (showComments) | ||||
|                 InkWell( | ||||
|                   child: Row( | ||||
|                     children: [ | ||||
|                       Icon(Symbols.comment, size: 20, color: iconColor), | ||||
|                       const Gap(8), | ||||
|                       Text('postComments').plural(data.metric.replyCount), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 8, vertical: 8), | ||||
|                   onTap: () { | ||||
|                     showModalBottomSheet( | ||||
|                       context: context, | ||||
|                       useRootNavigator: true, | ||||
|                       builder: (context) => PostCommentListPopup( | ||||
|                         post: data, | ||||
|                         commentCount: data.metric.replyCount, | ||||
|                 if (_reactions.isNotEmpty) const Gap(8), | ||||
|                 if (_reactions.isNotEmpty) | ||||
|                   SizedBox( | ||||
|                     width: 1, | ||||
|                     height: 20, | ||||
|                     child: const VerticalDivider(width: 1), | ||||
|                   ), | ||||
|                 if (_reactions.isNotEmpty) const Gap(4), | ||||
|               ], | ||||
|             ); | ||||
|           } | ||||
|  | ||||
|           final ele = _reactions.entries.elementAt(index - 1); | ||||
|           return ActionChip( | ||||
|             avatar: Text(kTemplateReactions[ele.key]!.icon), | ||||
|             label: Row( | ||||
|               mainAxisSize: MainAxisSize.min, | ||||
|               spacing: 4, | ||||
|               children: [ | ||||
|                 Text(ele.key), | ||||
|                 Text('x${ele.value}').bold(), | ||||
|               ], | ||||
|             ), | ||||
|             visualDensity: VisualDensity(horizontal: -4, vertical: -4), | ||||
|             onPressed: _isSubmitting | ||||
|                 ? null | ||||
|                 : () { | ||||
|                     _reactPost(ele.key, kTemplateReactions[ele.key]!.attitude); | ||||
|                   }, | ||||
|           ); | ||||
|         }, | ||||
|         separatorBuilder: (_, __) => const Gap(4), | ||||
|       ), | ||||
|               InkWell( | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     Icon(Symbols.play_circle, size: 20, color: iconColor), | ||||
|                     const Gap(8), | ||||
|                     Text('postViews').plural(data.totalViews), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         InkWell( | ||||
|           onTap: onShare, | ||||
|           onLongPress: onShareImage, | ||||
|           child: Icon( | ||||
|             Symbols.share, | ||||
|             size: 20, | ||||
|             color: iconColor, | ||||
|           ).padding(horizontal: 8, vertical: 8), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -849,22 +832,81 @@ class _PostHeadline extends StatelessWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostContentHeader extends StatelessWidget { | ||||
| class _PostAvatar extends StatelessWidget { | ||||
|   final SnPost data; | ||||
|   final bool isCompact; | ||||
|   const _PostAvatar({required this.data, required this.isCompact}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ud = context.read<UserDirectoryProvider>(); | ||||
|     final user = data.publisher.type == 0 | ||||
|         ? ud.getFromCache(data.publisher.accountId) | ||||
|         : null; | ||||
|  | ||||
|     return GestureDetector( | ||||
|       child: data.preload?.realm == null | ||||
|           ? AccountImage( | ||||
|               content: data.publisher.avatar, | ||||
|               radius: isCompact ? 12 : 20, | ||||
|               borderRadius: data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20, | ||||
|               badge: (user?.badges.isNotEmpty ?? false) | ||||
|                   ? AccountBadge( | ||||
|                       badge: user!.badges.first, | ||||
|                       radius: 16, | ||||
|                       padding: EdgeInsets.all(2), | ||||
|                     ) | ||||
|                   : null, | ||||
|             ) | ||||
|           : AccountImage( | ||||
|               content: data.preload!.realm!.avatar, | ||||
|               radius: isCompact ? 12 : 20, | ||||
|               borderRadius: isCompact ? 4 : 8, | ||||
|               badgeOffset: Offset(-6, -4), | ||||
|               badge: Container( | ||||
|                 decoration: BoxDecoration( | ||||
|                   border: Border.all( | ||||
|                     color: Theme.of(context).colorScheme.surface, | ||||
|                     width: 2, | ||||
|                   ), | ||||
|                   borderRadius: BorderRadius.circular(10), | ||||
|                 ), | ||||
|                 child: AccountImage( | ||||
|                   content: data.publisher.avatar, | ||||
|                   radius: 10, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|       onTap: () { | ||||
|         showPopover( | ||||
|           backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|           context: context, | ||||
|           transition: PopoverTransition.other, | ||||
|           bodyBuilder: (context) => SizedBox( | ||||
|             width: math.min(400, MediaQuery.of(context).size.width - 10), | ||||
|             child: PublisherPopoverCard( | ||||
|               data: data.publisher, | ||||
|             ), | ||||
|           ), | ||||
|           direction: PopoverDirection.bottom, | ||||
|           arrowHeight: 5, | ||||
|           arrowWidth: 15, | ||||
|           arrowDxOffset: -190, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostActionPopup extends StatelessWidget { | ||||
|   final SnPost data; | ||||
|   final bool isAuthor; | ||||
|   final bool isCompact; | ||||
|   final bool isRelativeDate; | ||||
|   final bool showMenu; | ||||
|   final Function onDeleted; | ||||
|   final Function() onShare, onShareImage; | ||||
|   final Function()? onSelectAnswer; | ||||
|  | ||||
|   const _PostContentHeader({ | ||||
|   const _PostActionPopup({ | ||||
|     required this.data, | ||||
|     required this.isAuthor, | ||||
|     this.isCompact = false, | ||||
|     this.isRelativeDate = true, | ||||
|     this.showMenu = true, | ||||
|     required this.onDeleted, | ||||
|     required this.onShare, | ||||
|     required this.onShareImage, | ||||
| @@ -914,124 +956,14 @@ class _PostContentHeader extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ud = context.read<UserDirectoryProvider>(); | ||||
|     final user = data.publisher.type == 0 | ||||
|         ? ud.getFromCache(data.publisher.accountId) | ||||
|         : null; | ||||
|  | ||||
|     return Row( | ||||
|       children: [ | ||||
|         GestureDetector( | ||||
|           child: data.preload?.realm == null | ||||
|               ? AccountImage( | ||||
|                   content: data.publisher.avatar, | ||||
|                   radius: isCompact ? 12 : 20, | ||||
|                   borderRadius: | ||||
|                       data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20, | ||||
|                   badge: (user?.badges.isNotEmpty ?? false) | ||||
|                       ? AccountBadge( | ||||
|                           badge: user!.badges.first, | ||||
|                           radius: 16, | ||||
|                           padding: EdgeInsets.all(2), | ||||
|                         ) | ||||
|                       : null, | ||||
|                 ) | ||||
|               : AccountImage( | ||||
|                   content: data.preload!.realm!.avatar, | ||||
|                   radius: isCompact ? 12 : 20, | ||||
|                   borderRadius: isCompact ? 4 : 8, | ||||
|                   badgeOffset: Offset(-6, -4), | ||||
|                   badge: Container( | ||||
|                     decoration: BoxDecoration( | ||||
|                       border: Border.all( | ||||
|                         color: Theme.of(context).colorScheme.surface, | ||||
|                         width: 2, | ||||
|                       ), | ||||
|                       borderRadius: BorderRadius.circular(10), | ||||
|                     ), | ||||
|                     child: AccountImage( | ||||
|                       content: data.publisher.avatar, | ||||
|                       radius: 10, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|           onTap: () { | ||||
|             showPopover( | ||||
|               backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|               context: context, | ||||
|               transition: PopoverTransition.other, | ||||
|               bodyBuilder: (context) => SizedBox( | ||||
|                 width: math.min(400, MediaQuery.of(context).size.width - 10), | ||||
|                 child: PublisherPopoverCard( | ||||
|                   data: data.publisher, | ||||
|                 ), | ||||
|               ), | ||||
|               direction: PopoverDirection.bottom, | ||||
|               arrowHeight: 5, | ||||
|               arrowWidth: 15, | ||||
|               arrowDxOffset: -190, | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|         Gap(isCompact ? 8 : 12), | ||||
|         if (isCompact) | ||||
|           Row( | ||||
|             children: [ | ||||
|               Text(data.publisher.nick).bold(), | ||||
|               const Gap(4), | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   Text('@${data.publisher.name}').fontSize(13), | ||||
|                   const Gap(4), | ||||
|                   Text( | ||||
|                     isRelativeDate | ||||
|                         ? RelativeTime(context).format( | ||||
|                             (data.publishedAt ?? data.createdAt).toLocal()) | ||||
|                         : DateFormat('y/M/d HH:mm').format( | ||||
|                             (data.publishedAt ?? data.createdAt).toLocal()), | ||||
|                   ).fontSize(13), | ||||
|                 ], | ||||
|               ).opacity(0.8), | ||||
|             ], | ||||
|           ) | ||||
|         else | ||||
|           Expanded( | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     Text(data.publisher.nick).bold(), | ||||
|                     if (data.preload?.realm != null) | ||||
|                       const Icon(Symbols.arrow_right, size: 16) | ||||
|                           .padding(horizontal: 2) | ||||
|                           .opacity(0.5), | ||||
|                     if (data.preload?.realm != null) | ||||
|                       Text(data.preload!.realm!.name), | ||||
|                   ], | ||||
|                 ), | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     Text('@${data.publisher.name}').fontSize(13), | ||||
|                     const Gap(4), | ||||
|                     Text( | ||||
|                       isRelativeDate | ||||
|                           ? RelativeTime(context).format( | ||||
|                               (data.publishedAt ?? data.createdAt).toLocal()) | ||||
|                           : DateFormat('y/M/d HH:mm').format( | ||||
|                               (data.publishedAt ?? data.createdAt).toLocal()), | ||||
|                     ).fontSize(13), | ||||
|                   ], | ||||
|                 ).opacity(0.8), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         if (showMenu) | ||||
|           PopupMenuButton( | ||||
|             icon: const Icon(Symbols.more_horiz), | ||||
|     return SizedBox( | ||||
|       height: 20, | ||||
|       child: PopupMenuButton( | ||||
|         icon: const Icon(Symbols.more_horiz, size: 20), | ||||
|         style: const ButtonStyle( | ||||
|           visualDensity: VisualDensity(horizontal: -4, vertical: -4), | ||||
|         ), | ||||
|         padding: EdgeInsets.zero, | ||||
|         itemBuilder: (BuildContext context) => <PopupMenuEntry>[ | ||||
|           if (isAuthor && onSelectAnswer != null) | ||||
|             PopupMenuItem( | ||||
| @@ -1187,8 +1119,71 @@ class _PostContentHeader extends StatelessWidget { | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostContentHeader extends StatelessWidget { | ||||
|   final SnPost data; | ||||
|   final bool isCompact; | ||||
|   final bool isRelativeDate; | ||||
|  | ||||
|   const _PostContentHeader({ | ||||
|     required this.data, | ||||
|     this.isCompact = false, | ||||
|     this.isRelativeDate = true, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (isCompact) { | ||||
|       return Row( | ||||
|         children: [ | ||||
|           Text(data.publisher.nick).bold(), | ||||
|           const Gap(4), | ||||
|           Row( | ||||
|             children: [ | ||||
|               Text( | ||||
|                 isRelativeDate | ||||
|                     ? RelativeTime(context) | ||||
|                         .format((data.publishedAt ?? data.createdAt).toLocal()) | ||||
|                     : DateFormat('y/M/d HH:mm') | ||||
|                         .format((data.publishedAt ?? data.createdAt).toLocal()), | ||||
|               ).fontSize(13), | ||||
|             ], | ||||
|           ).opacity(0.8), | ||||
|         ], | ||||
|       ); | ||||
|     } else { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Row( | ||||
|             children: [ | ||||
|               Text(data.publisher.nick).bold(), | ||||
|               if (data.preload?.realm != null) | ||||
|                 const Icon(Symbols.arrow_right, size: 16) | ||||
|                     .padding(horizontal: 2) | ||||
|                     .opacity(0.5), | ||||
|               if (data.preload?.realm != null) Text(data.preload!.realm!.name), | ||||
|             ], | ||||
|           ), | ||||
|           Row( | ||||
|             children: [ | ||||
|               Text('@${data.publisher.name}').fontSize(13), | ||||
|               const Gap(4), | ||||
|               Text( | ||||
|                 isRelativeDate | ||||
|                     ? RelativeTime(context) | ||||
|                         .format((data.publishedAt ?? data.createdAt).toLocal()) | ||||
|                     : DateFormat('y/M/d HH:mm') | ||||
|                         .format((data.publishedAt ?? data.createdAt).toLocal()), | ||||
|               ).fontSize(13), | ||||
|             ], | ||||
|           ).opacity(0.8), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -1248,14 +1243,9 @@ class _PostQuoteContent extends StatelessWidget { | ||||
|             Column( | ||||
|               children: [ | ||||
|                 _PostContentHeader( | ||||
|                   isAuthor: false, | ||||
|                   data: child, | ||||
|                   isCompact: true, | ||||
|                   isRelativeDate: isRelativeDate, | ||||
|                   showMenu: false, | ||||
|                   onShare: () {}, | ||||
|                   onShareImage: () {}, | ||||
|                   onDeleted: () {}, | ||||
|                 ).padding(bottom: 4), | ||||
|                 _PostContentBody(data: child), | ||||
|                 if (child.visibility > 0) | ||||
| @@ -1486,12 +1476,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (widget.data.metric.replyCount == 0) return const SizedBox.shrink(); | ||||
|     if (_featuredComment == null) return const SizedBox.shrink(); | ||||
|     final ua = context.read<UserProvider>(); | ||||
|  | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     return AnimateWidgetExtensions(Container( | ||||
|     return Container( | ||||
|       constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity), | ||||
|       margin: const EdgeInsets.only(top: 8), | ||||
|       width: double.infinity, | ||||
| @@ -1499,7 +1486,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | ||||
|         borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|         color: _isAnswer | ||||
|             ? Colors.green.withOpacity(0.5) | ||||
|             : Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|             : Theme.of(context).colorScheme.surfaceContainer, | ||||
|         child: InkWell( | ||||
|           borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|           onTap: () { | ||||
| @@ -1519,29 +1506,33 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | ||||
|                 crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   const Gap(2), | ||||
|                   Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, | ||||
|                       size: 20), | ||||
|                   Transform.flip( | ||||
|                     flipX: !_isAnswer, | ||||
|                     child: Icon( | ||||
|                       _isAnswer ? Symbols.task_alt : Symbols.reply, | ||||
|                       size: 20, | ||||
|                     ), | ||||
|                   ), | ||||
|                   const Gap(10), | ||||
|                   Text( | ||||
|                     _isAnswer | ||||
|                         ? 'postQuestionAnswerTitle' | ||||
|                         : 'postFeaturedComment', | ||||
|                         ? 'postQuestionAnswerTitle'.tr() | ||||
|                         : 'postComments'.plural(widget.data.metric.replyCount), | ||||
|                     style: Theme.of(context) | ||||
|                         .textTheme | ||||
|                         .titleMedium! | ||||
|                         .copyWith(fontSize: 15), | ||||
|                   ).tr(), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               const Gap(4), | ||||
|               if (_featuredComment != null) | ||||
|                 Row( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                   children: [ | ||||
|                   CircleAvatar( | ||||
|                     AccountImage( | ||||
|                       content: _featuredComment!.publisher.avatar, | ||||
|                       radius: 12, | ||||
|                     backgroundImage: UniversalImage.provider( | ||||
|                       sn.getAttachmentUrl(_featuredComment!.publisher.avatar), | ||||
|                     ), | ||||
|                     ), | ||||
|                     const Gap(8), | ||||
|                     Expanded( | ||||
| @@ -1551,12 +1542,37 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> { | ||||
|                       ), | ||||
|                     ) | ||||
|                   ], | ||||
|                 ) | ||||
|               else | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     AccountImage( | ||||
|                       content: ua.user?.avatar, | ||||
|                       radius: 12, | ||||
|                     ), | ||||
|                     const Gap(8), | ||||
|                     Expanded( | ||||
|                       child: Container( | ||||
|                         padding: | ||||
|                             EdgeInsets.symmetric(horizontal: 12, vertical: 2), | ||||
|                         decoration: BoxDecoration( | ||||
|                           color: Theme.of(context) | ||||
|                               .colorScheme | ||||
|                               .surfaceContainerHighest, | ||||
|                           borderRadius: const BorderRadius.all( | ||||
|                             Radius.circular(16), | ||||
|                           ), | ||||
|                         ), | ||||
|                         child: Text('postCommentAdd').tr(), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|             ], | ||||
|           ).padding(horizontal: 16, vertical: 8), | ||||
|         ), | ||||
|       ), | ||||
|     )).animate().fadeIn(duration: 300.ms, curve: Curves.easeInOut); | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user