♻️ Post list controller layer
This commit is contained in:
		
							
								
								
									
										109
									
								
								lib/controllers/post_list_controller.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								lib/controllers/post_list_controller.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| 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 '../providers/content/posts.dart'; | ||||
|  | ||||
| class PostListController { | ||||
|   /// The polling source modifier. | ||||
|   /// - `0`: default recommendations | ||||
|   /// - `1`: shuffle mode | ||||
|   RxInt mode = 0.obs; | ||||
|  | ||||
|   /// The paging controller for infinite loading. | ||||
|   /// Only available when mode is `0`. | ||||
|   PagingController<int, Post> pagingController = | ||||
|   PagingController(firstPageKey: 0); | ||||
|  | ||||
|   PostListController() { | ||||
|     _initPagingController(); | ||||
|   } | ||||
|  | ||||
|   /// Initialize a compatibility layer to paging controller | ||||
|   void _initPagingController() { | ||||
|     pagingController.addPageRequestListener(_onPagingControllerRequest); | ||||
|   } | ||||
|  | ||||
|   Future<void> _onPagingControllerRequest(int pageKey) async { | ||||
|     try { | ||||
|       final result = await loadMore(); | ||||
|  | ||||
|       if (result != null && hasMore.value) { | ||||
|         pagingController.appendPage(result, nextPageKey.value); | ||||
|       } else if (result != null) { | ||||
|         pagingController.appendLastPage(result); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       pagingController.error = e; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _resetPagingController() { | ||||
|     pagingController.removePageRequestListener(_onPagingControllerRequest); | ||||
|     pagingController.nextPageKey = nextPageKey.value; | ||||
|     pagingController.itemList?.clear(); | ||||
|   } | ||||
|  | ||||
|   RxBool isBusy = false.obs; | ||||
|  | ||||
|   RxList<Post> postList = RxList.empty(growable: true); | ||||
|   RxInt nextPageKey = 0.obs; | ||||
|   RxBool hasMore = true.obs; | ||||
|  | ||||
|   Future<void> reloadAllOver() async { | ||||
|     nextPageKey.value = 0; | ||||
|     hasMore.value = true; | ||||
|     _resetPagingController(); | ||||
|     final result = await loadMore(); | ||||
|     if (result != null && hasMore.value) { | ||||
|       pagingController.appendPage(result, nextPageKey.value); | ||||
|     } else if (result != null) { | ||||
|       pagingController.appendLastPage(result); | ||||
|     } | ||||
|     _initPagingController(); | ||||
|   } | ||||
|  | ||||
|   Future<List<Post>?> loadMore() async { | ||||
|     final result = await _loadPosts(nextPageKey.value); | ||||
|  | ||||
|     if (result != null && result.length >= 10) { | ||||
|       nextPageKey.value = nextPageKey.value + result.length; | ||||
|       hasMore.value = true; | ||||
|     } else if (result != null) { | ||||
|       nextPageKey.value = nextPageKey.value + result.length; | ||||
|       hasMore.value = false; | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   Future<List<Post>?> _loadPosts(int pageKey) async { | ||||
|     isBusy.value = true; | ||||
|  | ||||
|     final PostProvider provider = Get.find(); | ||||
|  | ||||
|     Response resp; | ||||
|     try { | ||||
|       resp = await provider.listRecommendations( | ||||
|         pageKey, | ||||
|         channel: mode.value == 0 ? null : 'shuffle', | ||||
|       ); | ||||
|     } catch (e) { | ||||
|       rethrow; | ||||
|     } finally { | ||||
|       isBusy.value = false; | ||||
|     } | ||||
|  | ||||
|     final PaginationResult result = PaginationResult.fromJson(resp.body); | ||||
|     final out = result.data?.map((e) => Post.fromJson(e)).toList(); | ||||
|  | ||||
|     if (out != null) postList.addAll(out.cast<Post>()); | ||||
|  | ||||
|     return out; | ||||
|   } | ||||
|  | ||||
|   void dispose() { | ||||
|     pagingController.dispose(); | ||||
|   } | ||||
| } | ||||
| @@ -1,10 +1,7 @@ | ||||
| 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/controllers/post_list_controller.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/content/posts.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/account/notification.dart'; | ||||
| import 'package:solian/theme.dart'; | ||||
| @@ -22,48 +19,21 @@ class HomeScreen extends StatefulWidget { | ||||
|  | ||||
| class _HomeScreenState extends State<HomeScreen> | ||||
|     with SingleTickerProviderStateMixin { | ||||
|   final PagingController<int, Post> _pagingController = | ||||
|       PagingController(firstPageKey: 0); | ||||
|   final PostListController _postController = PostListController(); | ||||
|  | ||||
|   late final TabController _tabController; | ||||
|  | ||||
|   int mode = 0; | ||||
|  | ||||
|   getPosts(int pageKey) async { | ||||
|     final PostProvider provider = Get.find(); | ||||
|  | ||||
|     Response resp; | ||||
|     try { | ||||
|       resp = await provider.listRecommendations( | ||||
|         pageKey, | ||||
|         channel: mode == 0 ? null : 'shuffle', | ||||
|       ); | ||||
|     } catch (e) { | ||||
|       _pagingController.error = e; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _pagingController.addPageRequestListener(getPosts); | ||||
|     _tabController = TabController(length: 2, vsync: this); | ||||
|     _tabController.addListener(() { | ||||
|       switch (_tabController.index) { | ||||
|         case 0: | ||||
|         case 1: | ||||
|           if (mode == _tabController.index) break; | ||||
|           mode = _tabController.index; | ||||
|           _pagingController.refresh(); | ||||
|           if (_postController.mode.value == _tabController.index) break; | ||||
|           _postController.mode.value = _tabController.index; | ||||
|           _postController.reloadAllOver(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| @@ -84,46 +54,45 @@ class _HomeScreenState extends State<HomeScreen> | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|         body: RefreshIndicator( | ||||
|           onRefresh: () => Future.sync(() => _pagingController.refresh()), | ||||
|           child: NestedScrollView( | ||||
|             headerSliverBuilder: | ||||
|                 (BuildContext context, bool innerBoxIsScrolled) { | ||||
|               return [ | ||||
|                 SliverAppBar( | ||||
|                   title: AppBarTitle('home'.tr), | ||||
|                   centerTitle: false, | ||||
|                   floating: true, | ||||
|                   toolbarHeight: SolianTheme.toolbarHeight(context), | ||||
|                   leading: AppBarLeadingButton.adaptive(context), | ||||
|                   actions: [ | ||||
|                     const BackgroundStateWidget(), | ||||
|                     const NotificationButton(), | ||||
|                     SizedBox( | ||||
|                       width: SolianTheme.isLargeScreen(context) ? 8 : 16, | ||||
|                     ), | ||||
|                   ], | ||||
|                   bottom: TabBar( | ||||
|                     controller: _tabController, | ||||
|                     tabs: [ | ||||
|                       Tab(text: 'postListNews'.tr), | ||||
|                       Tab(text: 'postListShuffle'.tr), | ||||
|                     ], | ||||
|         body: NestedScrollView( | ||||
|           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||
|             return [ | ||||
|               SliverAppBar( | ||||
|                 title: AppBarTitle('home'.tr), | ||||
|                 centerTitle: false, | ||||
|                 floating: true, | ||||
|                 toolbarHeight: SolianTheme.toolbarHeight(context), | ||||
|                 leading: AppBarLeadingButton.adaptive(context), | ||||
|                 actions: [ | ||||
|                   const BackgroundStateWidget(), | ||||
|                   const NotificationButton(), | ||||
|                   SizedBox( | ||||
|                     width: SolianTheme.isLargeScreen(context) ? 8 : 16, | ||||
|                   ), | ||||
|                 ) | ||||
|               ]; | ||||
|             }, | ||||
|             body: TabBarView( | ||||
|               controller: _tabController, | ||||
|               children: [ | ||||
|                 CustomScrollView(slivers: [ | ||||
|                   FeedListWidget(controller: _pagingController), | ||||
|                 ], | ||||
|                 bottom: TabBar( | ||||
|                   controller: _tabController, | ||||
|                   tabs: [ | ||||
|                     Tab(text: 'postListNews'.tr), | ||||
|                     Tab(text: 'postListShuffle'.tr), | ||||
|                   ], | ||||
|                 ), | ||||
|               ) | ||||
|             ]; | ||||
|           }, | ||||
|           body: TabBarView( | ||||
|             controller: _tabController, | ||||
|             children: [ | ||||
|               RefreshIndicator( | ||||
|                 onRefresh: () => _postController.reloadAllOver(), | ||||
|                 child: CustomScrollView(slivers: [ | ||||
|                   FeedListWidget(controller: _postController.pagingController), | ||||
|                 ]), | ||||
|                 CustomScrollView(slivers: [ | ||||
|                   FeedListWidget(controller: _pagingController), | ||||
|                 ]), | ||||
|               ], | ||||
|             ), | ||||
|               ), | ||||
|               CustomScrollView(slivers: [ | ||||
|                 FeedListWidget(controller: _postController.pagingController), | ||||
|               ]), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
| @@ -132,7 +101,7 @@ class _HomeScreenState extends State<HomeScreen> | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _pagingController.dispose(); | ||||
|     _postController.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -105,7 +105,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> { | ||||
|   void syncWidget() { | ||||
|     if (widget.edit != null) { | ||||
|       _contentController.text = widget.edit!.body['content']; | ||||
|       _attachments = widget.edit!.body['attachments'] ?? List.empty(); | ||||
|       _attachments = widget.edit!.body['attachments']?.cast<int>() ?? List.empty(); | ||||
|       _isDraft = widget.edit!.isDraft ?? false; | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -95,7 +95,7 @@ const messagesEnglish = { | ||||
|   'postEdited': 'Edited at @date', | ||||
|   'postNewCreated': 'Created at @date', | ||||
|   'postAttachmentTip': '@count attachment(s)', | ||||
|   'postInRealm': 'In realm @realm', | ||||
|   'postInRealm': 'In @realm', | ||||
|   'postDetail': 'Post', | ||||
|   'postReplies': 'Replies', | ||||
|   'postPublish': 'Post a post', | ||||
|   | ||||
| @@ -86,7 +86,7 @@ class _PostItemState extends State<PostItem> { | ||||
|     } | ||||
|     if (widget.item.realm != null) { | ||||
|       labels.add('postInRealm'.trParams({ | ||||
|         'realm': '#${widget.item.realm!.id}', | ||||
|         'realm': widget.item.realm!.alias, | ||||
|       })); | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user