💄 Bunch of optimization
This commit is contained in:
		| @@ -551,9 +551,11 @@ class _PostListWidgetState extends State<_PostListWidget> { | ||||
|                   maxWidth: 640, | ||||
|                 ); | ||||
|               case 'reader.news': | ||||
|                 return Container( | ||||
|                   constraints: BoxConstraints(maxWidth: 640), | ||||
|                   child: NewsFeedEntry(data: ele), | ||||
|                 return Center( | ||||
|                   child: Container( | ||||
|                     constraints: BoxConstraints(maxWidth: 640), | ||||
|                     child: NewsFeedEntry(data: ele), | ||||
|                   ), | ||||
|                 ); | ||||
|               default: | ||||
|                 return Container( | ||||
|   | ||||
| @@ -389,7 +389,7 @@ class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> { | ||||
|                         size: 20, | ||||
|                       ), | ||||
|                       const Gap(10), | ||||
|                       Text('serviceStatusOperational').tr(), | ||||
|                       Text('loading').tr(), | ||||
|                     ], | ||||
|                   ) | ||||
|                 : switch (_serviceStatus) { | ||||
| @@ -434,6 +434,7 @@ class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> { | ||||
|                 padding: EdgeInsets.only(top: 6), | ||||
|                 child: Wrap( | ||||
|                   spacing: 8, | ||||
|                   runSpacing: 8, | ||||
|                   children: [ | ||||
|                     for (final entry in _statuses!.entries) | ||||
|                       Tooltip( | ||||
| @@ -441,6 +442,8 @@ class _HomeDashServiceStatusState extends State<_HomeDashServiceStatus> { | ||||
|                             ? 'serviceName${kServicesName[entry.key]}'.tr() | ||||
|                             : 'unknown'.tr(), | ||||
|                         child: Chip( | ||||
|                           visualDensity: | ||||
|                               VisualDensity(horizontal: -4, vertical: -4), | ||||
|                           avatar: entry.value | ||||
|                               ? const Icon( | ||||
|                                   Symbols.circle, | ||||
| @@ -877,8 +880,10 @@ class _HomeDashRecommendationPostWidgetState | ||||
|                   ).tr(), | ||||
|                 ], | ||||
|               ), | ||||
|               Text('${_currentPage + 1}/${_posts?.length ?? 0}', | ||||
|                   style: GoogleFonts.robotoMono()) | ||||
|               Text( | ||||
|                 '${_currentPage + 1}/${_posts?.length ?? 0}', | ||||
|                 style: GoogleFonts.robotoMono(), | ||||
|               ) | ||||
|             ], | ||||
|           ).padding(horizontal: 18, top: 12, bottom: 8), | ||||
|           Expanded( | ||||
| @@ -896,6 +901,7 @@ class _HomeDashRecommendationPostWidgetState | ||||
|                     child: PostItem( | ||||
|                       data: _posts![index], | ||||
|                       showMenu: false, | ||||
|                       showFullPost: true, | ||||
|                     ).padding(bottom: 8), | ||||
|                     onTap: () { | ||||
|                       GoRouter.of(context) | ||||
|   | ||||
| @@ -63,7 +63,10 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|         queryParameters: {'take': 10, 'offset': _notifications.length}, | ||||
|       ); | ||||
|       _totalCount = resp.data['count']; | ||||
|       _notifications.addAll(resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? []); | ||||
|       _notifications.addAll(resp.data['data'] | ||||
|               ?.map((e) => SnNotification.fromJson(e)) | ||||
|               .cast<SnNotification>() ?? | ||||
|           []); | ||||
|       nty.updateTray(); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
| @@ -98,7 +101,8 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|       nty.clear(); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       context.showSnackbar('notificationMarkAllReadPrompt'.plural(resp.data['count'])); | ||||
|       context.showSnackbar( | ||||
|           'notificationMarkAllReadPrompt'.plural(resp.data['count'])); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
| @@ -122,7 +126,8 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|       _fetchNotifications(); | ||||
|  | ||||
|       if (!mounted) return; | ||||
|       context.showSnackbar('notificationMarkOneReadPrompt'.tr(args: ['#${notification.id}'])); | ||||
|       context.showSnackbar( | ||||
|           'notificationMarkOneReadPrompt'.tr(args: ['#${notification.id}'])); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
| @@ -143,7 +148,9 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|  | ||||
|     if (!ua.isAuthorized) { | ||||
|       return AppScaffold( | ||||
|         appBar: AppBar(leading: AutoAppBarLeading(), title: Text('screenNotification').tr()), | ||||
|         appBar: AppBar( | ||||
|             leading: AutoAppBarLeading(), | ||||
|             title: Text('screenNotification').tr()), | ||||
|         body: Center(child: UnauthorizedHint()), | ||||
|       ); | ||||
|     } | ||||
| @@ -153,7 +160,9 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|         leading: AutoAppBarLeading(), | ||||
|         title: Text('screenNotification').tr(), | ||||
|         actions: [ | ||||
|           IconButton(icon: const Icon(Symbols.checklist), onPressed: _isSubmitting ? null : _markAllAsRead), | ||||
|           IconButton( | ||||
|               icon: const Icon(Symbols.checklist), | ||||
|               onPressed: _isSubmitting ? null : _markAllAsRead), | ||||
|           const Gap(8), | ||||
|         ], | ||||
|       ), | ||||
| @@ -167,13 +176,17 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                 return _fetchNotifications(); | ||||
|               }, | ||||
|               child: InfiniteList( | ||||
|                 padding: EdgeInsets.only(top: 16, bottom: math.max(MediaQuery.of(context).padding.bottom, 16)), | ||||
|                 padding: EdgeInsets.only( | ||||
|                     top: 16, | ||||
|                     bottom: | ||||
|                         math.max(MediaQuery.of(context).padding.bottom, 16)), | ||||
|                 itemCount: _notifications.length, | ||||
|                 onFetchData: () { | ||||
|                   _fetchNotifications(); | ||||
|                 }, | ||||
|                 isLoading: _isBusy, | ||||
|                 hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!, | ||||
|                 hasReachedMax: _totalCount != null && | ||||
|                     _notifications.length >= _totalCount!, | ||||
|                 itemBuilder: (context, idx) { | ||||
|                   final nty = _notifications[idx]; | ||||
|                   return Row( | ||||
| @@ -186,12 +199,19 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             if (nty.readAt == null) | ||||
|                               StyledWidget(Badge(label: Text('notificationUnread').tr())).padding(bottom: 4), | ||||
|                             Text(nty.title, style: Theme.of(context).textTheme.titleMedium), | ||||
|                               StyledWidget(Badge( | ||||
|                                       label: Text('notificationUnread').tr())) | ||||
|                                   .padding(bottom: 4), | ||||
|                             Text(nty.title, | ||||
|                                 style: Theme.of(context).textTheme.titleMedium), | ||||
|                             if (nty.subtitle != null) | ||||
|                               Text(nty.subtitle!, style: Theme.of(context).textTheme.titleSmall), | ||||
|                               Text(nty.subtitle!, | ||||
|                                   style: | ||||
|                                       Theme.of(context).textTheme.titleSmall), | ||||
|                             if (nty.subtitle != null) const Gap(4), | ||||
|                             SelectionArea(child: MarkdownTextContent(content: nty.body, isAutoWarp: true)), | ||||
|                             SelectionArea( | ||||
|                                 child: MarkdownTextContent( | ||||
|                                     content: nty.body, isAutoWarp: true)), | ||||
|                             if ([ | ||||
|                                   'interactive.reply', | ||||
|                                   'interactive.feedback', | ||||
| @@ -201,31 +221,43 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                               GestureDetector( | ||||
|                                 child: Container( | ||||
|                                   decoration: BoxDecoration( | ||||
|                                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                                     border: Border.all(color: Theme.of(context).dividerColor, width: 1), | ||||
|                                     borderRadius: const BorderRadius.all( | ||||
|                                         Radius.circular(8)), | ||||
|                                     border: Border.all( | ||||
|                                         color: Theme.of(context).dividerColor, | ||||
|                                         width: 1), | ||||
|                                   ), | ||||
|                                   child: PostItem( | ||||
|                                     data: SnPost.fromJson(nty.metadata['related_post']!), | ||||
|                                     data: SnPost.fromJson( | ||||
|                                         nty.metadata['related_post']!), | ||||
|                                     showComments: false, | ||||
|                                     showReactions: false, | ||||
|                                     showMenu: false, | ||||
|                                   ), | ||||
|                                   ).padding(vertical: 4), | ||||
|                                 ), | ||||
|                                 onTap: () { | ||||
|                                   GoRouter.of(context).pushNamed( | ||||
|                                     'postDetail', | ||||
|                                     pathParameters: {'slug': nty.metadata['related_post']!['id'].toString()}, | ||||
|                                     pathParameters: { | ||||
|                                       'slug': nty | ||||
|                                           .metadata['related_post']!['id'] | ||||
|                                           .toString() | ||||
|                                     }, | ||||
|                                   ); | ||||
|                                 }, | ||||
|                               ).padding(top: 8), | ||||
|                             const Gap(8), | ||||
|                             Row( | ||||
|                               children: [ | ||||
|                                 Text(DateFormat('yy/MM/dd').format(nty.createdAt)).fontSize(12), | ||||
|                                 Text(DateFormat('yy/MM/dd') | ||||
|                                         .format(nty.createdAt)) | ||||
|                                     .fontSize(12), | ||||
|                                 const Gap(4), | ||||
|                                 Text('·', style: TextStyle(fontSize: 12)), | ||||
|                                 const Gap(4), | ||||
|                                 Text(RelativeTime(context).format(nty.createdAt)).fontSize(12), | ||||
|                                 Text(RelativeTime(context) | ||||
|                                         .format(nty.createdAt)) | ||||
|                                     .fontSize(12), | ||||
|                               ], | ||||
|                             ).opacity(0.75), | ||||
|                           ], | ||||
| @@ -235,8 +267,10 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                       IconButton( | ||||
|                         icon: const Icon(Symbols.check), | ||||
|                         padding: EdgeInsets.all(0), | ||||
|                         visualDensity: const VisualDensity(horizontal: -4, vertical: -4), | ||||
|                         onPressed: _isSubmitting ? null : () => _markOneAsRead(nty), | ||||
|                         visualDensity: | ||||
|                             const VisualDensity(horizontal: -4, vertical: -4), | ||||
|                         onPressed: | ||||
|                             _isSubmitting ? null : () => _markOneAsRead(nty), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 16); | ||||
|   | ||||
| @@ -22,7 +22,8 @@ class PostDetailScreen extends StatefulWidget { | ||||
|   final SnPost? preload; | ||||
|   final Function? onBack; | ||||
|  | ||||
|   const PostDetailScreen({super.key, required this.slug, this.preload, this.onBack}); | ||||
|   const PostDetailScreen( | ||||
|       {super.key, required this.slug, this.preload, this.onBack}); | ||||
|  | ||||
|   @override | ||||
|   State<PostDetailScreen> createState() => _PostDetailScreenState(); | ||||
| @@ -88,14 +89,16 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | ||||
|                     TextSpan( | ||||
|                       text: _data?.body['title'] ?? 'postNoun'.tr(), | ||||
|                       style: Theme.of(context).textTheme.titleLarge!.copyWith( | ||||
|                             color: Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                             color: | ||||
|                                 Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                           ), | ||||
|                     ), | ||||
|                     const TextSpan(text: '\n'), | ||||
|                     TextSpan( | ||||
|                       text: 'postDetail'.tr(), | ||||
|                       style: Theme.of(context).textTheme.bodySmall!.copyWith( | ||||
|                             color: Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                             color: | ||||
|                                 Theme.of(context).appBarTheme.foregroundColor!, | ||||
|                           ), | ||||
|                     ), | ||||
|                   ]), | ||||
| @@ -124,8 +127,11 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|             if (_data != null && _data!.type != 'video') const SliverToBoxAdapter(child: Divider(height: 1)), | ||||
|             if (_data != null && _data!.type != 'video') | ||||
|             if (_data != null) | ||||
|               SliverToBoxAdapter( | ||||
|                 child: Divider(height: 1).padding(top: 8), | ||||
|               ), | ||||
|             if (_data != null) | ||||
|               SliverToBoxAdapter( | ||||
|                 child: Container( | ||||
|                   constraints: BoxConstraints(maxWidth: maxWidth), | ||||
| @@ -141,7 +147,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | ||||
|                   ).padding(horizontal: 20, vertical: 12).center(), | ||||
|                 ), | ||||
|               ), | ||||
|             if (_data != null && ua.isAuthorized && _data!.type != 'video') | ||||
|             if (_data != null && ua.isAuthorized) | ||||
|               SliverToBoxAdapter( | ||||
|                 child: PostCommentQuickAction( | ||||
|                   parentPost: _data!, | ||||
| @@ -158,13 +164,15 @@ class _PostDetailScreenState extends State<PostDetailScreen> { | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|             if (_data != null && _data!.type != 'video') | ||||
|             if (_data != null) SliverGap(8), | ||||
|             if (_data != null) | ||||
|               PostCommentSliverList( | ||||
|                 key: _childListKey, | ||||
|                 parentPost: _data!, | ||||
|                 maxWidth: maxWidth, | ||||
|               ), | ||||
|             if (_data != null && _data!.type == 'video') SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)), | ||||
|             if (_data != null) | ||||
|               SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/post.dart'; | ||||
| import 'package:surface/types/post.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| @@ -77,7 +77,8 @@ class _PostDraftBoxState extends State<PostDraftBox> { | ||||
|                     }, | ||||
|                   ); | ||||
|                 }, | ||||
|                 separatorBuilder: (_, __) => const Gap(8), | ||||
|                 separatorBuilder: (_, __) => | ||||
|                     const Divider().padding(vertical: 2), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|   | ||||
| @@ -45,7 +45,8 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | ||||
|   } | ||||
|  | ||||
|   Future<void> _fetchPosts() async { | ||||
|     if (_searchTerm.isEmpty && _searchCategories.isEmpty && _searchTags.isEmpty) return; | ||||
|     if (_searchTerm.isEmpty && _searchCategories.isEmpty && _searchTags.isEmpty) | ||||
|       return; | ||||
|     if (_postCount != null && _posts.length >= _postCount!) return; | ||||
|  | ||||
|     setState(() => _isBusy = true); | ||||
| @@ -152,7 +153,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | ||||
|                 }, | ||||
|               ); | ||||
|             }, | ||||
|             separatorBuilder: (_, __) => const Gap(8), | ||||
|             separatorBuilder: (_, __) => const Divider().padding(vertical: 2), | ||||
|           ), | ||||
|           Positioned( | ||||
|             top: 16, | ||||
| @@ -166,7 +167,8 @@ class _PostSearchScreenState extends State<PostSearchScreen> { | ||||
|                   padding: const WidgetStatePropertyAll( | ||||
|                     EdgeInsets.symmetric(horizontal: 24), | ||||
|                   ), | ||||
|                   onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                   onTapOutside: (_) => | ||||
|                       FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                   onChanged: (value) { | ||||
|                     _searchTerm = value; | ||||
|                   }, | ||||
|   | ||||
| @@ -34,9 +34,11 @@ class PostPublisherScreen extends StatefulWidget { | ||||
|   State<PostPublisherScreen> createState() => _PostPublisherScreenState(); | ||||
| } | ||||
|  | ||||
| class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTickerProviderStateMixin { | ||||
| class _PostPublisherScreenState extends State<PostPublisherScreen> | ||||
|     with SingleTickerProviderStateMixin { | ||||
|   late final ScrollController _scrollController = ScrollController(); | ||||
|   late final TabController _tabController = TabController(length: 3, vsync: this); | ||||
|   late final TabController _tabController = | ||||
|       TabController(length: 3, vsync: this); | ||||
|  | ||||
|   SnPublisher? _publisher; | ||||
|   SnAccount? _account; | ||||
| @@ -66,7 +68,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|       _account = await ud.getAccount(_publisher?.accountId); | ||||
|       _accountRelationship = await rel.getRelationship(_account!.id); | ||||
|       if (_publisher?.realmId != null && _publisher!.realmId != 0) { | ||||
|         final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}'); | ||||
|         final resp = | ||||
|             await sn.client.get('/cgi/id/realms/${_publisher!.realmId}'); | ||||
|         _realm = SnRealm.fromJson(resp.data); | ||||
|       } | ||||
|     } catch (_) { | ||||
| @@ -133,12 +136,14 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|   double _appBarBlur = 0.0; | ||||
|  | ||||
|   late final _appBarWidth = MediaQuery.of(context).size.width; | ||||
|   late final _appBarHeight = (_appBarWidth * kBannerAspectRatio).roundToDouble(); | ||||
|   late final _appBarHeight = | ||||
|       (_appBarWidth * kBannerAspectRatio).roundToDouble(); | ||||
|  | ||||
|   void _updateAppBarBlur() { | ||||
|     if (_scrollController.offset > _appBarHeight) return; | ||||
|     setState(() { | ||||
|       _appBarBlur = (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0); | ||||
|       _appBarBlur = | ||||
|           (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @@ -193,7 +198,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|         'related': _account!.name, | ||||
|       }); | ||||
|       if (!mounted) return; | ||||
|       context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}'])); | ||||
|       context.showSnackbar( | ||||
|           'userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}'])); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
| @@ -209,9 +215,11 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|  | ||||
|     try { | ||||
|       final rel = context.read<SnRelationshipProvider>(); | ||||
|       await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {}); | ||||
|       await rel.updateRelationship( | ||||
|           _account!.id, 1, _accountRelationship?.permNodes ?? {}); | ||||
|       if (!mounted) return; | ||||
|       context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}'])); | ||||
|       context.showSnackbar( | ||||
|           'userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}'])); | ||||
|     } catch (err) { | ||||
|       if (!mounted) return; | ||||
|       context.showErrorDialog(err); | ||||
| @@ -299,7 +307,10 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                               text: TextSpan(children: [ | ||||
|                                 TextSpan( | ||||
|                                   text: _publisher!.nick, | ||||
|                                   style: Theme.of(context).textTheme.titleLarge!.copyWith( | ||||
|                                   style: Theme.of(context) | ||||
|                                       .textTheme | ||||
|                                       .titleLarge! | ||||
|                                       .copyWith( | ||||
|                                         color: Colors.white, | ||||
|                                         shadows: labelShadows, | ||||
|                                       ), | ||||
| @@ -307,7 +318,10 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                 const TextSpan(text: '\n'), | ||||
|                                 TextSpan( | ||||
|                                   text: '@${_publisher!.name}', | ||||
|                                   style: Theme.of(context).textTheme.bodySmall!.copyWith( | ||||
|                                   style: Theme.of(context) | ||||
|                                       .textTheme | ||||
|                                       .bodySmall! | ||||
|                                       .copyWith( | ||||
|                                         color: Colors.white, | ||||
|                                         shadows: labelShadows, | ||||
|                                       ), | ||||
| @@ -330,13 +344,16 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                   ) | ||||
|                                 else | ||||
|                                   Container( | ||||
|                                     color: Theme.of(context).colorScheme.surfaceContainer, | ||||
|                                     color: Theme.of(context) | ||||
|                                         .colorScheme | ||||
|                                         .surfaceContainer, | ||||
|                                   ), | ||||
|                                 Positioned( | ||||
|                                   top: 0, | ||||
|                                   left: 0, | ||||
|                                   right: 0, | ||||
|                                   height: 56 + MediaQuery.of(context).padding.top, | ||||
|                                   height: | ||||
|                                       56 + MediaQuery.of(context).padding.top, | ||||
|                                   child: ClipRect( | ||||
|                                     child: BackdropFilter( | ||||
|                                       filter: ImageFilter.blur( | ||||
| @@ -345,7 +362,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                       ), | ||||
|                                       child: Container( | ||||
|                                         color: Colors.black.withOpacity( | ||||
|                                           clampDouble(_appBarBlur * 0.1, 0, 0.5), | ||||
|                                           clampDouble( | ||||
|                                               _appBarBlur * 0.1, 0, 0.5), | ||||
|                                         ), | ||||
|                                       ), | ||||
|                                     ), | ||||
| @@ -372,11 +390,14 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                 const Gap(16), | ||||
|                                 Expanded( | ||||
|                                   child: Column( | ||||
|                                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                                     crossAxisAlignment: | ||||
|                                         CrossAxisAlignment.start, | ||||
|                                     children: [ | ||||
|                                       Text( | ||||
|                                         _publisher!.nick, | ||||
|                                         style: Theme.of(context).textTheme.titleMedium, | ||||
|                                         style: Theme.of(context) | ||||
|                                             .textTheme | ||||
|                                             .titleMedium, | ||||
|                                       ).bold(), | ||||
|                                       Text('@${_publisher!.name}').fontSize(13), | ||||
|                                     ], | ||||
| @@ -387,7 +408,9 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                     style: ButtonStyle( | ||||
|                                       elevation: WidgetStatePropertyAll(0), | ||||
|                                     ), | ||||
|                                     onPressed: _isSubscribing ? null : _toggleSubscription, | ||||
|                                     onPressed: _isSubscribing | ||||
|                                         ? null | ||||
|                                         : _toggleSubscription, | ||||
|                                     label: Text('subscribe').tr(), | ||||
|                                     icon: const Icon(Symbols.add), | ||||
|                                   ) | ||||
| @@ -396,14 +419,17 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                     style: ButtonStyle( | ||||
|                                       elevation: WidgetStatePropertyAll(0), | ||||
|                                     ), | ||||
|                                     onPressed: _isSubscribing ? null : _toggleSubscription, | ||||
|                                     onPressed: _isSubscribing | ||||
|                                         ? null | ||||
|                                         : _toggleSubscription, | ||||
|                                     label: Text('unsubscribe').tr(), | ||||
|                                     icon: const Icon(Symbols.remove), | ||||
|                                   ), | ||||
|                                 PopupMenuButton( | ||||
|                                   padding: EdgeInsets.zero, | ||||
|                                   style: ButtonStyle( | ||||
|                                     visualDensity: VisualDensity(horizontal: -4, vertical: -4), | ||||
|                                     visualDensity: VisualDensity( | ||||
|                                         horizontal: -4, vertical: -4), | ||||
|                                   ), | ||||
|                                   itemBuilder: (BuildContext context) => [ | ||||
|                                     PopupMenuItem( | ||||
| @@ -443,7 +469,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                               ], | ||||
|                             ), | ||||
|                             const Gap(12), | ||||
|                             Text(_publisher!.description).padding(horizontal: 8), | ||||
|                             Text(_publisher!.description) | ||||
|                                 .padding(horizontal: 8), | ||||
|                             const Gap(12), | ||||
|                             Column( | ||||
|                               children: [ | ||||
| @@ -451,8 +478,10 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                   children: [ | ||||
|                                     const Icon(Symbols.calendar_add_on), | ||||
|                                     const Gap(8), | ||||
|                                     Text('publisherJoinedAt') | ||||
|                                         .tr(args: [DateFormat('y/M/d').format(_publisher!.createdAt)]), | ||||
|                                     Text('publisherJoinedAt').tr(args: [ | ||||
|                                       DateFormat('y/M/d') | ||||
|                                           .format(_publisher!.createdAt) | ||||
|                                     ]), | ||||
|                                   ], | ||||
|                                 ), | ||||
|                                 Row( | ||||
| @@ -460,7 +489,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                     const Icon(Symbols.trending_up), | ||||
|                                     const Gap(8), | ||||
|                                     Text('publisherSocialPointTotal').plural( | ||||
|                                       _publisher!.totalUpvote - _publisher!.totalDownvote, | ||||
|                                       _publisher!.totalUpvote - | ||||
|                                           _publisher!.totalDownvote, | ||||
|                                     ), | ||||
|                                   ], | ||||
|                                 ), | ||||
| @@ -470,18 +500,22 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                       const Icon(Symbols.group_work), | ||||
|                                       const Gap(8), | ||||
|                                       InkWell( | ||||
|                                         child: Text('publisherAffiliatedBy').tr(args: [ | ||||
|                                         child: Text('publisherAffiliatedBy') | ||||
|                                             .tr(args: [ | ||||
|                                           '@${_realm?.alias ?? 'unknown'}', | ||||
|                                         ]), | ||||
|                                         onTap: () { | ||||
|                                           GoRouter.of(context).pushNamed( | ||||
|                                             'realmDetail', | ||||
|                                             pathParameters: {'alias': _realm!.alias}, | ||||
|                                             pathParameters: { | ||||
|                                               'alias': _realm!.alias | ||||
|                                             }, | ||||
|                                           ); | ||||
|                                         }, | ||||
|                                       ), | ||||
|                                       const Gap(8), | ||||
|                                       AccountImage(content: _realm?.avatar, radius: 8), | ||||
|                                       AccountImage( | ||||
|                                           content: _realm?.avatar, radius: 8), | ||||
|                                     ], | ||||
|                                   ), | ||||
|                                 Row( | ||||
| @@ -502,7 +536,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi | ||||
|                                       }, | ||||
|                                     ), | ||||
|                                     const Gap(8), | ||||
|                                     AccountImage(content: _account?.avatar, radius: 8), | ||||
|                                     AccountImage( | ||||
|                                         content: _account?.avatar, radius: 8), | ||||
|                                   ], | ||||
|                                 ), | ||||
|                               ], | ||||
| @@ -606,7 +641,7 @@ class _PublisherPostList extends StatelessWidget { | ||||
|           onDeleted: onDeleted, | ||||
|         ); | ||||
|       }, | ||||
|       separatorBuilder: (_, __) => const Gap(8), | ||||
|       separatorBuilder: (_, __) => const Divider().padding(vertical: 2), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -19,89 +19,87 @@ class NewsFeedEntry extends StatelessWidget { | ||||
|         .cast<SnNewsArticle>() | ||||
|         .toList(); | ||||
|  | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Row( | ||||
|             children: [ | ||||
|               const Icon(Symbols.newspaper), | ||||
|               const Gap(8), | ||||
|               Text( | ||||
|                 'newsToday', | ||||
|                 style: Theme.of(context).textTheme.titleLarge, | ||||
|               ).tr() | ||||
|             ], | ||||
|           ).padding(horizontal: 18, top: 12, bottom: 8), | ||||
|           Container( | ||||
|             margin: const EdgeInsets.only(bottom: 12), | ||||
|             height: 150, | ||||
|             child: ListView.separated( | ||||
|               scrollDirection: Axis.horizontal, | ||||
|               itemCount: news.length, | ||||
|               padding: const EdgeInsets.symmetric(horizontal: 12), | ||||
|               itemBuilder: (context, idx) { | ||||
|                 return Container( | ||||
|                   width: 360, | ||||
|                   decoration: BoxDecoration( | ||||
|                     border: Border.all( | ||||
|                       color: Theme.of(context).dividerColor, | ||||
|                       width: 1, | ||||
|                     ), | ||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Row( | ||||
|           children: [ | ||||
|             const Icon(Symbols.newspaper), | ||||
|             const Gap(8), | ||||
|             Text( | ||||
|               'newsToday', | ||||
|               style: Theme.of(context).textTheme.titleLarge, | ||||
|             ).tr() | ||||
|           ], | ||||
|         ).padding(horizontal: 18, top: 12, bottom: 8), | ||||
|         Container( | ||||
|           margin: const EdgeInsets.only(bottom: 12), | ||||
|           height: 150, | ||||
|           child: ListView.separated( | ||||
|             scrollDirection: Axis.horizontal, | ||||
|             itemCount: news.length, | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 12), | ||||
|             itemBuilder: (context, idx) { | ||||
|               return Container( | ||||
|                 width: 360, | ||||
|                 decoration: BoxDecoration( | ||||
|                   border: Border.all( | ||||
|                     color: Theme.of(context).dividerColor, | ||||
|                     width: 1, | ||||
|                   ), | ||||
|                   child: Material( | ||||
|                     elevation: 0, | ||||
|                     color: Theme.of(context).colorScheme.surface, | ||||
|                   borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                 ), | ||||
|                 child: Material( | ||||
|                   elevation: 0, | ||||
|                   color: Theme.of(context).colorScheme.surface, | ||||
|                   borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                   child: InkWell( | ||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                     child: InkWell( | ||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                       child: Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             news[idx].title, | ||||
|                             maxLines: 2, | ||||
|                             style: Theme.of(context).textTheme.titleMedium, | ||||
|                           ).padding(horizontal: 16, top: 12, bottom: 4), | ||||
|                           Text( | ||||
|                             news[idx].description, | ||||
|                             maxLines: 2, | ||||
|                             style: Theme.of(context).textTheme.bodyMedium, | ||||
|                           ).padding(horizontal: 16, vertical: 4), | ||||
|                           const Gap(4), | ||||
|                           Row( | ||||
|                             children: [ | ||||
|                               Text( | ||||
|                                 DateFormat('y/M/d HH:mm') | ||||
|                                     .format(news[idx].createdAt.toLocal()), | ||||
|                                 style: Theme.of(context).textTheme.bodySmall, | ||||
|                               ), | ||||
|                               const Gap(4), | ||||
|                               Text( | ||||
|                                 RelativeTime(context) | ||||
|                                     .format(news[idx].createdAt.toLocal()), | ||||
|                                 style: Theme.of(context).textTheme.bodySmall, | ||||
|                               ), | ||||
|                             ], | ||||
|                           ).opacity(0.8).padding(horizontal: 16), | ||||
|                         ], | ||||
|                       ), | ||||
|                       onTap: () { | ||||
|                         GoRouter.of(context).pushNamed( | ||||
|                           'newsDetail', | ||||
|                           pathParameters: {'hash': news[idx].hash}, | ||||
|                         ); | ||||
|                       }, | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         Text( | ||||
|                           news[idx].title, | ||||
|                           maxLines: 2, | ||||
|                           style: Theme.of(context).textTheme.titleMedium, | ||||
|                         ).padding(horizontal: 16, top: 12, bottom: 4), | ||||
|                         Text( | ||||
|                           news[idx].description, | ||||
|                           maxLines: 2, | ||||
|                           style: Theme.of(context).textTheme.bodyMedium, | ||||
|                         ).padding(horizontal: 16, vertical: 4), | ||||
|                         const Gap(4), | ||||
|                         Row( | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               DateFormat('y/M/d HH:mm') | ||||
|                                   .format(news[idx].createdAt.toLocal()), | ||||
|                               style: Theme.of(context).textTheme.bodySmall, | ||||
|                             ), | ||||
|                             const Gap(4), | ||||
|                             Text( | ||||
|                               RelativeTime(context) | ||||
|                                   .format(news[idx].createdAt.toLocal()), | ||||
|                               style: Theme.of(context).textTheme.bodySmall, | ||||
|                             ), | ||||
|                           ], | ||||
|                         ).opacity(0.8).padding(horizontal: 16), | ||||
|                       ], | ||||
|                     ), | ||||
|                     onTap: () { | ||||
|                       GoRouter.of(context).pushNamed( | ||||
|                         'newsDetail', | ||||
|                         pathParameters: {'hash': news[idx].hash}, | ||||
|                       ); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|               separatorBuilder: (_, __) => const Gap(12), | ||||
|             ), | ||||
|                 ), | ||||
|               ); | ||||
|             }, | ||||
|             separatorBuilder: (_, __) => const Gap(12), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -12,16 +12,14 @@ class FeedUnknownEntry extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           const Icon(Symbols.help, size: 36), | ||||
|           const Gap(4), | ||||
|           Text('feedUnknownItem').tr(), | ||||
|           Text(data.type, style: GoogleFonts.robotoMono()), | ||||
|         ], | ||||
|       ).padding(horizontal: 12, vertical: 8), | ||||
|     ); | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         const Icon(Symbols.help, size: 36), | ||||
|         const Gap(4), | ||||
|         Text('feedUnknownItem').tr(), | ||||
|         Text(data.type, style: GoogleFonts.robotoMono()), | ||||
|       ], | ||||
|     ).padding(horizontal: 12, vertical: 8); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -23,57 +23,54 @@ class FediversePostWidget extends StatelessWidget { | ||||
|     return Center( | ||||
|       child: Container( | ||||
|         constraints: BoxConstraints(maxWidth: maxWidth), | ||||
|         child: Card( | ||||
|           margin: EdgeInsets.zero, | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   AccountImage( | ||||
|                     content: data.user.avatar, | ||||
|                     radius: 20, | ||||
|                   ), | ||||
|                   const Gap(12), | ||||
|                   Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         data.user.nick.isNotEmpty | ||||
|                             ? data.user.nick | ||||
|                             : '@${data.user.name}', | ||||
|                         maxLines: 1, | ||||
|                       ).bold(), | ||||
|                       Row( | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             data.user.identifier.contains('@') | ||||
|                                 ? data.user.identifier | ||||
|                                 : '${data.user.identifier}@${data.user.origin}', | ||||
|                             maxLines: 1, | ||||
|                           ).fontSize(13), | ||||
|                           const Gap(4), | ||||
|                           Text( | ||||
|                             RelativeTime(context) | ||||
|                                 .format(data.createdAt.toLocal()), | ||||
|                           ).fontSize(13), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ], | ||||
|               ).padding(horizontal: 12, vertical: 8), | ||||
|               MarkdownTextContent( | ||||
|                 isAutoWarp: true, | ||||
|                 content: html2md.convert(data.content), | ||||
|               ).padding(horizontal: 16, bottom: 6), | ||||
|               if (data.images.isNotEmpty) | ||||
|                 _FediversePostImageList( | ||||
|                   data: data, | ||||
|                   maxWidth: maxWidth, | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Row( | ||||
|               children: [ | ||||
|                 AccountImage( | ||||
|                   content: data.user.avatar, | ||||
|                   radius: 20, | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|                 const Gap(12), | ||||
|                 Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Text( | ||||
|                       data.user.nick.isNotEmpty | ||||
|                           ? data.user.nick | ||||
|                           : '@${data.user.name}', | ||||
|                       maxLines: 1, | ||||
|                     ).bold(), | ||||
|                     Row( | ||||
|                       children: [ | ||||
|                         Text( | ||||
|                           data.user.identifier.contains('@') | ||||
|                               ? data.user.identifier | ||||
|                               : '${data.user.identifier}@${data.user.origin}', | ||||
|                           maxLines: 1, | ||||
|                         ).fontSize(13), | ||||
|                         const Gap(4), | ||||
|                         Text( | ||||
|                           RelativeTime(context) | ||||
|                               .format(data.createdAt.toLocal()), | ||||
|                         ).fontSize(13), | ||||
|                       ], | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ], | ||||
|             ).padding(horizontal: 12, vertical: 8), | ||||
|             MarkdownTextContent( | ||||
|               isAutoWarp: true, | ||||
|               content: html2md.convert(data.content), | ||||
|             ).padding(horizontal: 16, bottom: 6), | ||||
|             if (data.images.isNotEmpty) | ||||
|               _FediversePostImageList( | ||||
|                 data: data, | ||||
|                 maxWidth: maxWidth, | ||||
|               ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
| @@ -166,6 +166,14 @@ class _PostItemState extends State<PostItem> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void didUpdateWidget(covariant PostItem oldWidget) { | ||||
|     _displayText = widget.data.body['content'] ?? ''; | ||||
|     _displayTitle = widget.data.body['title'] ?? ''; | ||||
|     _displayDescription = widget.data.body['description'] ?? ''; | ||||
|     super.didUpdateWidget(oldWidget); | ||||
|   } | ||||
|  | ||||
|   Future<void> _translateText() async { | ||||
|     final ta = context.read<SnTranslator>(); | ||||
|     setState(() => _isTranslating = true); | ||||
| @@ -284,6 +292,247 @@ class _PostItemState extends State<PostItem> { | ||||
|       attachmentSize -= 80; | ||||
|     } | ||||
|  | ||||
|     if (widget.showFullPost) { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|         children: [ | ||||
|           Container( | ||||
|             constraints: | ||||
|                 BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity), | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Expanded( | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Row( | ||||
|                         children: [ | ||||
|                           if (widget.showAvatar) | ||||
|                             _PostAvatar( | ||||
|                               data: widget.data, | ||||
|                               isCompact: false, | ||||
|                             ), | ||||
|                           if (widget.showAvatar) const Gap(12), | ||||
|                           Expanded( | ||||
|                             child: _PostContentHeader( | ||||
|                               isRelativeDate: !widget.showFullPost, | ||||
|                               isCompact: false, | ||||
|                               data: widget.data, | ||||
|                             ), | ||||
|                           ), | ||||
|                           _PostActionPopup( | ||||
|                             data: widget.data, | ||||
|                             isAuthor: isAuthor, | ||||
|                             onShare: () => _doShare(context), | ||||
|                             onShareImage: () => _doShareViaPicture(context), | ||||
|                             onSelectAnswer: widget.onSelectAnswer, | ||||
|                             onDeleted: () { | ||||
|                               widget.onDeleted?.call(); | ||||
|                             }, | ||||
|                             onTranslate: () { | ||||
|                               _translateText(); | ||||
|                             }, | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                       const Gap(8), | ||||
|                       if (widget.data.preload?.thumbnail != null) | ||||
|                         Container( | ||||
|                           margin: const EdgeInsets.only(bottom: 8), | ||||
|                           decoration: BoxDecoration( | ||||
|                             borderRadius: const BorderRadius.all( | ||||
|                               Radius.circular(8), | ||||
|                             ), | ||||
|                             border: Border.all( | ||||
|                               color: Theme.of(context).dividerColor, | ||||
|                               width: 1, | ||||
|                             ), | ||||
|                           ), | ||||
|                           child: AspectRatio( | ||||
|                             aspectRatio: 16 / 9, | ||||
|                             child: ClipRRect( | ||||
|                               borderRadius: const BorderRadius.all( | ||||
|                                 Radius.circular(8), | ||||
|                               ), | ||||
|                               child: AutoResizeUniversalImage( | ||||
|                                 sn.getAttachmentUrl( | ||||
|                                   widget.data.preload!.thumbnail!.rid, | ||||
|                                 ), | ||||
|                                 fit: BoxFit.cover, | ||||
|                               ), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ), | ||||
|                       if (widget.data.preload?.video != null) | ||||
|                         _PostVideoPlayer(data: widget.data).padding(bottom: 8), | ||||
|                       if (widget.data.type == 'question') | ||||
|                         _PostQuestionHint(data: widget.data).padding(bottom: 8), | ||||
|                       if (_displayDescription.isNotEmpty || | ||||
|                           _displayTitle.isNotEmpty) | ||||
|                         _PostHeadline( | ||||
|                           title: _displayTitle, | ||||
|                           description: _displayDescription, | ||||
|                           data: widget.data, | ||||
|                           isEnlarge: widget.data.type == 'article' && | ||||
|                               widget.showFullPost, | ||||
|                         ).padding(bottom: 8), | ||||
|                       if (widget.data.type == 'article' && !widget.showFullPost) | ||||
|                         Text('postArticle') | ||||
|                             .tr() | ||||
|                             .fontSize(13) | ||||
|                             .opacity(0.75) | ||||
|                             .padding(bottom: 8), | ||||
|                       if ((_displayText.isNotEmpty) && | ||||
|                           (widget.showFullPost || | ||||
|                               widget.data.type != 'article')) | ||||
|                         _PostContentBody( | ||||
|                           text: _displayText, | ||||
|                           data: widget.data, | ||||
|                           isSelectable: widget.showFullPost, | ||||
|                           isEnlarge: widget.data.type == 'article' && | ||||
|                               widget.showFullPost, | ||||
|                         ).padding(bottom: 6), | ||||
|                       if (widget.data.visibility > 0) | ||||
|                         _PostVisibilityHint(data: widget.data).padding( | ||||
|                           vertical: 4, | ||||
|                         ), | ||||
|                       if (widget.data.body['content_truncated'] == true) | ||||
|                         _PostTruncatedHint(data: widget.data).padding( | ||||
|                           vertical: 4, | ||||
|                         ), | ||||
|                       if (widget.data.tags.isNotEmpty) | ||||
|                         _PostTagsList(data: widget.data) | ||||
|                             .padding(top: 4, bottom: 6), | ||||
|                       Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                         spacing: 4, | ||||
|                         children: [ | ||||
|                           if (widget.showViews) | ||||
|                             Row( | ||||
|                               children: [ | ||||
|                                 Icon(Symbols.play_circle, size: 20), | ||||
|                                 const Gap(4), | ||||
|                                 Text('postViews') | ||||
|                                     .plural(widget.data.totalViews), | ||||
|                               ], | ||||
|                             ).opacity(0.75), | ||||
|                           if (_isTranslating) | ||||
|                             AnimateWidgetExtensions(Row( | ||||
|                               children: [ | ||||
|                                 Icon(Symbols.translate, size: 20), | ||||
|                                 const Gap(4), | ||||
|                                 Text('translating').tr(), | ||||
|                               ], | ||||
|                             )) | ||||
|                                 .animate(onPlay: (e) => e.repeat()) | ||||
|                                 .fadeIn(duration: 500.ms, curve: Curves.easeOut) | ||||
|                                 .then() | ||||
|                                 .fadeOut( | ||||
|                                   duration: 500.ms, | ||||
|                                   delay: 1000.ms, | ||||
|                                   curve: Curves.easeIn, | ||||
|                                 ), | ||||
|                           if (_isTranslated) | ||||
|                             InkWell( | ||||
|                               child: Row( | ||||
|                                 children: [ | ||||
|                                   Icon(Symbols.translate, size: 20), | ||||
|                                   const Gap(4), | ||||
|                                   Text('translated').tr(), | ||||
|                                 ], | ||||
|                               ).opacity(0.75), | ||||
|                               onTap: () { | ||||
|                                 setState(() { | ||||
|                                   _displayText = | ||||
|                                       widget.data.body['content'] ?? ''; | ||||
|                                   _displayTitle = | ||||
|                                       widget.data.body['title'] ?? ''; | ||||
|                                   _displayDescription = | ||||
|                                       widget.data.body['description'] ?? ''; | ||||
|                                   _isTranslated = false; | ||||
|                                 }); | ||||
|                               }, | ||||
|                             ), | ||||
|                           if (widget.data.repostTo != null) | ||||
|                             _PostQuoteContent(child: widget.data.repostTo!) | ||||
|                                 .padding( | ||||
|                               top: 4, | ||||
|                               bottom: widget.data.preload?.attachments | ||||
|                                           ?.isNotEmpty ?? | ||||
|                                       false | ||||
|                                   ? 12 | ||||
|                                   : 0, | ||||
|                             ), | ||||
|                         ], | ||||
|                       ).padding( | ||||
|                         bottom: | ||||
|                             widget.showViews || _isTranslated || _isTranslating | ||||
|                                 ? 8 | ||||
|                                 : 0, | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ).padding(horizontal: 12, top: 8), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|           if (displayableAttachments?.isNotEmpty ?? false) | ||||
|             AttachmentList( | ||||
|               data: displayableAttachments!, | ||||
|               bordered: true, | ||||
|               maxHeight: widget.showFullPost ? null : 480, | ||||
|               minWidth: attachmentSize, | ||||
|               maxWidth: attachmentSize, | ||||
|               fit: widget.showFullPost ? BoxFit.cover : BoxFit.contain, | ||||
|               padding: EdgeInsets.only(left: 12, right: 12), | ||||
|             ), | ||||
|           if (widget.data.preload?.poll != null) | ||||
|             StyledWidget(Container( | ||||
|               constraints: | ||||
|                   BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity), | ||||
|               child: PostPoll(poll: widget.data.preload!.poll!), | ||||
|             )) | ||||
|                 .padding( | ||||
|                   left: 12, | ||||
|                   right: 12, | ||||
|                   top: 12, | ||||
|                   bottom: 4, | ||||
|                 ) | ||||
|                 .center(), | ||||
|           if (widget.data.body['content'] != null && | ||||
|               (cfg.prefs.getBool(kAppExpandPostLink) ?? true)) | ||||
|             LinkPreviewWidget( | ||||
|               text: widget.data.body['content'], | ||||
|             ).padding(left: 12, right: 4), | ||||
|           if (widget.showExpandableComments) | ||||
|             _PostCommentIntent( | ||||
|               data: widget.data, | ||||
|               showAvatar: widget.showAvatar, | ||||
|             ).padding(left: 12, right: 12) | ||||
|           else | ||||
|             _PostFeaturedComment(data: widget.data, maxWidth: widget.maxWidth) | ||||
|                 .padding(left: 12, right: 12), | ||||
|           if (widget.showReactions) | ||||
|             Center( | ||||
|               child: Container( | ||||
|                 constraints: BoxConstraints( | ||||
|                   maxWidth: (widget.maxWidth ?? double.infinity) + 24, | ||||
|                 ), | ||||
|                 child: Padding( | ||||
|                   padding: const EdgeInsets.only(top: 4), | ||||
|                   child: _PostReactionList( | ||||
|                     data: widget.data, | ||||
|                     padding: EdgeInsets.only(left: 12, right: 12), | ||||
|                     onChanged: _onChanged, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|       children: [ | ||||
| @@ -389,15 +638,6 @@ class _PostItemState extends State<PostItem> { | ||||
|                             isEnlarge: widget.data.type == 'article' && | ||||
|                                 widget.showFullPost, | ||||
|                           ).padding(bottom: 6), | ||||
|                         if (widget.data.repostTo != null) | ||||
|                           _PostQuoteContent(child: widget.data.repostTo!) | ||||
|                               .padding( | ||||
|                             bottom: | ||||
|                                 widget.data.preload?.attachments?.isNotEmpty ?? | ||||
|                                         false | ||||
|                                     ? 12 | ||||
|                                     : 0, | ||||
|                           ), | ||||
|                         if (widget.data.visibility > 0) | ||||
|                           _PostVisibilityHint(data: widget.data).padding( | ||||
|                             vertical: 4, | ||||
| @@ -462,12 +702,25 @@ class _PostItemState extends State<PostItem> { | ||||
|                               ), | ||||
|                           ], | ||||
|                         ).padding( | ||||
|                           bottom: widget.showViews || | ||||
|                                   _isTranslated || | ||||
|                                   _isTranslating | ||||
|                           bottom: (widget.showViews || | ||||
|                                       _isTranslated || | ||||
|                                       _isTranslating) && | ||||
|                                   (widget.data.repostTo != null || | ||||
|                                       (widget.data.preload?.attachments | ||||
|                                               ?.isNotEmpty ?? | ||||
|                                           false)) | ||||
|                               ? 8 | ||||
|                               : 0, | ||||
|                         ), | ||||
|                         if (widget.data.repostTo != null) | ||||
|                           _PostQuoteContent(child: widget.data.repostTo!) | ||||
|                               .padding( | ||||
|                             bottom: | ||||
|                                 (widget.data.preload?.attachments?.isNotEmpty ?? | ||||
|                                         false) | ||||
|                                     ? 8 | ||||
|                                     : 0, | ||||
|                           ), | ||||
|                       ], | ||||
|                     ), | ||||
|                   ) | ||||
| @@ -504,7 +757,7 @@ class _PostItemState extends State<PostItem> { | ||||
|             data: widget.data, | ||||
|             showAvatar: widget.showAvatar, | ||||
|           ).padding(left: widget.showAvatar ? 60 : 12, right: 12) | ||||
|         else | ||||
|         else if (widget.showComments) | ||||
|           _PostFeaturedComment(data: widget.data, maxWidth: widget.maxWidth) | ||||
|               .padding(left: widget.showAvatar ? 60 : 12, right: 12), | ||||
|         if (widget.showReactions) | ||||
| @@ -1403,10 +1656,19 @@ class _PostQuoteContent extends StatelessWidget { | ||||
|           children: [ | ||||
|             Column( | ||||
|               children: [ | ||||
|                 _PostContentHeader( | ||||
|                   data: child, | ||||
|                   isCompact: true, | ||||
|                   isRelativeDate: isRelativeDate, | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     _PostAvatar( | ||||
|                       data: child, | ||||
|                       isCompact: true, | ||||
|                     ), | ||||
|                     const Gap(8), | ||||
|                     _PostContentHeader( | ||||
|                       data: child, | ||||
|                       isCompact: true, | ||||
|                       isRelativeDate: isRelativeDate, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ).padding(bottom: 4), | ||||
|                 _PostContentBody( | ||||
|                   data: child, | ||||
| @@ -1637,6 +1899,7 @@ class _PostCommentIntentState extends State<_PostCommentIntent> { | ||||
|       children: [ | ||||
|         if (_comments.isNotEmpty) | ||||
|           Card( | ||||
|             elevation: 4, | ||||
|             margin: EdgeInsets.zero, | ||||
|             child: Column( | ||||
|               spacing: 8, | ||||
| @@ -1652,7 +1915,7 @@ class _PostCommentIntentState extends State<_PostCommentIntent> { | ||||
|                   ).padding(vertical: 8, left: 6), | ||||
|               ], | ||||
|             ), | ||||
|           ).padding(bottom: 8), | ||||
|           ).padding(vertical: 8), | ||||
|         Row( | ||||
|           children: [ | ||||
|             Transform.flip( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user