diff --git a/lib/screens/channel/channel_chat.dart b/lib/screens/channel/channel_chat.dart index 2fa89f6..604f36d 100644 --- a/lib/screens/channel/channel_chat.dart +++ b/lib/screens/channel/channel_chat.dart @@ -33,6 +33,8 @@ class ChannelChatScreen extends StatefulWidget { class _ChannelChatScreenState extends State { bool _isBusy = false; + int? _accountId; + String? _overrideAlias; Channel? _channel; @@ -41,6 +43,12 @@ class _ChannelChatScreenState extends State { final PagingController _pagingController = PagingController(firstPageKey: 0); + getProfile() async { + final AuthProvider auth = Get.find(); + final prof = await auth.getProfile(); + _accountId = prof.body['id']; + } + getChannel({String? overrideAlias}) async { final ChannelProvider provider = Get.find(); @@ -167,6 +175,7 @@ class _ChannelChatScreenState extends State { void initState() { super.initState(); + getProfile(); getChannel().then((_) { listenMessages(); _pagingController.addPageRequestListener(getMessages); @@ -181,9 +190,22 @@ class _ChannelChatScreenState extends State { ); } + String title = _channel?.name ?? 'loading'.tr; + String? placeholder; + + if (_channel?.type == 1) { + final otherside = _channel!.members! + .where((e) => e.account.externalId != _accountId) + .first; + title = otherside.account.nick; + placeholder = 'messageInputPlaceholder'.trParams( + {'channel': '@${otherside.account.name}'}, + ); + } + return Scaffold( appBar: AppBar( - title: Text(_channel?.name ?? 'loading'.tr), + title: Text(title), centerTitle: false, actions: [ IconButton( @@ -233,6 +255,7 @@ class _ChannelChatScreenState extends State { right: 16, child: ChatMessageInput( realm: widget.realm, + placeholder: placeholder, channel: _channel!, onSent: (Message item) { setState(() { diff --git a/lib/screens/realms/realm_view.dart b/lib/screens/realms/realm_view.dart index f59a14c..55ca1a5 100644 --- a/lib/screens/realms/realm_view.dart +++ b/lib/screens/realms/realm_view.dart @@ -276,6 +276,7 @@ class RealmChannelListWidget extends StatelessWidget { ChannelListWidget( channels: channels, selfId: snapshot.data?.body['id'] ?? 0, + noCategory: true, ) ], ), diff --git a/lib/translations.dart b/lib/translations.dart index ab1de27..615412a 100644 --- a/lib/translations.dart +++ b/lib/translations.dart @@ -144,6 +144,8 @@ class SolianMessages extends Translations { 'channelDeletionConfirm': 'Confirm channel deletion', 'channelDeletionConfirmCaption': 'Are you sure to delete channel @channel? This action cannot be undone!', + 'channelCategoryDirect': 'DM', + 'channelCategoryDirectHint': 'Your direct messages', 'messageDecoding': 'Decoding...', 'messageDecodeFailed': 'Unable to decode: @message', 'messageInputPlaceholder': 'Message @channel', @@ -280,6 +282,8 @@ class SolianMessages extends Translations { 'channelSettings': '频道设置', 'channelDeletionConfirm': '确认删除频道', 'channelDeletionConfirmCaption': '你确认要删除频道 @channel 吗?该操作不可撤销。', + 'channelCategoryDirect': '私聊频道', + 'channelCategoryDirectHint': '你的所有私聊频道', 'messageDecoding': '解码信息中…', 'messageDecodeFailed': '解码信息失败:@message', 'messageInputPlaceholder': '在 @channel 发信息', diff --git a/lib/widgets/channel/channel_list.dart b/lib/widgets/channel/channel_list.dart index 3a8e9de..d75f005 100644 --- a/lib/widgets/channel/channel_list.dart +++ b/lib/widgets/channel/channel_list.dart @@ -5,72 +5,146 @@ import 'package:solian/models/channel.dart'; import 'package:solian/router.dart'; import 'package:solian/widgets/account/account_avatar.dart'; -class ChannelListWidget extends StatelessWidget { +class ChannelListWidget extends StatefulWidget { final List channels; final int selfId; + final bool noCategory; - const ChannelListWidget( - {super.key, required this.channels, required this.selfId}); + const ChannelListWidget({ + super.key, + required this.channels, + required this.selfId, + this.noCategory = false, + }); @override - Widget build(BuildContext context) { - return SliverList.builder( - itemCount: channels.length, - itemBuilder: (context, index) { - final element = channels[index]; + State createState() => _ChannelListWidgetState(); +} - if (element.type == 1) { - final otherside = element.members! - .where((e) => e.account.externalId != selfId) - .first; +class _ChannelListWidgetState extends State { + final List _globalChannels = List.empty(growable: true); + final List _directMessages = List.empty(growable: true); + final Map> _inRealms = {}; - return ListTile( - leading: AccountAvatar( - content: otherside.account.avatar, - bgColor: Colors.indigo, - feColor: Colors.white, - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - title: Text(otherside.account.nick), - subtitle: Text( - 'channelDirectDescription' - .trParams({'username': '@${otherside.account.name}'}), - ), - onTap: () { - AppRouter.instance.pushNamed( - 'channelChat', - pathParameters: {'alias': element.alias}, - queryParameters: { - if (element.realmId != null) 'realm': element.realm!.alias, - }, - ); + void mapChannels() { + _inRealms.clear(); + _globalChannels.clear(); + _directMessages.clear(); + + if (widget.noCategory) { + _globalChannels.addAll(widget.channels); + return; + } + + for (final channel in widget.channels) { + if (channel.realmId != null) { + if (_inRealms[channel.alias] == null) { + _inRealms[channel.alias] = List.empty(growable: true); + } + _inRealms[channel.alias]!.add(channel); + } else if (channel.type == 1) { + _directMessages.add(channel); + } else { + _globalChannels.add(channel); + } + } + } + + @override + void didUpdateWidget(covariant ChannelListWidget oldWidget) { + setState(() => mapChannels()); + super.didUpdateWidget(oldWidget); + } + + Widget buildItem(Channel element) { + if (element.type == 1) { + final otherside = element.members! + .where((e) => e.account.externalId != widget.selfId) + .first; + + return ListTile( + leading: AccountAvatar( + content: otherside.account.avatar, + bgColor: Colors.indigo, + feColor: Colors.white, + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + title: Text(otherside.account.nick), + subtitle: Text( + 'channelDirectDescription' + .trParams({'username': '@${otherside.account.name}'}), + ), + onTap: () { + AppRouter.instance.pushNamed( + 'channelChat', + pathParameters: {'alias': element.alias}, + queryParameters: { + if (element.realmId != null) 'realm': element.realm!.alias, }, ); - } + }, + ); + } - return ListTile( - leading: const CircleAvatar( - backgroundColor: Colors.indigo, - child: FaIcon( - FontAwesomeIcons.hashtag, - color: Colors.white, - size: 16, - ), - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 24), - title: Text(element.name), - subtitle: Text(element.description), - onTap: () { - AppRouter.instance.pushNamed( - 'channelChat', - pathParameters: {'alias': element.alias}, - queryParameters: { - if (element.realmId != null) 'realm': element.realm!.alias, - }, - ); + return ListTile( + leading: const CircleAvatar( + backgroundColor: Colors.indigo, + child: FaIcon( + FontAwesomeIcons.hashtag, + color: Colors.white, + size: 16, + ), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + title: Text(element.name), + subtitle: Text(element.description), + onTap: () { + AppRouter.instance.pushNamed( + 'channelChat', + pathParameters: {'alias': element.alias}, + queryParameters: { + if (element.realmId != null) 'realm': element.realm!.alias, }, ); }, ); } + + @override + Widget build(BuildContext context) { + if (widget.noCategory) { + return SliverList.builder( + itemCount: _globalChannels.length, + itemBuilder: (context, index) { + final element = _globalChannels[index]; + return buildItem(element); + }, + ); + } + + return SliverList.list( + children: [ + ..._globalChannels.map((e) => buildItem(e)), + if (_directMessages.isNotEmpty) + ExpansionTile( + tilePadding: const EdgeInsets.symmetric(horizontal: 24), + title: Text('channelCategoryDirect'.tr), + subtitle: Text('channelCategoryDirectHint'.tr), + children: _directMessages.map((e) => buildItem(e)).toList(), + ), + ..._inRealms.entries.map((element) { + return ExpansionTile( + tilePadding: const EdgeInsets.symmetric(horizontal: 24), + title: Text(element.value.first.realm!.name), + subtitle: Text( + element.value.first.realm!.description, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + children: element.value.map((e) => buildItem(e)).toList(), + ); + }), + ], + ); + } } diff --git a/lib/widgets/chat/chat_message_input.dart b/lib/widgets/chat/chat_message_input.dart index 3d56baa..eeaee86 100644 --- a/lib/widgets/chat/chat_message_input.dart +++ b/lib/widgets/chat/chat_message_input.dart @@ -12,6 +12,7 @@ import 'package:uuid/uuid.dart'; class ChatMessageInput extends StatefulWidget { final Message? edit; final Message? reply; + final String? placeholder; final Channel channel; final String realm; final Function(Message) onSent; @@ -20,6 +21,7 @@ class ChatMessageInput extends StatefulWidget { super.key, this.edit, this.reply, + this.placeholder, required this.channel, required this.realm, required this.onSent, @@ -145,9 +147,10 @@ class _ChatMessageInputState extends State { autocorrect: true, keyboardType: TextInputType.text, decoration: InputDecoration.collapsed( - hintText: 'messageInputPlaceholder'.trParams( - {'channel': '#${widget.channel.alias}'}, - ), + hintText: widget.placeholder ?? + 'messageInputPlaceholder'.trParams( + {'channel': '#${widget.channel.alias}'}, + ), ), onSubmitted: (_) => sendMessage(), onTapOutside: (_) =>