diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 1c3ea2a..ca71d31 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -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( diff --git a/lib/screens/home.dart b/lib/screens/home.dart index d6ca87e..0997e10 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -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) diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index 4587fab..c48d90f 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -63,7 +63,10 @@ class _NotificationScreenState extends State { queryParameters: {'take': 10, 'offset': _notifications.length}, ); _totalCount = resp.data['count']; - _notifications.addAll(resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast() ?? []); + _notifications.addAll(resp.data['data'] + ?.map((e) => SnNotification.fromJson(e)) + .cast() ?? + []); nty.updateTray(); } catch (err) { if (!mounted) return; @@ -98,7 +101,8 @@ class _NotificationScreenState extends State { 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 { _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 { 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 { 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 { 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 { 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 { 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 { 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); diff --git a/lib/screens/post/post_detail.dart b/lib/screens/post/post_detail.dart index 04686f1..fa2d49f 100644 --- a/lib/screens/post/post_detail.dart +++ b/lib/screens/post/post_detail.dart @@ -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 createState() => _PostDetailScreenState(); @@ -88,14 +89,16 @@ class _PostDetailScreenState extends State { 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 { }, ), ), - 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 { ).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 { }, ), ), - 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)), ], ), ), diff --git a/lib/screens/post/post_draft.dart b/lib/screens/post/post_draft.dart index 0e20c7f..c1db4ac 100644 --- a/lib/screens/post/post_draft.dart +++ b/lib/screens/post/post_draft.dart @@ -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 { }, ); }, - separatorBuilder: (_, __) => const Gap(8), + separatorBuilder: (_, __) => + const Divider().padding(vertical: 2), ), ), ), diff --git a/lib/screens/post/post_search.dart b/lib/screens/post/post_search.dart index 1daad54..ca83b07 100644 --- a/lib/screens/post/post_search.dart +++ b/lib/screens/post/post_search.dart @@ -45,7 +45,8 @@ class _PostSearchScreenState extends State { } Future _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 { }, ); }, - separatorBuilder: (_, __) => const Gap(8), + separatorBuilder: (_, __) => const Divider().padding(vertical: 2), ), Positioned( top: 16, @@ -166,7 +167,8 @@ class _PostSearchScreenState extends State { padding: const WidgetStatePropertyAll( EdgeInsets.symmetric(horizontal: 24), ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), onChanged: (value) { _searchTerm = value; }, diff --git a/lib/screens/post/publisher_page.dart b/lib/screens/post/publisher_page.dart index 67943a4..f074e47 100644 --- a/lib/screens/post/publisher_page.dart +++ b/lib/screens/post/publisher_page.dart @@ -34,9 +34,11 @@ class PostPublisherScreen extends StatefulWidget { State createState() => _PostPublisherScreenState(); } -class _PostPublisherScreenState extends State with SingleTickerProviderStateMixin { +class _PostPublisherScreenState extends State + 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 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 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 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 with SingleTi try { final rel = context.read(); - 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 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 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 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 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 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 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 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 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 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 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 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 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), ); } } diff --git a/lib/widgets/feed/feed_news.dart b/lib/widgets/feed/feed_news.dart index b699474..08acd27 100644 --- a/lib/widgets/feed/feed_news.dart +++ b/lib/widgets/feed/feed_news.dart @@ -19,89 +19,87 @@ class NewsFeedEntry extends StatelessWidget { .cast() .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), ), - ], - ), + ), + ], ); } } diff --git a/lib/widgets/feed/feed_unknown.dart b/lib/widgets/feed/feed_unknown.dart index dc48852..cca6b41 100644 --- a/lib/widgets/feed/feed_unknown.dart +++ b/lib/widgets/feed/feed_unknown.dart @@ -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); } } diff --git a/lib/widgets/post/fediverse_post_item.dart b/lib/widgets/post/fediverse_post_item.dart index d7378e1..01f8ae5 100644 --- a/lib/widgets/post/fediverse_post_item.dart +++ b/lib/widgets/post/fediverse_post_item.dart @@ -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, + ), + ], ), ), ); diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 97192dc..2506af4 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -166,6 +166,14 @@ class _PostItemState extends State { } } + @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 _translateText() async { final ta = context.read(); setState(() => _isTranslating = true); @@ -284,6 +292,247 @@ class _PostItemState extends State { 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 { 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 { ), ], ).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 { 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(