✨ Basic publisher page
This commit is contained in:
		| @@ -102,7 +102,13 @@ | |||||||
|   "publisherTotalUpvote": "Upvote", |   "publisherTotalUpvote": "Upvote", | ||||||
|   "publisherTotalDownvote": "Downvote", |   "publisherTotalDownvote": "Downvote", | ||||||
|   "publisherSocialPoint": "Social Point", |   "publisherSocialPoint": "Social Point", | ||||||
|   "publisherJoinedAt": "Joined At", |   "publisherJoinedAt": "Joined At {}", | ||||||
|  |   "publisherSocialPointTotal": { | ||||||
|  |     "zero": "No social point", | ||||||
|  |     "one": "{} social point", | ||||||
|  |     "other": "{} social points" | ||||||
|  |   }, | ||||||
|  |   "publisherRunBy": "Run by {}", | ||||||
|   "fieldPublisherBelongToRealm": "Belongs to", |   "fieldPublisherBelongToRealm": "Belongs to", | ||||||
|   "fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm", |   "fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm", | ||||||
|   "writePostTypeStory": "Post a story", |   "writePostTypeStory": "Post a story", | ||||||
| @@ -176,7 +182,7 @@ | |||||||
|   "fieldChatBelongToRealm": "Belongs to", |   "fieldChatBelongToRealm": "Belongs to", | ||||||
|   "fieldChatBelongToRealmUnset": "Unset Channel Belongs to Realm", |   "fieldChatBelongToRealmUnset": "Unset Channel Belongs to Realm", | ||||||
|   "channelEditingNotice": "You are editing channel {}", |   "channelEditingNotice": "You are editing channel {}", | ||||||
|   "channelDeleted": "Chat channel {} has been deleted." , |   "channelDeleted": "Chat channel {} has been deleted.", | ||||||
|   "channelDelete": "Delete channel {}", |   "channelDelete": "Delete channel {}", | ||||||
|   "channelDeleteDescription": "Are you sure you want to delete this channel? This operation is irreversible, all messages in this channel will be permanently deleted.", |   "channelDeleteDescription": "Are you sure you want to delete this channel? This operation is irreversible, all messages in this channel will be permanently deleted.", | ||||||
|   "channelDetailPersonalRegion": "Personal", |   "channelDetailPersonalRegion": "Personal", | ||||||
| @@ -256,7 +262,7 @@ | |||||||
|   "postDelete": "Delete post {}", |   "postDelete": "Delete post {}", | ||||||
|   "postDeleteDescription": "Are you sure you want to delete this post? This operation is irreversible.", |   "postDeleteDescription": "Are you sure you want to delete this post? This operation is irreversible.", | ||||||
|   "postDeleted": "Post {} has been deleted.", |   "postDeleted": "Post {} has been deleted.", | ||||||
|   "call" : "Call", |   "call": "Call", | ||||||
|   "callOngoingNotice": "A call is ongoing", |   "callOngoingNotice": "A call is ongoing", | ||||||
|   "callJoin": "Join", |   "callJoin": "Join", | ||||||
|   "callResume": "Resume", |   "callResume": "Resume", | ||||||
| @@ -343,5 +349,7 @@ | |||||||
|   "friendDelete": "Delete relation with {}", |   "friendDelete": "Delete relation with {}", | ||||||
|   "friendDeleteDescription": "Are you sure you want to delete the relation with {}? This operation is irreversible.", |   "friendDeleteDescription": "Are you sure you want to delete the relation with {}? This operation is irreversible.", | ||||||
|   "friendRequestAccept": "Accept", |   "friendRequestAccept": "Accept", | ||||||
|   "friendRequestDecline": "Decline" |   "friendRequestDecline": "Decline", | ||||||
|  |   "subscribe": "Subscribe", | ||||||
|  |   "unsubscribe": "Unsubscribe" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -102,7 +102,13 @@ | |||||||
|   "publisherTotalUpvote": "总顶数", |   "publisherTotalUpvote": "总顶数", | ||||||
|   "publisherTotalDownvote": "总踩数", |   "publisherTotalDownvote": "总踩数", | ||||||
|   "publisherSocialPoint": "社会信用点", |   "publisherSocialPoint": "社会信用点", | ||||||
|   "publisherJoinedAt": "加入于", |   "publisherJoinedAt": "加入于 {}", | ||||||
|  |   "publisherSocialPointTotal": { | ||||||
|  |     "zero": "无社会信用点", | ||||||
|  |     "one": "{} 点社会信用点", | ||||||
|  |     "other": "{} 点社会信用点" | ||||||
|  |   }, | ||||||
|  |   "publisherRunBy": "由 {} 管理", | ||||||
|   "fieldPublisherBelongToRealm": "所属领域", |   "fieldPublisherBelongToRealm": "所属领域", | ||||||
|   "fieldPublisherBelongToRealmUnset": "未设置发布者所属领域", |   "fieldPublisherBelongToRealmUnset": "未设置发布者所属领域", | ||||||
|   "writePostTypeStory": "发动态", |   "writePostTypeStory": "发动态", | ||||||
| @@ -343,5 +349,7 @@ | |||||||
|   "friendDelete": "遗忘跟 {} 的关系", |   "friendDelete": "遗忘跟 {} 的关系", | ||||||
|   "friendDeleteDescription": "你确定要遗忘跟 {} 的关系吗?这个操作无法撤销。", |   "friendDeleteDescription": "你确定要遗忘跟 {} 的关系吗?这个操作无法撤销。", | ||||||
|   "friendRequestAccept": "接受", |   "friendRequestAccept": "接受", | ||||||
|   "friendRequestDecline": "拒绝" |   "friendRequestDecline": "拒绝", | ||||||
|  |   "subscribe": "订阅", | ||||||
|  |   "unsubscribe": "取消订阅" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ void main() async { | |||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   if (!kReleaseMode) { |   if (!kReleaseMode) { | ||||||
|     debugInvertOversizedImages = true; |     // debugInvertOversizedImages = true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   GoRouter.optionURLReflectsImperativeAPIs = true; |   GoRouter.optionURLReflectsImperativeAPIs = true; | ||||||
|   | |||||||
| @@ -63,10 +63,15 @@ class SnPostContentProvider { | |||||||
|     return out; |     return out; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<(List<SnPost>, int)> listPosts({int take = 10, int offset = 0}) async { |   Future<(List<SnPost>, int)> listPosts({ | ||||||
|  |     int take = 10, | ||||||
|  |     int offset = 0, | ||||||
|  |     String? author, | ||||||
|  |   }) async { | ||||||
|     final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { |     final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { | ||||||
|       'take': take, |       'take': take, | ||||||
|       'offset': offset, |       'offset': offset, | ||||||
|  |       if (author != null) 'author': author, | ||||||
|     }); |     }); | ||||||
|     final List<SnPost> out = await _preloadRelatedDataInBatch( |     final List<SnPost> out = await _preloadRelatedDataInBatch( | ||||||
|       List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), |       List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), | ||||||
|   | |||||||
| @@ -89,12 +89,6 @@ class WebSocketProvider extends ChangeNotifier { | |||||||
|         final packet = WebSocketPackage.fromJson(jsonDecode(event)); |         final packet = WebSocketPackage.fromJson(jsonDecode(event)); | ||||||
|         log('Websocket incoming message: ${packet.method} ${packet.message}'); |         log('Websocket incoming message: ${packet.method} ${packet.message}'); | ||||||
|         stream.sink.add(packet); |         stream.sink.add(packet); | ||||||
|         // TODO handle notification |  | ||||||
|         // if (packet.method == 'notifications.new') { |  | ||||||
|         //   final NotificationProvider nty = Get.find(); |  | ||||||
|         //   nty.notifications.add(Notification.fromJson(packet.payload!)); |  | ||||||
|         //   nty.notificationUnread.value++; |  | ||||||
|         // } |  | ||||||
|       }, |       }, | ||||||
|       onDone: () { |       onDone: () { | ||||||
|         isConnected = false; |         isConnected = false; | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import 'package:surface/screens/home.dart'; | |||||||
| import 'package:surface/screens/notification.dart'; | import 'package:surface/screens/notification.dart'; | ||||||
| import 'package:surface/screens/post/post_detail.dart'; | import 'package:surface/screens/post/post_detail.dart'; | ||||||
| import 'package:surface/screens/post/post_editor.dart'; | import 'package:surface/screens/post/post_editor.dart'; | ||||||
|  | import 'package:surface/screens/post/post_publisher.dart'; | ||||||
| import 'package:surface/screens/post/post_search.dart'; | import 'package:surface/screens/post/post_search.dart'; | ||||||
| import 'package:surface/screens/realm.dart'; | import 'package:surface/screens/realm.dart'; | ||||||
| import 'package:surface/screens/realm/manage.dart'; | import 'package:surface/screens/realm/manage.dart'; | ||||||
| @@ -77,6 +78,13 @@ final _appRoutes = [ | |||||||
|               child: PostSearchScreen(), |               child: PostSearchScreen(), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|  |           GoRoute( | ||||||
|  |             path: '/pub/:name', | ||||||
|  |             name: 'postPublisher', | ||||||
|  |             builder: (context, state) => AppBackground( | ||||||
|  |               child: PostPublisherScreen(name: state.pathParameters['name']!), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|           GoRoute( |           GoRoute( | ||||||
|             path: '/:slug', |             path: '/:slug', | ||||||
|             name: 'postDetail', |             name: 'postDetail', | ||||||
|   | |||||||
							
								
								
									
										272
									
								
								lib/screens/post/post_publisher.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								lib/screens/post/post_publisher.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | |||||||
|  | import 'dart:ui'; | ||||||
|  |  | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/post.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/user_directory.dart'; | ||||||
|  | import 'package:surface/types/account.dart'; | ||||||
|  | import 'package:surface/types/post.dart'; | ||||||
|  | import 'package:surface/widgets/account/account_image.dart'; | ||||||
|  | import 'package:surface/widgets/dialog.dart'; | ||||||
|  | import 'package:surface/widgets/post/post_item.dart'; | ||||||
|  | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  | import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||||
|  |  | ||||||
|  | class PostPublisherScreen extends StatefulWidget { | ||||||
|  |   final String name; | ||||||
|  |   const PostPublisherScreen({super.key, required this.name}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<PostPublisherScreen> createState() => _PostPublisherScreenState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PostPublisherScreenState extends State<PostPublisherScreen> { | ||||||
|  |   late final ScrollController _scrollController = ScrollController(); | ||||||
|  |  | ||||||
|  |   SnPublisher? _publisher; | ||||||
|  |   SnAccount? _account; | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPublisher() async { | ||||||
|  |     try { | ||||||
|  |       final sn = context.read<SnNetworkProvider>(); | ||||||
|  |       final ud = context.read<UserDirectoryProvider>(); | ||||||
|  |       final resp = await sn.client.get('/cgi/co/publishers/${widget.name}'); | ||||||
|  |       if (!mounted) return; | ||||||
|  |       _publisher = SnPublisher.fromJson(resp.data); | ||||||
|  |       _account = await ud.getAccount(_publisher?.accountId); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err).then((_) { | ||||||
|  |         if (mounted) Navigator.pop(context); | ||||||
|  |       }); | ||||||
|  |     } finally { | ||||||
|  |       setState(() {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   double _appBarBlur = 0.0; | ||||||
|  |  | ||||||
|  |   late final _appBarWidth = MediaQuery.of(context).size.width; | ||||||
|  |   late final _appBarHeight = | ||||||
|  |       (_appBarWidth * kBannerAspectRatio).roundToDouble(); | ||||||
|  |  | ||||||
|  |   void _updateAppBarBlur() { | ||||||
|  |     if (_scrollController.offset > _appBarHeight) return; | ||||||
|  |     setState(() { | ||||||
|  |       _appBarBlur = | ||||||
|  |           (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   int? _postCount; | ||||||
|  |   final List<SnPost> _posts = List.empty(growable: true); | ||||||
|  |  | ||||||
|  |   Future<void> _fetchPosts() async { | ||||||
|  |     if (_isBusy) return; | ||||||
|  |     _isBusy = true; | ||||||
|  |     try { | ||||||
|  |       final pt = context.read<SnPostContentProvider>(); | ||||||
|  |       final result = await pt.listPosts( | ||||||
|  |         offset: _posts.length, | ||||||
|  |         author: widget.name, | ||||||
|  |       ); | ||||||
|  |       _postCount = result.$2; | ||||||
|  |       _posts.addAll(result.$1); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (!mounted) return; | ||||||
|  |       context.showErrorDialog(err); | ||||||
|  |     } finally { | ||||||
|  |       _isBusy = false; | ||||||
|  |       setState(() {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _fetchPublisher().then((_) { | ||||||
|  |       _fetchPosts(); | ||||||
|  |     }); | ||||||
|  |     _scrollController.addListener(_updateAppBarBlur); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _scrollController.removeListener(_updateAppBarBlur); | ||||||
|  |     _scrollController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const kBannerAspectRatio = 7 / 16; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final imageHeight = _appBarHeight + kToolbarHeight + 8; | ||||||
|  |  | ||||||
|  |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |     return Scaffold( | ||||||
|  |       body: CustomScrollView( | ||||||
|  |         controller: _scrollController, | ||||||
|  |         slivers: [ | ||||||
|  |           SliverAppBar( | ||||||
|  |             expandedHeight: _appBarHeight, | ||||||
|  |             title: Text(_publisher?.nick ?? 'loading'.tr()), | ||||||
|  |             pinned: true, | ||||||
|  |             flexibleSpace: _publisher != null | ||||||
|  |                 ? Stack( | ||||||
|  |                     fit: StackFit.expand, | ||||||
|  |                     children: [ | ||||||
|  |                       UniversalImage( | ||||||
|  |                         sn.getAttachmentUrl(_publisher!.banner), | ||||||
|  |                         fit: BoxFit.cover, | ||||||
|  |                         height: imageHeight, | ||||||
|  |                         width: _appBarWidth, | ||||||
|  |                         cacheHeight: imageHeight, | ||||||
|  |                         cacheWidth: _appBarWidth, | ||||||
|  |                       ), | ||||||
|  |                       Positioned( | ||||||
|  |                         top: 0, | ||||||
|  |                         left: 0, | ||||||
|  |                         right: 0, | ||||||
|  |                         height: 56 + MediaQuery.of(context).padding.top, | ||||||
|  |                         child: ClipRect( | ||||||
|  |                           child: BackdropFilter( | ||||||
|  |                             filter: ImageFilter.blur( | ||||||
|  |                               sigmaX: _appBarBlur, | ||||||
|  |                               sigmaY: _appBarBlur, | ||||||
|  |                             ), | ||||||
|  |                             child: Container( | ||||||
|  |                               color: Colors.black.withOpacity( | ||||||
|  |                                 clampDouble(_appBarBlur * 0.1, 0, 0.5), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ) | ||||||
|  |                 : null, | ||||||
|  |           ), | ||||||
|  |           if (_publisher != null) | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Container( | ||||||
|  |                 constraints: const BoxConstraints(maxWidth: 640), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                   children: [ | ||||||
|  |                     Row( | ||||||
|  |                       children: [ | ||||||
|  |                         AccountImage( | ||||||
|  |                           content: _publisher!.avatar, | ||||||
|  |                           radius: 28, | ||||||
|  |                         ), | ||||||
|  |                         const Gap(16), | ||||||
|  |                         Expanded( | ||||||
|  |                           child: Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                             children: [ | ||||||
|  |                               Text( | ||||||
|  |                                 _publisher!.nick, | ||||||
|  |                                 style: Theme.of(context).textTheme.titleMedium, | ||||||
|  |                               ).bold(), | ||||||
|  |                               Text('@${_publisher!.name}').fontSize(13), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         ElevatedButton( | ||||||
|  |                           style: | ||||||
|  |                               ButtonStyle(elevation: WidgetStatePropertyAll(0)), | ||||||
|  |                           onPressed: () {}, | ||||||
|  |                           child: Text('subscribe').tr(), | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|  |                     ).padding(right: 8), | ||||||
|  |                     const Gap(12), | ||||||
|  |                     Text(_publisher!.description).padding(horizontal: 8), | ||||||
|  |                     const Gap(12), | ||||||
|  |                     Column( | ||||||
|  |                       children: [ | ||||||
|  |                         Row( | ||||||
|  |                           children: [ | ||||||
|  |                             const Icon(Symbols.calendar_add_on), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             Text('publisherJoinedAt').tr(args: [ | ||||||
|  |                               DateFormat('y/M/d').format(_publisher!.createdAt) | ||||||
|  |                             ]), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         Row( | ||||||
|  |                           children: [ | ||||||
|  |                             const Icon(Symbols.trending_up), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             Text('publisherSocialPointTotal').plural( | ||||||
|  |                               _publisher!.totalUpvote - | ||||||
|  |                                   _publisher!.totalDownvote, | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         Row( | ||||||
|  |                           children: [ | ||||||
|  |                             const Icon(Symbols.tools_wrench), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             InkWell( | ||||||
|  |                               child: Text('publisherRunBy').tr(args: [ | ||||||
|  |                                 '@${_account?.name ?? 'unknown'}', | ||||||
|  |                               ]), | ||||||
|  |                               onTap: () {}, | ||||||
|  |                             ), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             AccountImage(content: _account?.avatar, radius: 8), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|  |                     ).padding(horizontal: 8), | ||||||
|  |                   ], | ||||||
|  |                 ).padding(all: 16), | ||||||
|  |               ).center(), | ||||||
|  |             ), | ||||||
|  |           SliverToBoxAdapter( | ||||||
|  |             child: const Divider(height: 1), | ||||||
|  |           ), | ||||||
|  |           SliverInfiniteList( | ||||||
|  |             itemCount: _posts.length, | ||||||
|  |             isLoading: _isBusy, | ||||||
|  |             hasReachedMax: _postCount != null && _posts.length >= _postCount!, | ||||||
|  |             onFetchData: _fetchPosts, | ||||||
|  |             itemBuilder: (context, idx) { | ||||||
|  |               return GestureDetector( | ||||||
|  |                 child: PostItem( | ||||||
|  |                   data: _posts[idx], | ||||||
|  |                   maxWidth: 640, | ||||||
|  |                   onChanged: (data) { | ||||||
|  |                     setState(() => _posts[idx] = data); | ||||||
|  |                   }, | ||||||
|  |                   onDeleted: () { | ||||||
|  |                     _posts.clear(); | ||||||
|  |                     _fetchPosts(); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 onTap: () { | ||||||
|  |                   GoRouter.of(context).pushNamed( | ||||||
|  |                     'postDetail', | ||||||
|  |                     pathParameters: {'slug': _posts[idx].id.toString()}, | ||||||
|  |                     extra: _posts[idx], | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |             separatorBuilder: (context, index) => const Divider(height: 1), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -89,12 +89,15 @@ class AppBackground extends StatelessWidget { | |||||||
|                 return _buildWithBackgroundImage(context, file, child); |                 return _buildWithBackgroundImage(context, file, child); | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             return Material( | ||||||
|  |               color: Theme.of(context).colorScheme.surface, | ||||||
|  |               child: child, | ||||||
|  |             ); | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           return Material( |           return Material( | ||||||
|             color: isRoot |             color: Colors.transparent, | ||||||
|                 ? Theme.of(context).colorScheme.surface |  | ||||||
|                 : Colors.transparent, |  | ||||||
|             child: child, |             child: child, | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -273,13 +273,14 @@ class _PostContentHeader extends StatelessWidget { | |||||||
|           ), |           ), | ||||||
|           onTap: () { |           onTap: () { | ||||||
|             showPopover( |             showPopover( | ||||||
|  |               backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|               context: context, |               context: context, | ||||||
|               transition: PopoverTransition.other, |               transition: PopoverTransition.other, | ||||||
|               bodyBuilder: (context) => SizedBox( |               bodyBuilder: (context) => SizedBox( | ||||||
|                 width: 400, |                 width: 400, | ||||||
|                 child: PublisherPopoverCard( |                 child: PublisherPopoverCard( | ||||||
|                   data: data.publisher, |                   data: data.publisher, | ||||||
|                 ).padding(horizontal: 16, vertical: 16), |                 ), | ||||||
|               ), |               ), | ||||||
|               direction: PopoverDirection.bottom, |               direction: PopoverDirection.bottom, | ||||||
|               arrowHeight: 5, |               arrowHeight: 5, | ||||||
|   | |||||||
| @@ -1,10 +1,14 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
| import 'package:surface/widgets/account/account_image.dart'; | import 'package:surface/widgets/account/account_image.dart'; | ||||||
|  | import 'package:surface/widgets/universal_image.dart'; | ||||||
|  |  | ||||||
| class PublisherPopoverCard extends StatelessWidget { | class PublisherPopoverCard extends StatelessWidget { | ||||||
|   final SnPublisher data; |   final SnPublisher data; | ||||||
| @@ -12,10 +16,25 @@ class PublisherPopoverCard extends StatelessWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |     final sn = context.read<SnNetworkProvider>(); | ||||||
|  |  | ||||||
|     return Column( |     return Column( | ||||||
|       crossAxisAlignment: CrossAxisAlignment.start, |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|       mainAxisSize: MainAxisSize.min, |       mainAxisSize: MainAxisSize.min, | ||||||
|       children: [ |       children: [ | ||||||
|  |         if (data.banner.isNotEmpty) | ||||||
|  |           Container( | ||||||
|  |             color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |             child: AspectRatio( | ||||||
|  |               aspectRatio: 16 / 7, | ||||||
|  |               child: AutoResizeUniversalImage( | ||||||
|  |                 sn.getAttachmentUrl(data.banner), | ||||||
|  |                 fit: BoxFit.cover, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         // Top padding | ||||||
|  |         Gap(16), | ||||||
|         Row( |         Row( | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|           children: [ |           children: [ | ||||||
| @@ -35,14 +54,20 @@ class PublisherPopoverCard extends StatelessWidget { | |||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|             IconButton( |             IconButton( | ||||||
|               onPressed: () {}, |               onPressed: () { | ||||||
|  |                 Navigator.pop(context); | ||||||
|  |                 GoRouter.of(context).pushNamed( | ||||||
|  |                   'postPublisher', | ||||||
|  |                   pathParameters: {'name': data.name}, | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|               icon: const Icon(Symbols.chevron_right), |               icon: const Icon(Symbols.chevron_right), | ||||||
|               padding: EdgeInsets.zero, |               padding: EdgeInsets.zero, | ||||||
|               visualDensity: const VisualDensity(horizontal: -4, vertical: -4), |               visualDensity: const VisualDensity(horizontal: -4, vertical: -4), | ||||||
|             ), |             ), | ||||||
|             const Gap(8) |             const Gap(8) | ||||||
|           ], |           ], | ||||||
|         ), |         ).padding(horizontal: 16), | ||||||
|         const Gap(16), |         const Gap(16), | ||||||
|         Row( |         Row( | ||||||
|           children: [ |           children: [ | ||||||
| @@ -92,7 +117,9 @@ class PublisherPopoverCard extends StatelessWidget { | |||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|           ], |           ], | ||||||
|         ), |         ).padding(horizontal: 16), | ||||||
|  |         // Bottom padding | ||||||
|  |         const Gap(16), | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user