✨ Post a post
This commit is contained in:
		| @@ -7,5 +7,5 @@ class PostExploreProvider extends GetConnect { | ||||
|     httpClient.baseUrl = ServiceFinder.services['interactive']; | ||||
|   } | ||||
|  | ||||
|   Future<Response> listPost(int page) => get('/api/feed?take=${10}&offset=${page * 10}'); | ||||
|   Future<Response> listPost(int page) => get('/api/feed?take=${10}&offset=$page'); | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import 'package:solian/screens/account.dart'; | ||||
| import 'package:solian/screens/auth/signin.dart'; | ||||
| import 'package:solian/screens/auth/signup.dart'; | ||||
| import 'package:solian/screens/home.dart'; | ||||
| import 'package:solian/screens/posts/publish.dart'; | ||||
| import 'package:solian/shells/nav_shell.dart'; | ||||
|  | ||||
| abstract class AppRouter { | ||||
| @@ -33,6 +34,11 @@ abstract class AppRouter { | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: "/posts/publish", | ||||
|         name: "postPublishing", | ||||
|         builder: (context, state) => const PostPublishingScreen(), | ||||
|       ), | ||||
|     ], | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -106,7 +106,7 @@ class AccountNameCard extends StatelessWidget { | ||||
|             contentPadding: const EdgeInsets.only(left: 22, right: 34, top: 4, bottom: 4), | ||||
|             leading: AccountAvatar(content: snapshot.data!.body?['avatar'], radius: 24), | ||||
|             title: Text(snapshot.data!.body?['nick']), | ||||
|             subtitle: Text(snapshot.data!.body?['name']), | ||||
|             subtitle: Text(snapshot.data!.body?['email']), | ||||
|           ), | ||||
|         ); | ||||
|       }, | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/content/post_explore.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/widgets/posts/post_item.dart'; | ||||
|  | ||||
| class HomeScreen extends StatefulWidget { | ||||
| @@ -13,27 +16,23 @@ class HomeScreen extends StatefulWidget { | ||||
| } | ||||
|  | ||||
| class _HomeScreenState extends State<HomeScreen> { | ||||
|   int _pageKey = 0; | ||||
|   int? _dataTotal; | ||||
|  | ||||
|   bool _isFirstLoading = true; | ||||
|  | ||||
|   final List<Post> _data = List.empty(growable: true); | ||||
|  | ||||
|   getPosts() async { | ||||
|     if (_dataTotal != null && _pageKey * 10 > _dataTotal!) return; | ||||
|   final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0); | ||||
|  | ||||
|   getPosts(int pageKey) async { | ||||
|     final PostExploreProvider provider = Get.find(); | ||||
|     final resp = await provider.listPost(_pageKey); | ||||
|     final PaginationResult result = PaginationResult.fromJson(resp.body); | ||||
|     final resp = await provider.listPost(pageKey); | ||||
|     if (resp.statusCode != 200) { | ||||
|       _pagingController.error = resp.bodyString; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     setState(() { | ||||
|       final parsed = result.data?.map((e) => Post.fromJson(e)); | ||||
|       if (parsed != null) _data.addAll(parsed); | ||||
|       _isFirstLoading = false; | ||||
|       _dataTotal = result.count; | ||||
|       _pageKey++; | ||||
|     }); | ||||
|     final PaginationResult result = PaginationResult.fromJson(resp.body); | ||||
|     final parsed = result.data?.map((e) => Post.fromJson(e)).toList(); | ||||
|     if (parsed != null && parsed.length >= 10) { | ||||
|       _pagingController.appendPage(parsed, pageKey + parsed.length); | ||||
|     } else if (parsed != null) { | ||||
|       _pagingController.appendLastPage(parsed); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -41,36 +40,46 @@ class _HomeScreenState extends State<HomeScreen> { | ||||
|     Get.lazyPut(() => PostExploreProvider()); | ||||
|     super.initState(); | ||||
|  | ||||
|     Future.delayed(Duration.zero, () => getPosts()); | ||||
|     _pagingController.addPageRequestListener(getPosts); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (_isFirstLoading) { | ||||
|       return const Center( | ||||
|         child: CircularProgressIndicator(), | ||||
|       ); | ||||
|     } | ||||
|     final AuthProvider auth = Get.find(); | ||||
|  | ||||
|     return Material( | ||||
|       color: Theme.of(context).colorScheme.background, | ||||
|       child: RefreshIndicator( | ||||
|         onRefresh: () { | ||||
|           _data.clear(); | ||||
|           _pageKey = 0; | ||||
|           _dataTotal = null; | ||||
|           return getPosts(); | ||||
|         }, | ||||
|         child: ListView.separated( | ||||
|           itemCount: _data.length, | ||||
|           itemBuilder: (BuildContext context, int index) { | ||||
|             final item = _data[index]; | ||||
|             return GestureDetector( | ||||
|               child: PostItem(key: Key('p${item.alias}'), item: item), | ||||
|               onTap: () {}, | ||||
|             ); | ||||
|           }, | ||||
|           separatorBuilder: (_, __) => const Divider(thickness: 0.3, height: 0.3), | ||||
|     return Scaffold( | ||||
|       floatingActionButton: FutureBuilder( | ||||
|           future: auth.isAuthorized, | ||||
|           builder: (context, snapshot) { | ||||
|             if (snapshot.hasData && snapshot.data == true) { | ||||
|               return FloatingActionButton( | ||||
|                 child: const Icon(Icons.add), | ||||
|                 onPressed: () async { | ||||
|                   final value = await AppRouter.instance.pushNamed('postPublishing'); | ||||
|                   if (value != null) { | ||||
|                     _pagingController.refresh(); | ||||
|                   } | ||||
|                 }, | ||||
|               ); | ||||
|             } | ||||
|             return Container(); | ||||
|           }), | ||||
|       body: Material( | ||||
|         color: Theme.of(context).colorScheme.background, | ||||
|         child: RefreshIndicator( | ||||
|           onRefresh: () => Future.sync(() => _pagingController.refresh()), | ||||
|           child: PagedListView<int, Post>.separated( | ||||
|             pagingController: _pagingController, | ||||
|             builderDelegate: PagedChildBuilderDelegate<Post>( | ||||
|               itemBuilder: (context, item, index) { | ||||
|                 return GestureDetector( | ||||
|                   child: PostItem(key: Key('p${item.alias}'), item: item), | ||||
|                   onTap: () {}, | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|             separatorBuilder: (_, __) => const Divider(thickness: 0.3, height: 0.3), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
							
								
								
									
										124
									
								
								lib/screens/posts/publish.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								lib/screens/posts/publish.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/shells/nav_shell.dart' as shell; | ||||
|  | ||||
| class PostPublishingScreen extends StatefulWidget { | ||||
|   const PostPublishingScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<PostPublishingScreen> createState() => _PostPublishingScreenState(); | ||||
| } | ||||
|  | ||||
| class _PostPublishingScreenState extends State<PostPublishingScreen> { | ||||
|   final _contentController = TextEditingController(); | ||||
|  | ||||
|   bool _isSubmitting = false; | ||||
|  | ||||
|   void applyPost() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (!await auth.isAuthorized) return; | ||||
|     if (_contentController.value.text.isEmpty) return; | ||||
|  | ||||
|     setState(() => _isSubmitting = true); | ||||
|  | ||||
|     final client = GetConnect(); | ||||
|     client.httpClient.baseUrl = ServiceFinder.services['interactive']; | ||||
|     client.httpClient.addAuthenticator(auth.reqAuthenticator); | ||||
|  | ||||
|     final resp = await client.post('/api/posts', { | ||||
|       'content': _contentController.value.text, | ||||
|     }); | ||||
|     if (resp.statusCode != 200) { | ||||
|       Get.showSnackbar(GetSnackBar( | ||||
|         title: 'errorHappened'.tr, | ||||
|         message: resp.bodyString, | ||||
|       )); | ||||
|     } else { | ||||
|       AppRouter.instance.pop(resp.body); | ||||
|     } | ||||
|  | ||||
|     setState(() => _isSubmitting = false); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|  | ||||
|     return Material( | ||||
|       color: Theme.of(context).colorScheme.background, | ||||
|       child: Scaffold( | ||||
|         appBar: AppBar( | ||||
|           title: Text('postPublishing'.tr), | ||||
|           leading: const shell.BackButton(), | ||||
|           actions: [ | ||||
|             TextButton( | ||||
|               child: Text('postAction'.tr.toUpperCase()), | ||||
|               onPressed: () => applyPost(), | ||||
|             ) | ||||
|           ], | ||||
|         ), | ||||
|         body: SafeArea( | ||||
|           top: false, | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), | ||||
|               FutureBuilder( | ||||
|                 future: auth.getProfile(), | ||||
|                 builder: (context, snapshot) { | ||||
|                   if (snapshot.hasData) { | ||||
|                     return ListTile( | ||||
|                       leading: AccountAvatar(content: snapshot.data?.body!['avatar'], radius: 22), | ||||
|                       title: Text(snapshot.data?.body!['nick']), | ||||
|                       subtitle: Text('postIdentityNotify'.tr), | ||||
|                     ); | ||||
|                   } else { | ||||
|                     return Container(); | ||||
|                   } | ||||
|                 }, | ||||
|               ), | ||||
|               const Divider(thickness: 0.3), | ||||
|               Expanded( | ||||
|                 child: Container( | ||||
|                   padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | ||||
|                   child: TextField( | ||||
|                     maxLines: null, | ||||
|                     autofocus: true, | ||||
|                     autocorrect: true, | ||||
|                     keyboardType: TextInputType.multiline, | ||||
|                     controller: _contentController, | ||||
|                     decoration: InputDecoration.collapsed( | ||||
|                       hintText: 'postContentPlaceholder'.tr, | ||||
|                     ), | ||||
|                     onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|               Container( | ||||
|                 constraints: const BoxConstraints(minHeight: 56), | ||||
|                 decoration: BoxDecoration( | ||||
|                   border: Border( | ||||
|                     top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor), | ||||
|                   ), | ||||
|                 ), | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     TextButton( | ||||
|                       style: TextButton.styleFrom(shape: const CircleBorder()), | ||||
|                       child: const Icon(Icons.camera_alt), | ||||
|                       onPressed: () {}, | ||||
|                     ) | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -14,16 +14,6 @@ class NavShell extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final backButton = IconButton( | ||||
|       icon: const Icon(Icons.arrow_back), | ||||
|       tooltip: MaterialLocalizations.of(context).backButtonTooltip, | ||||
|       onPressed: () { | ||||
|         if (AppRouter.instance.canPop()) { | ||||
|           AppRouter.instance.pop(); | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|  | ||||
|     final canPop = AppRouter.instance.canPop(); | ||||
|  | ||||
|     return Scaffold( | ||||
| @@ -32,7 +22,7 @@ class NavShell extends StatelessWidget { | ||||
|         centerTitle: false, | ||||
|         titleSpacing: canPop ? null : 24, | ||||
|         elevation: SolianTheme.isLargeScreen(context) ? 1 : 0, | ||||
|         leading: canPop ? backButton : null, | ||||
|         leading: canPop ? BackButton() : null, | ||||
|       ), | ||||
|       bottomNavigationBar: SolianTheme.isLargeScreen(context) ? null : const AppNavigationBottomBar(), | ||||
|       body: SolianTheme.isLargeScreen(context) | ||||
| @@ -47,3 +37,20 @@ class NavShell extends StatelessWidget { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class BackButton extends StatelessWidget { | ||||
|   const BackButton({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IconButton( | ||||
|       icon: const Icon(Icons.arrow_back), | ||||
|       tooltip: MaterialLocalizations.of(context).backButtonTooltip, | ||||
|       onPressed: () { | ||||
|         if (AppRouter.instance.canPop()) { | ||||
|           AppRouter.instance.pop(); | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -17,13 +17,18 @@ class SolianMessages extends Translations { | ||||
|           'friend': 'Friend', | ||||
|           'signin': 'Sign in', | ||||
|           'signinCaption': 'Sign in to create post, start a realm, message your friend and more!', | ||||
|           'signinRiskDetected': 'Risk detected, click Next to open a webpage and signin through it to pass security check.', | ||||
|           'signinRiskDetected': | ||||
|               'Risk detected, click Next to open a webpage and signin through it to pass security check.', | ||||
|           'signup': 'Sign up', | ||||
|           'signupCaption': 'Create an account on Solarpass and then get the access of entire Solar Network!', | ||||
|           'signout': 'Sign out', | ||||
|           'riskDetection': 'Risk Detected', | ||||
|           'matureContent': 'Mature Content', | ||||
|           'matureContentCaption': 'The content is rated and may not suitable for everyone to view' | ||||
|           'matureContentCaption': 'The content is rated and may not suitable for everyone to view', | ||||
|           'postAction': 'Post', | ||||
|           'postPublishing': 'Post a post', | ||||
|           'postIdentityNotify': 'You will post this post as', | ||||
|           'postContentPlaceholder': 'What\'s happened?!', | ||||
|         }, | ||||
|         'zh_CN': { | ||||
|           'next': '下一步', | ||||
| @@ -44,8 +49,12 @@ class SolianMessages extends Translations { | ||||
|           'signupCaption': '在 Solarpass 注册一个账号以获得整个 Solar Network 的存取权!', | ||||
|           'signout': '登出', | ||||
|           'riskDetection': '检测到风险', | ||||
|           'matureContent': '成人内容', | ||||
|           'matureContentCaption': '该内容可能会对您的社会关系产生影响,请确认四下环境后再查看' | ||||
|           'matureContent': '评级内容', | ||||
|           'matureContentCaption': '该内容已被评级为家长指导级或以上,这可能说明内容包含一系列不友好的成分', | ||||
|           'postAction': '发表', | ||||
|           'postPublishing': '发表帖子', | ||||
|           'postIdentityNotify': '你将会以本身份发表帖子', | ||||
|           'postContentPlaceholder': '发生什么事了?!', | ||||
|         } | ||||
|       }; | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,7 @@ class AccountAvatar extends StatelessWidget { | ||||
|     final direct = content.startsWith('http'); | ||||
|  | ||||
|     return CircleAvatar( | ||||
|       key: Key('a$content'), | ||||
|       radius: radius, | ||||
|       backgroundColor: color, | ||||
|       backgroundImage: NetworkImage(direct ? content : '${ServiceFinder.services['paperclip']}/api/attachments/$content'), | ||||
|   | ||||
							
								
								
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -174,6 +174,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.1.2" | ||||
|   flutter_staggered_grid_view: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_staggered_grid_view | ||||
|       sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.0" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @@ -216,6 +224,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.0.2" | ||||
|   infinite_scroll_pagination: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: infinite_scroll_pagination | ||||
|       sha256: b68bce20752fcf36c7739e60de4175494f74e99e9a69b4dd2fe3a1dd07a7f16a | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.0.0" | ||||
|   intl: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -389,6 +405,14 @@ packages: | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.99" | ||||
|   sliver_tools: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: sliver_tools | ||||
|       sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.2.12" | ||||
|   source_span: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -44,6 +44,7 @@ dependencies: | ||||
|   oauth2: ^2.0.2 | ||||
|   carousel_slider: ^4.2.1 | ||||
|   url_launcher: ^6.2.6 | ||||
|   infinite_scroll_pagination: ^4.0.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user