✨ Channel detail
This commit is contained in:
		| @@ -1,8 +1,10 @@ | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/screens/account.dart'; | ||||
| import 'package:solian/screens/account/friend.dart'; | ||||
| import 'package:solian/screens/account/personalize.dart'; | ||||
| import 'package:solian/screens/channel/channel_chat.dart'; | ||||
| import 'package:solian/screens/channel/channel_detail.dart'; | ||||
| import 'package:solian/screens/channel/channel_organize.dart'; | ||||
| import 'package:solian/screens/contact.dart'; | ||||
| import 'package:solian/screens/posts/post_detail.dart'; | ||||
| @@ -64,6 +66,20 @@ abstract class AppRouter { | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       ShellRoute( | ||||
|         builder: (context, state, child) => | ||||
|             BasicShell(state: state, child: child), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/chat/:alias/detail', | ||||
|             name: 'channelDetail', | ||||
|             builder: (context, state) => ChannelDetailScreen( | ||||
|               channel: state.extra as Channel, | ||||
|               realm: state.uri.queryParameters['realm'] ?? 'global', | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/posts/publish', | ||||
|         name: 'postPublishing', | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| import 'package:solian/providers/content/channel.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| import 'package:solian/theme.dart'; | ||||
| import 'package:solian/widgets/chat/chat_message.dart'; | ||||
| @@ -33,6 +34,7 @@ class ChannelChatScreen extends StatefulWidget { | ||||
|  | ||||
| class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|   bool _isBusy = false; | ||||
|   String? _overrideAlias; | ||||
|  | ||||
|   Channel? _channel; | ||||
|   StreamSubscription<NetworkPackage>? _subscription; | ||||
| @@ -40,13 +42,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|   final PagingController<int, Message> _pagingController = | ||||
|       PagingController(firstPageKey: 0); | ||||
|  | ||||
|   getChannel() async { | ||||
|   getChannel({String? overrideAlias}) async { | ||||
|     final ChannelProvider provider = Get.find(); | ||||
|  | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     if (overrideAlias != null) { | ||||
|       _overrideAlias = overrideAlias; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       final resp = await provider.getChannel(widget.alias, realm: widget.realm); | ||||
|       final resp = await provider.getChannel( | ||||
|         _overrideAlias ?? widget.alias, | ||||
|         realm: widget.realm, | ||||
|       ); | ||||
|       setState(() => _channel = Channel.fromJson(resp.body)); | ||||
|     } catch (e) { | ||||
|       context.showErrorDialog(e); | ||||
| @@ -184,8 +193,23 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|         centerTitle: false, | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|             icon: const Icon(Icons.settings), | ||||
|             onPressed: () {}, | ||||
|             icon: const Icon(Icons.more_vert), | ||||
|             onPressed: () { | ||||
|               AppRouter.instance | ||||
|                   .pushNamed( | ||||
|                 'channelDetail', | ||||
|                 pathParameters: {'alias': widget.alias}, | ||||
|                 queryParameters: {'realm': widget.realm}, | ||||
|                 extra: _channel, | ||||
|               ) | ||||
|                   .then((value) { | ||||
|                 if (value == false) AppRouter.instance.pop(); | ||||
|                 if (value != null) { | ||||
|                   final resp = Channel.fromJson(value as Map<String, dynamic>); | ||||
|                   getChannel(overrideAlias: resp.alias); | ||||
|                 } | ||||
|               }); | ||||
|             }, | ||||
|           ), | ||||
|           SizedBox( | ||||
|             width: SolianTheme.isLargeScreen(context) ? 8 : 16, | ||||
|   | ||||
							
								
								
									
										142
									
								
								lib/screens/channel/channel_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								lib/screens/channel/channel_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/channel/channel_organize.dart'; | ||||
| import 'package:solian/widgets/channel/channel_deletion.dart'; | ||||
|  | ||||
| class ChannelDetailScreen extends StatefulWidget { | ||||
|   final String realm; | ||||
|   final Channel channel; | ||||
|  | ||||
|   const ChannelDetailScreen({ | ||||
|     super.key, | ||||
|     required this.channel, | ||||
|     required this.realm, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<ChannelDetailScreen> createState() => _ChannelDetailScreenState(); | ||||
| } | ||||
|  | ||||
| class _ChannelDetailScreenState extends State<ChannelDetailScreen> { | ||||
|   bool _isOwned = false; | ||||
|  | ||||
|   void checkOwner() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     final prof = await auth.getProfile(); | ||||
|     setState(() { | ||||
|       _isOwned = prof.body['id'] == widget.channel.account.externalId; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void promptLeaveChannel() async { | ||||
|     final did = await showDialog( | ||||
|       context: context, | ||||
|       builder: (context) => ChannelDeletion( | ||||
|         channel: widget.channel, | ||||
|         realm: widget.realm, | ||||
|         isOwned: _isOwned, | ||||
|       ), | ||||
|     ); | ||||
|     if (did == true && AppRouter.instance.canPop()) { | ||||
|       AppRouter.instance.pop(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|  | ||||
|     checkOwner(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ownerActions = [ | ||||
|       ListTile( | ||||
|         leading: const Icon(Icons.edit), | ||||
|         title: Text('channelAdjust'.tr), | ||||
|         onTap: () async { | ||||
|           AppRouter.instance | ||||
|               .pushNamed( | ||||
|             'channelOrganizing', | ||||
|             extra: ChannelOrganizeArguments(edit: widget.channel), | ||||
|             queryParameters: | ||||
|                 widget.realm != 'global' ? {'realm': widget.realm} : {}, | ||||
|           ) | ||||
|               .then((resp) { | ||||
|             if (resp != null) { | ||||
|               AppRouter.instance.pop(resp); | ||||
|             } | ||||
|           }); | ||||
|         }, | ||||
|       ), | ||||
|     ]; | ||||
|  | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Padding( | ||||
|           padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), | ||||
|           child: Row( | ||||
|             children: [ | ||||
|               CircleAvatar( | ||||
|                 radius: 28, | ||||
|                 backgroundColor: Colors.teal, | ||||
|                 child: FaIcon( | ||||
|                   widget.channel.icon, | ||||
|                   color: Colors.white, | ||||
|                   size: 18, | ||||
|                 ), | ||||
|               ), | ||||
|               const SizedBox(width: 16), | ||||
|               Expanded( | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Text(widget.channel.name, | ||||
|                         style: Theme.of(context).textTheme.bodyLarge), | ||||
|                     Text(widget.channel.description, | ||||
|                         style: Theme.of(context).textTheme.bodySmall), | ||||
|                     Text( | ||||
|                       '#${widget.channel.id.toString().padLeft(8, '0')} · ${widget.channel.alias}', | ||||
|                       style: const TextStyle(fontSize: 11), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ) | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|         const Divider(thickness: 0.3), | ||||
|         Expanded( | ||||
|           child: ListView( | ||||
|             children: [ | ||||
|               ListTile( | ||||
|                 leading: const Icon(Icons.settings), | ||||
|                 title: Text('channelSettings'.tr), | ||||
|                 onTap: () {}, | ||||
|               ), | ||||
|               ListTile( | ||||
|                 leading: const Icon(Icons.supervisor_account), | ||||
|                 title: Text('channelMembers'.tr), | ||||
|                 onTap: () {}, | ||||
|               ), | ||||
|               ...(_isOwned ? ownerActions : List.empty()), | ||||
|               const Divider(thickness: 0.3), | ||||
|               ListTile( | ||||
|                 leading: _isOwned | ||||
|                     ? const Icon(Icons.delete) | ||||
|                     : const Icon(Icons.exit_to_app), | ||||
|                 title: Text(_isOwned ? 'delete'.tr : 'leave'.tr), | ||||
|                 onTap: () => promptLeaveChannel(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -227,7 +227,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> { | ||||
|                 ).paddingSymmetric(horizontal: 16, vertical: 12), | ||||
|               ), | ||||
|               const Divider(thickness: 0.3), | ||||
|               if (_channelType == 1) | ||||
|               if (_channelType == 1 && widget.edit == null) | ||||
|                 ListTile( | ||||
|                   leading: const Icon(Icons.supervisor_account) | ||||
|                       .paddingSymmetric(horizontal: 8), | ||||
| @@ -250,6 +250,8 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> { | ||||
|                     isExpanded: true, | ||||
|                     items: channelTypes.entries | ||||
|                         .map((item) => DropdownMenuItem<int>( | ||||
|                               enabled: widget.edit == null || | ||||
|                                   item.key == widget.edit?.type, | ||||
|                               value: item.key, | ||||
|                               child: Text( | ||||
|                                 item.value, | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class SolianMessages extends Translations { | ||||
|           'apply': 'Apply', | ||||
|           'cancel': 'Cancel', | ||||
|           'confirm': 'Confirm', | ||||
|           'leave': 'Leave', | ||||
|           'loading': 'Loading...', | ||||
|           'edit': 'Edit', | ||||
|           'delete': 'Delete', | ||||
| @@ -79,7 +80,7 @@ class SolianMessages extends Translations { | ||||
|           'postRepostingNotify': 'You\'re reposting a post from @username.', | ||||
|           'postDeletionConfirm': 'Confirm post deletion', | ||||
|           'postDeletionConfirmCaption': | ||||
|               'Are your sure to delete post "@content"? this action cannot be undone!', | ||||
|               'Are your sure to delete post "@content"? This action cannot be undone!', | ||||
|           'reactAdd': 'React', | ||||
|           'reactCompleted': 'Your reaction has been added', | ||||
|           'reactUncompleted': 'Your reaction has been removed', | ||||
| @@ -98,9 +99,16 @@ class SolianMessages extends Translations { | ||||
|           'channelDescription': 'Description', | ||||
|           'channelEncrypted': 'Encrypted Channel', | ||||
|           'channelMember': 'Channel member', | ||||
|           'channelMembers': 'Channel members', | ||||
|           'channelType': 'Channel type', | ||||
|           'channelTypeCommon': 'Regular', | ||||
|           'channelTypeDirect': 'DM', | ||||
|           'channelAdjust': 'Channel Adjustment', | ||||
|           'channelDetail': 'Channel Detail', | ||||
|           'channelSettings': 'Channel settings', | ||||
|           'channelDeletionConfirm': 'Confirm channel deletion', | ||||
|           'channelDeletionConfirmCaption': | ||||
|               'Are you sure to delete channel @channel? This action cannot be undone!', | ||||
|           'messageDecoding': 'Decoding...', | ||||
|           'messageDecodeFailed': 'Unable to decode: @message', | ||||
|           'messageInputPlaceholder': 'Message @channel...', | ||||
| @@ -112,6 +120,7 @@ class SolianMessages extends Translations { | ||||
|           'reset': '重置', | ||||
|           'cancel': '取消', | ||||
|           'confirm': '确认', | ||||
|           'leave': '离开', | ||||
|           'loading': '载入中…', | ||||
|           'edit': '编辑', | ||||
|           'delete': '删除', | ||||
| @@ -174,7 +183,7 @@ class SolianMessages extends Translations { | ||||
|           'postReplyingNotify': '你正在回一个来自 @username 的帖子', | ||||
|           'postRepostingNotify': '你正在转一个来自 @username 的帖子', | ||||
|           'postDeletionConfirm': '确认删除帖子', | ||||
|           'postDeletionConfirmCaption': '你确定要删除帖子 “@content” 吗?该操作不可不可撤销。', | ||||
|           'postDeletionConfirmCaption': '你确定要删除帖子 “@content” 吗?该操作不可撤销。', | ||||
|           'reactAdd': '作出反应', | ||||
|           'reactCompleted': '你的反应已被添加', | ||||
|           'reactUncompleted': '你的反应已被移除', | ||||
| @@ -193,9 +202,15 @@ class SolianMessages extends Translations { | ||||
|           'channelDescription': '频道简介', | ||||
|           'channelEncrypted': '加密频道', | ||||
|           'channelMember': '频道成员', | ||||
|           'channelMembers': '频道成员', | ||||
|           'channelType': '频道类型', | ||||
|           'channelTypeCommon': '普通频道', | ||||
|           'channelTypeDirect': '私信聊天', | ||||
|           'channelAdjust': '调整频道', | ||||
|           'channelDetail': '频道详情', | ||||
|           'channelSettings': '频道设置', | ||||
|           'channelDeletionConfirm': '确认删除频道', | ||||
|           'channelDeletionConfirmCaption': '你确认要删除频道 @channel 吗?该操作不可撤销。', | ||||
|           'messageDecoding': '解码信息中…', | ||||
|           'messageDecodeFailed': '解码信息失败:@message', | ||||
|           'messageInputPlaceholder': '在 @channel 发信息…', | ||||
|   | ||||
							
								
								
									
										94
									
								
								lib/widgets/channel/channel_deletion.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								lib/widgets/channel/channel_deletion.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
|  | ||||
| class ChannelDeletion extends StatefulWidget { | ||||
|   final Channel channel; | ||||
|   final String realm; | ||||
|   final bool isOwned; | ||||
|  | ||||
|   const ChannelDeletion({ | ||||
|     super.key, | ||||
|     required this.channel, | ||||
|     required this.realm, | ||||
|     required this.isOwned, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<ChannelDeletion> createState() => _ChannelDeletionState(); | ||||
| } | ||||
|  | ||||
| class _ChannelDeletionState extends State<ChannelDeletion> { | ||||
|   bool _isBusy = false; | ||||
|  | ||||
|   Future<void> deleteChannel() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (!await auth.isAuthorized) return; | ||||
|  | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     final client = GetConnect(); | ||||
|     client.httpClient.baseUrl = ServiceFinder.services['messaging']; | ||||
|     client.httpClient.addAuthenticator(auth.requestAuthenticator); | ||||
|  | ||||
|     final resp = await client | ||||
|         .delete('/api/channels/${widget.realm}/${widget.channel.id}'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       context.showErrorDialog(resp.bodyString); | ||||
|     } else if (Navigator.canPop(context)) { | ||||
|       Navigator.pop(context, 'OK'); | ||||
|     } | ||||
|  | ||||
|     setState(() => _isBusy = false); | ||||
|   } | ||||
|  | ||||
|   Future<void> leaveChannel() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (!await auth.isAuthorized) return; | ||||
|  | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     final client = GetConnect(); | ||||
|     client.httpClient.baseUrl = ServiceFinder.services['messaging']; | ||||
|     client.httpClient.addAuthenticator(auth.requestAuthenticator); | ||||
|  | ||||
|     final resp = await client.delete( | ||||
|       '/api/channels/${widget.realm}/${widget.channel.alias}/members/me', | ||||
|     ); | ||||
|     if (resp.statusCode != 200) { | ||||
|       context.showErrorDialog(resp.bodyString); | ||||
|     } else if (Navigator.canPop(context)) { | ||||
|       Navigator.pop(context, 'OK'); | ||||
|     } | ||||
|  | ||||
|     setState(() => _isBusy = false); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AlertDialog( | ||||
|       title: Text('channelDeletionConfirm'.tr), | ||||
|       content: Text( | ||||
|         'channelDeletionConfirmCaption' | ||||
|             .trParams({'channel': '#${widget.channel.alias}'}), | ||||
|       ), | ||||
|       actions: <Widget>[ | ||||
|         TextButton( | ||||
|           onPressed: _isBusy ? null : () => Navigator.pop(context), | ||||
|           child: Text('cancel'.tr), | ||||
|         ), | ||||
|         TextButton( | ||||
|           onPressed: _isBusy | ||||
|               ? null | ||||
|               : widget.isOwned | ||||
|                   ? deleteChannel | ||||
|                   : leaveChannel, | ||||
|           child: Text('confirm'.tr), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user