From 36013a3a573fa0863971b1070576a98fac7d8ff1 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 28 Nov 2024 23:35:25 +0800 Subject: [PATCH] :sparkles: Editable channel --- assets/translations/en.json | 6 + assets/translations/zh.json | 6 + lib/controllers/chat_message_controller.dart | 5 +- lib/router.dart | 15 +- lib/screens/chat/channel_detail.dart | 161 +++++++++++++++++++ lib/screens/chat/manage.dart | 3 + lib/screens/chat/room.dart | 11 +- lib/types/chat.dart | 1 - 8 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 lib/screens/chat/channel_detail.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 40bea63..f618f70 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -171,6 +171,12 @@ "channelDeleted": "Chat channel {} has been deleted." , "channelDelete": "Delete channel {}", "channelDeleteDescription": "Are you sure you want to delete this channel? This operation is irreversible, all messages in this channel will be permanently deleted.", + "channelDetailPersonalRegion": "Personal", + "channelDetailMemberRegion": "Members", + "channelDetailAdminRegion": "Administration", + "channelEditProfile": "Edit Channel Profile", + "channelEdit": "Edit Channel", + "channelEditDescription": "Change the basic information of the channel, metadata, etc.", "fieldRealmAlias": "Realm Alias", "fieldRealmAliasHint": "The unique realm alias within the site, used to represent the realm in URL, leave blank to auto generate. Should be URL-Safe.", "fieldRealmName": "Name", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 59b9d20..01ad3c0 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -171,6 +171,12 @@ "channelDeleted": "聊天频道 {} 已被删除", "channelDelete": "删除聊天频道 {}", "channelDeleteDescription": "你确定要删除这个聊天频道吗?该操作不可撤销,其频道内的所有消息将被永久删除。", + "channelDetailPersonalRegion": "个人区域", + "channelDetailMemberRegion": "成员管理", + "channelDetailAdminRegion": "管理区域", + "channelEditProfile": "更改频道身份", + "channelEdit": "编辑频道", + "channelEditDescription": "更改频道基本信息,元数据等。", "fieldRealmAlias": "领域别名", "fieldRealmAliasHint": "全站范围内唯一的领域别名,用于在 URL 中表示该领域,留空则自动生成。应遵循 URL-Safe 的原则。", "fieldRealmName": "名称", diff --git a/lib/controllers/chat_message_controller.dart b/lib/controllers/chat_message_controller.dart index 650bf93..10b9ca3 100644 --- a/lib/controllers/chat_message_controller.dart +++ b/lib/controllers/chat_message_controller.dart @@ -212,10 +212,11 @@ class ChatMessageController extends ChangeNotifier { }; // Mock the message locally + final createdAt = DateTime.now(); final message = SnChatMessage( id: 0, - createdAt: DateTime.now(), - updatedAt: DateTime.now(), + createdAt: createdAt, + updatedAt: createdAt, deletedAt: null, uuid: nonce, body: body, diff --git a/lib/router.dart b/lib/router.dart index 7bef268..f624492 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -11,6 +11,7 @@ import 'package:surface/screens/auth/login.dart'; import 'package:surface/screens/auth/register.dart'; import 'package:surface/screens/chat.dart'; import 'package:surface/screens/chat/call_room.dart'; +import 'package:surface/screens/chat/channel_detail.dart'; import 'package:surface/screens/chat/manage.dart'; import 'package:surface/screens/chat/room.dart'; import 'package:surface/screens/explore.dart'; @@ -121,11 +122,23 @@ final _appRoutes = [ ), ), ), + GoRoute( + path: '/:scope/:alias/detail', + name: 'channelDetail', + builder: (context, state) => AppBackground( + child: ChannelDetailScreen( + scope: state.pathParameters['scope']!, + alias: state.pathParameters['alias']!, + ), + ), + ), GoRoute( path: '/manage', name: 'chatManage', pageBuilder: (context, state) => CustomTransitionPage( - child: ChatManageScreen(), + child: ChatManageScreen( + editingChannelAlias: state.uri.queryParameters['editing'], + ), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeThroughTransition( diff --git a/lib/screens/chat/channel_detail.dart b/lib/screens/chat/channel_detail.dart new file mode 100644 index 0000000..d4ef6c0 --- /dev/null +++ b/lib/screens/chat/channel_detail.dart @@ -0,0 +1,161 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/channel.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/user_directory.dart'; +import 'package:surface/types/chat.dart'; +import 'package:surface/widgets/account/account_image.dart'; +import 'package:surface/widgets/dialog.dart'; +import 'package:surface/widgets/loading_indicator.dart'; + +class ChannelDetailScreen extends StatefulWidget { + final String scope; + final String alias; + const ChannelDetailScreen({ + super.key, + required this.scope, + required this.alias, + }); + + @override + State createState() => _ChannelDetailScreenState(); +} + +class _ChannelDetailScreenState extends State { + bool _isBusy = false; + + SnChannel? _channel; + SnChannelMember? _profile; + + Future _fetchChannel() async { + setState(() => _isBusy = true); + + try { + final chan = context.read(); + _channel = await chan.getChannel('${widget.scope}:${widget.alias}'); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + Future _fetchChannelProfile() async { + setState(() => _isBusy = true); + + try { + final sn = context.read(); + final resp = await sn.client + .get('/cgi/im/channels/${_channel!.keyPath}/members/me'); + _profile = SnChannelMember.fromJson(resp.data); + if (!mounted) return; + final ud = context.read(); + await ud.getAccount(_profile!.accountId); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + @override + void initState() { + super.initState(); + _fetchChannel().then((_) { + _fetchChannelProfile(); + }); + } + + @override + Widget build(BuildContext context) { + final ud = context.read(); + return Scaffold( + appBar: AppBar( + title: _channel != null ? Text(_channel!.name) : Text('loading').tr(), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LoadingIndicator(isActive: _isBusy), + const Gap(24), + if (_channel != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _channel!.name, + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + _channel!.description, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ).padding(horizontal: 24), + const Gap(16), + const Divider(), + if (_profile != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('channelDetailPersonalRegion') + .bold() + .fontSize(17) + .tr() + .padding(horizontal: 20, bottom: 4), + ListTile( + leading: AccountImage( + content: + ud.getAccountFromCache(_profile!.accountId)?.avatar, + radius: 18, + ), + trailing: const Icon(Symbols.chevron_right), + title: Text('channelEditProfile').tr(), + subtitle: Text( + _profile?.nick ?? + ud.getAccountFromCache(_profile!.accountId)!.nick, + ), + contentPadding: const EdgeInsets.only(left: 20, right: 20), + onTap: () {}, + ), + ], + ).padding(bottom: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('channelDetailAdminRegion') + .bold() + .fontSize(17) + .tr() + .padding(horizontal: 20, bottom: 4), + ListTile( + leading: const Icon(Symbols.edit), + trailing: const Icon(Symbols.chevron_right), + title: Text('channelEdit').tr(), + subtitle: Text('channelEditDescription').tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + onTap: () { + GoRouter.of(context).pushNamed( + 'chatManage', + queryParameters: {'editing': _channel!.keyPath}, + ).then((value) { + if (value != null && context.mounted) { + Navigator.pop(context, value); + } + }); + }, + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/screens/chat/manage.dart b/lib/screens/chat/manage.dart index 0400752..7c2dee7 100644 --- a/lib/screens/chat/manage.dart +++ b/lib/screens/chat/manage.dart @@ -58,6 +58,9 @@ class _ChatManageScreenState extends State { '/cgi/im/channels/${widget.editingChannelAlias}', ); _editingChannel = SnChannel.fromJson(resp.data); + _aliasController.text = _editingChannel!.alias; + _nameController.text = _editingChannel!.name; + _descriptionController.text = _editingChannel!.description; } catch (err) { if (!mounted) return; context.showErrorDialog(err); diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index 4edee0c..5ffd7e7 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -200,7 +200,16 @@ class _ChatRoomScreenState extends State { ), IconButton( icon: const Icon(Symbols.more_vert), - onPressed: () {}, + onPressed: () { + GoRouter.of(context).pushNamed('channelDetail', pathParameters: { + 'scope': widget.scope, + 'alias': widget.alias, + }).then((value) { + if (value != null) { + _fetchChannel(); + } + }); + }, ), const Gap(8), ], diff --git a/lib/types/chat.dart b/lib/types/chat.dart index 9fa3581..2ce9728 100644 --- a/lib/types/chat.dart +++ b/lib/types/chat.dart @@ -23,7 +23,6 @@ class SnChannel with _$SnChannel { @HiveField(6) required String description, @HiveField(7) required List? members, List? messages, - dynamic calls, @HiveField(8) required int type, @HiveField(9) required int accountId, @HiveField(10) required SnRealm? realm,