✨ Post detail
This commit is contained in:
		| @@ -97,6 +97,7 @@ class AuthProvider extends GetConnect { | ||||
|     ); | ||||
|  | ||||
|     Get.find<AccountProvider>().connect(); | ||||
|     Get.find<AccountProvider>().notifyPrefetch(); | ||||
|  | ||||
|     return credentials!; | ||||
|   } | ||||
| @@ -105,6 +106,8 @@ class AuthProvider extends GetConnect { | ||||
|     _cacheUserProfileResponse = null; | ||||
|  | ||||
|     Get.find<AccountProvider>().disconnect(); | ||||
|     Get.find<AccountProvider>().notifications.clear(); | ||||
|     Get.find<AccountProvider>().notificationUnread.value = 0; | ||||
|  | ||||
|     storage.deleteAll(); | ||||
|   } | ||||
|   | ||||
| @@ -1,11 +1,27 @@ | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
|  | ||||
| class PostExploreProvider extends GetConnect { | ||||
| class PostProvider extends GetConnect { | ||||
|   @override | ||||
|   void onInit() { | ||||
|     httpClient.baseUrl = ServiceFinder.services['interactive']; | ||||
|   } | ||||
|  | ||||
|   Future<Response> listPost(int page) => get('/api/feed?take=${10}&offset=$page'); | ||||
|   Future<Response> listPost(int page) async { | ||||
|     final resp = await get('/api/feed?take=${10}&offset=$page'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw Exception(resp.body); | ||||
|     } | ||||
|  | ||||
|     return resp; | ||||
|   } | ||||
|  | ||||
|   Future<Response> getPost(String alias) async { | ||||
|     final resp = await get('/api/posts/$alias'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw Exception(resp.body); | ||||
|     } | ||||
|  | ||||
|     return resp; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import 'package:solian/screens/account.dart'; | ||||
| import 'package:solian/screens/account/friend.dart'; | ||||
| import 'package:solian/screens/account/personalize.dart'; | ||||
| import 'package:solian/screens/contact.dart'; | ||||
| import 'package:solian/screens/posts/post_detail.dart'; | ||||
| import 'package:solian/screens/social.dart'; | ||||
| import 'package:solian/screens/posts/publish.dart'; | ||||
| import 'package:solian/shells/basic_shell.dart'; | ||||
| @@ -32,6 +33,19 @@ abstract class AppRouter { | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       ShellRoute( | ||||
|         builder: (context, state, child) => | ||||
|             BasicShell(state: state, child: child), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/posts/:alias', | ||||
|             name: 'postDetail', | ||||
|             builder: (context, state) => PostDetailScreen( | ||||
|               alias: state.pathParameters['alias']!, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       ShellRoute( | ||||
|         builder: (context, state, child) => | ||||
|             BasicShell(state: state, child: child), | ||||
|   | ||||
							
								
								
									
										59
									
								
								lib/screens/posts/post_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								lib/screens/posts/post_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/providers/content/post_explore.dart'; | ||||
| import 'package:solian/widgets/posts/post_item.dart'; | ||||
|  | ||||
| class PostDetailScreen extends StatefulWidget { | ||||
|   final String alias; | ||||
|  | ||||
|   const PostDetailScreen({super.key, required this.alias}); | ||||
|  | ||||
|   @override | ||||
|   State<PostDetailScreen> createState() => _PostDetailScreenState(); | ||||
| } | ||||
|  | ||||
| class _PostDetailScreenState extends State<PostDetailScreen> { | ||||
|   Post? item; | ||||
|  | ||||
|   Future<Post?> getDetail() async { | ||||
|     final PostProvider provider = Get.find(); | ||||
|  | ||||
|     try { | ||||
|       final resp = await provider.getPost(widget.alias); | ||||
|       item = Post.fromJson(resp.body); | ||||
|     } catch (e) { | ||||
|       context.showErrorDialog(e).then((_) => Navigator.pop(context)); | ||||
|     } | ||||
|  | ||||
|     return item; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Material( | ||||
|       color: Theme.of(context).colorScheme.surface, | ||||
|       child: FutureBuilder( | ||||
|         future: getDetail(), | ||||
|         builder: (context, snapshot) { | ||||
|           if (!snapshot.hasData || snapshot.data == null) { | ||||
|             return const Center( | ||||
|               child: CircularProgressIndicator(), | ||||
|             ); | ||||
|           } | ||||
|  | ||||
|           return Column( | ||||
|             children: [ | ||||
|               PostItem( | ||||
|                 item: item!, | ||||
|                 isClickable: true, | ||||
|                 isShowReply: false, | ||||
|               ), | ||||
|             ], | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -23,10 +23,13 @@ class _SocialScreenState extends State<SocialScreen> { | ||||
|       PagingController(firstPageKey: 0); | ||||
|  | ||||
|   getPosts(int pageKey) async { | ||||
|     final PostExploreProvider provider = Get.find(); | ||||
|     final resp = await provider.listPost(pageKey); | ||||
|     if (resp.statusCode != 200) { | ||||
|       _pagingController.error = resp.bodyString; | ||||
|     final PostProvider provider = Get.find(); | ||||
|  | ||||
|     Response resp; | ||||
|     try { | ||||
|       resp = await provider.listPost(pageKey); | ||||
|     } catch (e) { | ||||
|       _pagingController.error = e; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
| @@ -41,7 +44,7 @@ class _SocialScreenState extends State<SocialScreen> { | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     Get.lazyPut(() => PostExploreProvider()); | ||||
|     Get.lazyPut(() => PostProvider()); | ||||
|     super.initState(); | ||||
|  | ||||
|     _pagingController.addPageRequestListener(getPosts); | ||||
| @@ -104,11 +107,17 @@ class _SocialScreenState extends State<SocialScreen> { | ||||
|                         child: PostItem( | ||||
|                           key: Key('p${item.alias}'), | ||||
|                           item: item, | ||||
|                           isClickable: true, | ||||
|                         ).paddingSymmetric( | ||||
|                           vertical: | ||||
|                               (item.attachments?.isEmpty ?? false) ? 8 : 0, | ||||
|                         ), | ||||
|                         onTap: () {}, | ||||
|                         onTap: () { | ||||
|                           AppRouter.instance.pushNamed( | ||||
|                             'postDetail', | ||||
|                             pathParameters: {'alias': item.alias}, | ||||
|                           ); | ||||
|                         }, | ||||
|                         onLongPress: () { | ||||
|                           showModalBottomSheet( | ||||
|                             useRootNavigator: true, | ||||
|   | ||||
| @@ -62,6 +62,7 @@ class SolianMessages extends Translations { | ||||
|           'notifyEmpty': 'All notifications read', | ||||
|           'notifyEmptyCaption': 'It seems like nothing happened recently', | ||||
|           'postAction': 'Post', | ||||
|           'postDetail': 'Post', | ||||
|           'postPublishing': 'Post a post', | ||||
|           'postIdentityNotify': 'You will post this post as', | ||||
|           'postContentPlaceholder': 'What\'s happened?!', | ||||
| @@ -140,6 +141,7 @@ class SolianMessages extends Translations { | ||||
|           'notifyEmpty': '通知箱为空', | ||||
|           'notifyEmptyCaption': '看起来最近没发生什么呢', | ||||
|           'postAction': '发表', | ||||
|           'postDetail': '帖子详情', | ||||
|           'postPublishing': '发表帖子', | ||||
|           'postIdentityNotify': '你将会以本身份发表帖子', | ||||
|           'postContentPlaceholder': '发生什么事了?!', | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import 'package:flutter_markdown/flutter_markdown.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:get/get_utils/get_utils.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/attachments/attachment_list.dart'; | ||||
| import 'package:solian/widgets/posts/post_quick_action.dart'; | ||||
| @@ -11,14 +12,18 @@ import 'package:timeago/timeago.dart' show format; | ||||
|  | ||||
| class PostItem extends StatefulWidget { | ||||
|   final Post item; | ||||
|   final bool isClickable; | ||||
|   final bool isCompact; | ||||
|   final bool isReactable; | ||||
|   final bool isShowReply; | ||||
|  | ||||
|   const PostItem({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     this.isClickable = false, | ||||
|     this.isCompact = false, | ||||
|     this.isReactable = true, | ||||
|     this.isShowReply = true, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -158,9 +163,31 @@ class _PostItemState extends State<PostItem> { | ||||
|                     padding: const EdgeInsets.all(0), | ||||
|                   ).paddingOnly(left: 12, right: 8), | ||||
|                   if (widget.item.replyTo != null) | ||||
|                     buildReply(context).paddingOnly(top: 4), | ||||
|                     GestureDetector( | ||||
|                       child: buildReply(context).paddingOnly(top: 4), | ||||
|                       onTap: () { | ||||
|                         if (!widget.isClickable) return; | ||||
|                         AppRouter.instance.pushNamed( | ||||
|                           'postDetail', | ||||
|                           pathParameters: { | ||||
|                             'alias': widget.item.replyTo!.alias, | ||||
|                           }, | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|                   if (widget.item.repostTo != null) | ||||
|                     buildRepost(context).paddingOnly(top: 4), | ||||
|                     GestureDetector( | ||||
|                       child: buildRepost(context).paddingOnly(top: 4), | ||||
|                       onTap: () { | ||||
|                         if (!widget.isClickable) return; | ||||
|                         AppRouter.instance.pushNamed( | ||||
|                           'postDetail', | ||||
|                           pathParameters: { | ||||
|                             'alias': widget.item.repostTo!.alias, | ||||
|                           }, | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ) | ||||
| @@ -173,6 +200,7 @@ class _PostItemState extends State<PostItem> { | ||||
|         ), | ||||
|         AttachmentList(attachmentsId: item.attachments ?? List.empty()), | ||||
|         PostQuickAction( | ||||
|           isShowReply: widget.isShowReply, | ||||
|           isReactable: widget.isReactable, | ||||
|           item: widget.item, | ||||
|           onReact: (symbol, changes) { | ||||
|   | ||||
| @@ -10,11 +10,13 @@ import 'package:solian/widgets/posts/post_reaction.dart'; | ||||
| class PostQuickAction extends StatefulWidget { | ||||
|   final Post item; | ||||
|   final bool isReactable; | ||||
|   final bool isShowReply; | ||||
|   final void Function(String symbol, int num) onReact; | ||||
|  | ||||
|   const PostQuickAction({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     this.isShowReply = true, | ||||
|     this.isReactable = true, | ||||
|     required this.onReact, | ||||
|   }); | ||||
| @@ -93,17 +95,17 @@ class _PostQuickActionState extends State<PostQuickAction> { | ||||
|         mainAxisAlignment: MainAxisAlignment.start, | ||||
|         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|         children: [ | ||||
|           if (widget.isReactable) | ||||
|           if (widget.isReactable && widget.isShowReply) | ||||
|             ActionChip( | ||||
|               avatar: const Icon(Icons.comment), | ||||
|               label: Text(widget.item.replyCount.toString()), | ||||
|               visualDensity: density, | ||||
|               onPressed: () {}, | ||||
|             ), | ||||
|           if (widget.isReactable) | ||||
|           if (widget.isReactable && widget.isShowReply) | ||||
|             const VerticalDivider( | ||||
|                     thickness: 0.3, width: 0.3, indent: 8, endIndent: 8) | ||||
|                 .paddingOnly(left: 8), | ||||
|                 .paddingSymmetric(horizontal: 8), | ||||
|           Expanded( | ||||
|             child: ListView( | ||||
|               shrinkWrap: true, | ||||
| @@ -132,7 +134,7 @@ class _PostQuickActionState extends State<PostQuickAction> { | ||||
|                     onPressed: () => showReactMenu(), | ||||
|                   ), | ||||
|               ], | ||||
|             ).paddingOnly(left: 8), | ||||
|             ), | ||||
|           ) | ||||
|         ], | ||||
|       ), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user