✨ Existing friends management
This commit is contained in:
		| @@ -10,6 +10,7 @@ | ||||
|   "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 Networks!", | ||||
|   "signOut": "Sign Out", | ||||
|   "poweredBy": "Powered by Project Hydrogen", | ||||
|   "copyright": "Copyright © 2024 Solsynth LLC", | ||||
|   "confirmation": "Confirmation", | ||||
| @@ -30,6 +31,11 @@ | ||||
|   "notifyDone": "You're done!", | ||||
|   "notifyDoneCaption": "There are no notifications unread for you.", | ||||
|   "notifyListHint": "Pull to refresh, swipe to dismiss", | ||||
|   "friend": "Friend", | ||||
|   "friendPending": "Pending", | ||||
|   "friendActive": "Active", | ||||
|   "friendBlocked": "Blocked", | ||||
|   "friendListHint": "Swipe left to decline, swipe right to approve", | ||||
|   "reaction": "Reaction", | ||||
|   "reactVerb": "React", | ||||
|   "post": "Post", | ||||
|   | ||||
| @@ -4,12 +4,13 @@ | ||||
|   "chat": "聊天", | ||||
|   "account": "账号", | ||||
|   "riskDetection": "风险监测", | ||||
|   "signIn": "登陆", | ||||
|   "signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!", | ||||
|   "signInRequired": "请先登陆", | ||||
|   "signInRiskDetected": "检测到风险,点击下一步按钮来打开一个网页,并通过在其上面登陆来通过安全检查。", | ||||
|   "signIn": "登录", | ||||
|   "signInCaption": "登录以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!", | ||||
|   "signInRequired": "请先登录", | ||||
|   "signInRiskDetected": "检测到风险,点击下一步按钮来打开一个网页,并通过在其上面登录来通过安全检查。", | ||||
|   "signUp": "注册", | ||||
|   "signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!", | ||||
|   "signOut": "登出", | ||||
|   "poweredBy": "由 Project Hydrogen 强力驱动", | ||||
|   "copyright": "2024 Solsynth LLC © 版权所有", | ||||
|   "confirmation": "确认", | ||||
| @@ -30,6 +31,11 @@ | ||||
|   "notifyDone": "所有通知已读!", | ||||
|   "notifyDoneCaption": "这里没有什么东西可以给你看的了~", | ||||
|   "notifyListHint": "下拉以刷新,左滑来已读", | ||||
|   "friend": "好友", | ||||
|   "friendPending": "请求中", | ||||
|   "friendActive": "活跃的好友", | ||||
|   "friendBlocked": "封锁中", | ||||
|   "friendListHint": "左滑来拒绝,右滑来接受", | ||||
|   "reaction": "反应", | ||||
|   "reactVerb": "作出反应", | ||||
|   "post": "帖子", | ||||
|   | ||||
| @@ -8,9 +8,9 @@ class Account { | ||||
|   String avatar; | ||||
|   String banner; | ||||
|   String description; | ||||
|   String emailAddress; | ||||
|   String? emailAddress; | ||||
|   int powerLevel; | ||||
|   int externalId; | ||||
|   int? externalId; | ||||
|  | ||||
|   Account({ | ||||
|     required this.id, | ||||
| @@ -22,9 +22,9 @@ class Account { | ||||
|     required this.avatar, | ||||
|     required this.banner, | ||||
|     required this.description, | ||||
|     required this.emailAddress, | ||||
|     this.emailAddress, | ||||
|     required this.powerLevel, | ||||
|     required this.externalId, | ||||
|     this.externalId, | ||||
|   }); | ||||
|  | ||||
|   factory Account.fromJson(Map<String, dynamic> json) => Account( | ||||
|   | ||||
							
								
								
									
										53
									
								
								lib/models/friendship.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/models/friendship.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| class Friendship { | ||||
|   int id; | ||||
|   DateTime createdAt; | ||||
|   DateTime updatedAt; | ||||
|   DateTime? deletedAt; | ||||
|   int accountId; | ||||
|   int relatedId; | ||||
|   int? blockedBy; | ||||
|   Account account; | ||||
|   Account related; | ||||
|   int status; | ||||
|  | ||||
|   Friendship({ | ||||
|     required this.id, | ||||
|     required this.createdAt, | ||||
|     required this.updatedAt, | ||||
|     this.deletedAt, | ||||
|     required this.accountId, | ||||
|     required this.relatedId, | ||||
|     this.blockedBy, | ||||
|     required this.account, | ||||
|     required this.related, | ||||
|     required this.status, | ||||
|   }); | ||||
|  | ||||
|   factory Friendship.fromJson(Map<String, dynamic> json) => Friendship( | ||||
|     id: json["id"], | ||||
|     createdAt: DateTime.parse(json["created_at"]), | ||||
|     updatedAt: DateTime.parse(json["updated_at"]), | ||||
|     deletedAt: json["deleted_at"], | ||||
|     accountId: json["account_id"], | ||||
|     relatedId: json["related_id"], | ||||
|     blockedBy: json["blocked_by"], | ||||
|     account: Account.fromJson(json["account"]), | ||||
|     related: Account.fromJson(json["related"]), | ||||
|     status: json["status"], | ||||
|   ); | ||||
|  | ||||
|   Map<String, dynamic> toJson() => { | ||||
|     "id": id, | ||||
|     "created_at": createdAt.toIso8601String(), | ||||
|     "updated_at": updatedAt.toIso8601String(), | ||||
|     "deleted_at": deletedAt, | ||||
|     "account_id": accountId, | ||||
|     "related_id": relatedId, | ||||
|     "blocked_by": blockedBy, | ||||
|     "account": account.toJson(), | ||||
|     "related": related.toJson(), | ||||
|     "status": status, | ||||
|   }; | ||||
| } | ||||
| @@ -50,6 +50,11 @@ class NotifyProvider extends ChangeNotifier { | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   void clearAt(int index) { | ||||
|     notifications.removeAt(index); | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   void clearNonRealtime() { | ||||
|     notifications = notifications.where((x) => !x.isRealtime).toList(); | ||||
|   } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/screens/account.dart'; | ||||
| import 'package:solian/screens/account/friend.dart'; | ||||
| import 'package:solian/screens/chat/chat.dart'; | ||||
| import 'package:solian/screens/chat/index.dart'; | ||||
| import 'package:solian/screens/chat/manage.dart'; | ||||
| @@ -20,6 +21,11 @@ final router = GoRouter( | ||||
|       name: 'explore', | ||||
|       builder: (context, state) => const ExploreScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/notification', | ||||
|       name: 'notification', | ||||
|       builder: (context, state) => const NotificationScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat', | ||||
|       name: 'chat', | ||||
| @@ -66,15 +72,15 @@ final router = GoRouter( | ||||
|         dataset: state.pathParameters['dataset'] as String, | ||||
|       ), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/notification', | ||||
|       name: 'notification', | ||||
|       builder: (context, state) => const NotificationScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/auth/sign-in', | ||||
|       name: 'auth.sign-in', | ||||
|       builder: (context, state) => SignInScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/account/friend', | ||||
|       name: 'account.friend', | ||||
|       builder: (context, state) => const FriendScreen(), | ||||
|     ), | ||||
|   ], | ||||
| ); | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/common_wrapper.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class AccountScreen extends StatefulWidget { | ||||
|   const AccountScreen({super.key}); | ||||
| @@ -40,21 +40,25 @@ class _AccountScreenState extends State<AccountScreen> { | ||||
|                   padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24), | ||||
|                   child: NameCard(), | ||||
|                 ), | ||||
|                 InkWell( | ||||
|                   child: const Padding( | ||||
|                     padding: EdgeInsets.symmetric(horizontal: 18), | ||||
|                     child: ListTile( | ||||
|                       leading: Icon(Icons.logout), | ||||
|                       title: Text("Sign out"), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ListTile( | ||||
|                   contentPadding: const EdgeInsets.symmetric(horizontal: 34), | ||||
|                   leading: const Icon(Icons.diversity_1), | ||||
|                   title: Text(AppLocalizations.of(context)!.friend), | ||||
|                   onTap: () { | ||||
|                     router.goNamed('account.friend'); | ||||
|                   }, | ||||
|                 ), | ||||
|                 ListTile( | ||||
|                   contentPadding: const EdgeInsets.symmetric(horizontal: 34), | ||||
|                   leading: const Icon(Icons.logout), | ||||
|                   title: Text(AppLocalizations.of(context)!.signOut), | ||||
|                   onTap: () { | ||||
|                     auth.signoff(); | ||||
|                     setState(() { | ||||
|                       isAuthorized = false; | ||||
|                     }); | ||||
|                   }, | ||||
|                 ) | ||||
|                 ), | ||||
|               ], | ||||
|             ) | ||||
|           : Center( | ||||
| @@ -126,8 +130,7 @@ class NameCard extends StatelessWidget { | ||||
|             children: [ | ||||
|               FutureBuilder( | ||||
|                 future: renderAvatar(context), | ||||
|                 builder: | ||||
|                     (BuildContext context, AsyncSnapshot<Widget> snapshot) { | ||||
|                 builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) { | ||||
|                   if (snapshot.hasData) { | ||||
|                     return snapshot.data!; | ||||
|                   } else { | ||||
| @@ -138,8 +141,7 @@ class NameCard extends StatelessWidget { | ||||
|               const SizedBox(width: 20), | ||||
|               FutureBuilder( | ||||
|                 future: renderLabel(context), | ||||
|                 builder: | ||||
|                     (BuildContext context, AsyncSnapshot<Column> snapshot) { | ||||
|                 builder: (BuildContext context, AsyncSnapshot<Column> snapshot) { | ||||
|                   if (snapshot.hasData) { | ||||
|                     return snapshot.data!; | ||||
|                   } else { | ||||
| @@ -161,12 +163,7 @@ class ActionCard extends StatelessWidget { | ||||
|   final String caption; | ||||
|   final Function onTap; | ||||
|  | ||||
|   const ActionCard( | ||||
|       {super.key, | ||||
|       required this.onTap, | ||||
|       required this.title, | ||||
|       required this.caption, | ||||
|       required this.icon}); | ||||
|   const ActionCard({super.key, required this.onTap, required this.title, required this.caption, required this.icon}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|   | ||||
							
								
								
									
										218
									
								
								lib/screens/account/friend.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								lib/screens/account/friend.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/friendship.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class FriendScreen extends StatefulWidget { | ||||
|   const FriendScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<FriendScreen> createState() => _FriendScreenState(); | ||||
| } | ||||
|  | ||||
| class _FriendScreenState extends State<FriendScreen> { | ||||
|   bool _isSubmitting = false; | ||||
|  | ||||
|   int _currentSideId = 0; | ||||
|   List<Friendship> _friendships = List.empty(); | ||||
|  | ||||
|   Future<void> fetchFriendships() async { | ||||
|     final auth = context.read<AuthProvider>(); | ||||
|     final prof = await auth.getProfiles(); | ||||
|     if (!await auth.isAuthorized()) return; | ||||
|  | ||||
|     _currentSideId = prof['id']; | ||||
|  | ||||
|     var uri = getRequestUri('passport', '/api/users/me/friends'); | ||||
|  | ||||
|     var res = await auth.client!.get(uri); | ||||
|     if (res.statusCode == 200) { | ||||
|       final result = jsonDecode(utf8.decode(res.bodyBytes)) as List<dynamic>; | ||||
|       setState(() { | ||||
|         _friendships = result.map((x) => Friendship.fromJson(x)).toList(); | ||||
|       }); | ||||
|     } else { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
|       ScaffoldMessenger.of(context).showSnackBar( | ||||
|         SnackBar(content: Text("Something went wrong... $message")), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> updateFriendship(Friendship relation, int status) async { | ||||
|     setState(() => _isSubmitting = true); | ||||
|  | ||||
|     final otherside = getOtherside(relation); | ||||
|  | ||||
|     final auth = context.read<AuthProvider>(); | ||||
|     if (!await auth.isAuthorized()) { | ||||
|       setState(() => _isSubmitting = false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     var res = await auth.client!.put( | ||||
|       getRequestUri('passport', '/api/users/me/friends/${otherside.id}'), | ||||
|       headers: <String, String>{ | ||||
|         'Content-Type': 'application/json', | ||||
|       }, | ||||
|       body: jsonEncode(<String, dynamic>{ | ||||
|         'status': status, | ||||
|       }), | ||||
|     ); | ||||
|     if (res.statusCode == 200) { | ||||
|       await fetchFriendships(); | ||||
|     } else { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
|       ScaffoldMessenger.of(context).showSnackBar( | ||||
|         SnackBar(content: Text("Something went wrong... $message")), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     setState(() => _isSubmitting = false); | ||||
|   } | ||||
|  | ||||
|   List<Friendship> filterWithStatus(int status) { | ||||
|     return _friendships.where((x) => x.status == status).toList(); | ||||
|   } | ||||
|  | ||||
|   DismissDirection getDismissDirection(Friendship relation) { | ||||
|     if (relation.status == 2) return DismissDirection.endToStart; | ||||
|     if (relation.status == 1) return DismissDirection.startToEnd; | ||||
|     if (relation.status == 0 && relation.relatedId != _currentSideId) return DismissDirection.startToEnd; | ||||
|     return DismissDirection.horizontal; | ||||
|   } | ||||
|  | ||||
|   Account getOtherside(Friendship relation) { | ||||
|     if (relation.accountId != _currentSideId) { | ||||
|       return relation.account; | ||||
|     } else { | ||||
|       return relation.related; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   String getAvatarUrl(String uuid) { | ||||
|     return getRequestUri('passport', '/api/avatar/$uuid').toString(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|  | ||||
|     Future.delayed(Duration.zero, () { | ||||
|       fetchFriendships(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     Widget friendshipTileBuilder(context, index, status) { | ||||
|       final element = filterWithStatus(status)[index]; | ||||
|       final otherside = getOtherside(element); | ||||
|  | ||||
|       final randomId = DateTime.now().microsecondsSinceEpoch >> 10; | ||||
|  | ||||
|       return Dismissible( | ||||
|         key: Key(randomId.toString()), | ||||
|         background: Container( | ||||
|           color: Colors.red, | ||||
|           padding: const EdgeInsets.symmetric(horizontal: 20), | ||||
|           alignment: Alignment.centerLeft, | ||||
|           child: const Icon(Icons.close, color: Colors.white), | ||||
|         ), | ||||
|         secondaryBackground: Container( | ||||
|           color: Colors.green, | ||||
|           padding: const EdgeInsets.symmetric(horizontal: 20), | ||||
|           alignment: Alignment.centerRight, | ||||
|           child: const Icon(Icons.check, color: Colors.white), | ||||
|         ), | ||||
|         direction: getDismissDirection(element), | ||||
|         child: ListTile( | ||||
|           title: Text(otherside.nick), | ||||
|           subtitle: Text(otherside.name), | ||||
|           leading: CircleAvatar( | ||||
|             backgroundImage: NetworkImage(getAvatarUrl(otherside.avatar)), | ||||
|           ), | ||||
|         ), | ||||
|         onDismissed: (direction) { | ||||
|           if (direction == DismissDirection.startToEnd) { | ||||
|             updateFriendship(element, 2); | ||||
|           } | ||||
|           if (direction == DismissDirection.endToStart) { | ||||
|             updateFriendship(element, 1); | ||||
|           } | ||||
|         }, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return IndentWrapper( | ||||
|       title: AppLocalizations.of(context)!.friend, | ||||
|       child: RefreshIndicator( | ||||
|         onRefresh: () => fetchFriendships(), | ||||
|         child: CustomScrollView( | ||||
|           slivers: [ | ||||
|             SliverToBoxAdapter( | ||||
|               child: _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), | ||||
|             ), | ||||
|             SliverToBoxAdapter( | ||||
|               child: Container( | ||||
|                 padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), | ||||
|                 color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), | ||||
|                 child: Text(AppLocalizations.of(context)!.friendPending), | ||||
|               ), | ||||
|             ), | ||||
|             SliverList.builder( | ||||
|               itemCount: filterWithStatus(0).length, | ||||
|               itemBuilder: (_, __) => friendshipTileBuilder(_, __, 0), | ||||
|             ), | ||||
|             SliverToBoxAdapter( | ||||
|               child: Container( | ||||
|                 padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), | ||||
|                 color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), | ||||
|                 child: Text(AppLocalizations.of(context)!.friendActive), | ||||
|               ), | ||||
|             ), | ||||
|             SliverList.builder( | ||||
|               itemCount: filterWithStatus(1).length, | ||||
|               itemBuilder: (_, __) => friendshipTileBuilder(_, __, 1), | ||||
|             ), | ||||
|             SliverToBoxAdapter( | ||||
|               child: Container( | ||||
|                 padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), | ||||
|                 color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), | ||||
|                 child: Text(AppLocalizations.of(context)!.friendBlocked), | ||||
|               ), | ||||
|             ), | ||||
|             SliverList.builder( | ||||
|               itemCount: filterWithStatus(2).length, | ||||
|               itemBuilder: (_, __) => friendshipTileBuilder(_, __, 2), | ||||
|             ), | ||||
|             SliverToBoxAdapter( | ||||
|               child: Container( | ||||
|                 decoration: BoxDecoration( | ||||
|                   border: Border( | ||||
|                       top: BorderSide( | ||||
|                     color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), | ||||
|                     width: 0.3, | ||||
|                   )), | ||||
|                 ), | ||||
|                 padding: const EdgeInsets.only(top: 16), | ||||
|                 child: Text( | ||||
|                   AppLocalizations.of(context)!.friendListHint, | ||||
|                   textAlign: TextAlign.center, | ||||
|                   style: Theme.of(context).textTheme.bodySmall, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -83,26 +83,21 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|         onRefresh: () => Future.sync( | ||||
|           () => _pagingController.refresh(), | ||||
|         ), | ||||
|         child: Center( | ||||
|           child: Container( | ||||
|             constraints: const BoxConstraints(maxWidth: 640), | ||||
|             child: PagedListView<int, Post>( | ||||
|               pagingController: _pagingController, | ||||
|               builderDelegate: PagedChildBuilderDelegate<Post>( | ||||
|                 itemBuilder: (context, item, index) => PostItem( | ||||
|                   item: item, | ||||
|                   onUpdate: () => _pagingController.refresh(), | ||||
|                   onTap: () { | ||||
|                     router.pushNamed( | ||||
|                       'posts.screen', | ||||
|                       pathParameters: { | ||||
|                         'alias': item.alias, | ||||
|                         'dataset': '${item.modelType}s', | ||||
|                       }, | ||||
|                     ); | ||||
|         child: PagedListView<int, Post>( | ||||
|           pagingController: _pagingController, | ||||
|           builderDelegate: PagedChildBuilderDelegate<Post>( | ||||
|             itemBuilder: (context, item, index) => PostItem( | ||||
|               item: item, | ||||
|               onUpdate: () => _pagingController.refresh(), | ||||
|               onTap: () { | ||||
|                 router.pushNamed( | ||||
|                   'posts.screen', | ||||
|                   pathParameters: { | ||||
|                     'alias': item.alias, | ||||
|                     'dataset': '${item.modelType}s', | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|                         index: index, | ||||
|                         item: element, | ||||
|                         onDismiss: () => setState(() { | ||||
|                           nty.notifications.removeAt(index); | ||||
|                           nty.clearAt(index); | ||||
|                         }), | ||||
|                       ); | ||||
|                     }, | ||||
|   | ||||
| @@ -23,12 +23,10 @@ class PostScreen extends StatefulWidget { | ||||
| class _PostScreenState extends State<PostScreen> { | ||||
|   final _client = http.Client(); | ||||
|  | ||||
|   final PagingController<int, Post> _commentPagingController = | ||||
|       PagingController(firstPageKey: 0); | ||||
|   final PagingController<int, Post> _commentPagingController = PagingController(firstPageKey: 0); | ||||
|  | ||||
|   Future<Post?> fetchPost(BuildContext context) async { | ||||
|     final uri = getRequestUri( | ||||
|         'interactive', '/api/p/${widget.dataset}/${widget.alias}'); | ||||
|     final uri = getRequestUri('interactive', '/api/p/${widget.dataset}/${widget.alias}'); | ||||
|     final res = await _client.get(uri); | ||||
|     if (res.statusCode != 200) { | ||||
|       final err = utf8.decode(res.bodyBytes); | ||||
| @@ -51,32 +49,27 @@ class _PostScreenState extends State<PostScreen> { | ||||
|         future: fetchPost(context), | ||||
|         builder: (context, snapshot) { | ||||
|           if (snapshot.hasData && snapshot.data != null) { | ||||
|             return Center( | ||||
|               child: Container( | ||||
|                 constraints: const BoxConstraints(maxWidth: 640), | ||||
|                 child: CustomScrollView( | ||||
|                   slivers: [ | ||||
|                     SliverToBoxAdapter( | ||||
|                       child: PostItem( | ||||
|                         item: snapshot.data!, | ||||
|                         brief: false, | ||||
|                         ripple: false, | ||||
|                       ), | ||||
|                     ), | ||||
|                     SliverToBoxAdapter( | ||||
|                       child: CommentListHeader( | ||||
|                         related: snapshot.data!, | ||||
|                         paging: _commentPagingController, | ||||
|                       ), | ||||
|                     ), | ||||
|                     CommentList( | ||||
|                       related: snapshot.data!, | ||||
|                       dataset: widget.dataset, | ||||
|                       paging: _commentPagingController, | ||||
|                     ), | ||||
|                   ], | ||||
|             return CustomScrollView( | ||||
|               slivers: [ | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: PostItem( | ||||
|                     item: snapshot.data!, | ||||
|                     brief: false, | ||||
|                     ripple: false, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|                 SliverToBoxAdapter( | ||||
|                   child: CommentListHeader( | ||||
|                     related: snapshot.data!, | ||||
|                     paging: _commentPagingController, | ||||
|                   ), | ||||
|                 ), | ||||
|                 CommentList( | ||||
|                   related: snapshot.data!, | ||||
|                   dataset: widget.dataset, | ||||
|                   paging: _commentPagingController, | ||||
|                 ), | ||||
|               ], | ||||
|             ); | ||||
|           } else { | ||||
|             return const Center( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user