Direct messages

This commit is contained in:
LittleSheep 2024-12-08 13:45:51 +08:00
parent 4805e68fcd
commit 669107a99f
8 changed files with 219 additions and 39 deletions

View File

@ -242,6 +242,7 @@
"realmMemberAddDescription": "Add new member to this realm.", "realmMemberAddDescription": "Add new member to this realm.",
"realmMemberAdded": "Realm member has been added.", "realmMemberAdded": "Realm member has been added.",
"fieldChatMessage": "Message in {}", "fieldChatMessage": "Message in {}",
"fieldChatMessageDirect": "Message with {}",
"eventResourceTag": "Event {}", "eventResourceTag": "Event {}",
"messageDelete": "Delete message {}", "messageDelete": "Delete message {}",
"messageDeleteDescription": "Are you sure you want to delete this message? This operation is irreversible. You will leave a record of the deleted message.", "messageDeleteDescription": "Are you sure you want to delete this message? This operation is irreversible. You will leave a record of the deleted message.",
@ -402,5 +403,8 @@
"accountDeletion": "Delete Account", "accountDeletion": "Delete Account",
"accountDeletionDescription": "Are you sure you want to delete this account? This operation is irreversible, all resources (posts, chat channels, publishers, etc) belonging to this account will be permanently deleted. Be careful and think twice!", "accountDeletionDescription": "Are you sure you want to delete this account? This operation is irreversible, all resources (posts, chat channels, publishers, etc) belonging to this account will be permanently deleted. Be careful and think twice!",
"accountDeletionActionDescription": "Delete your Solarpass account.", "accountDeletionActionDescription": "Delete your Solarpass account.",
"accountDeletionSubmitted": "Account deletion request has been sent, you can check your inbox and follow the instructions in the email to complete the deletion operation." "accountDeletionSubmitted": "Account deletion request has been sent, you can check your inbox and follow the instructions in the email to complete the deletion operation.",
"channelNewChannel": "New Channel",
"channelNewDirectMessage": "New Direct Message",
"channelDirectMessageDescription": "Direct Message with {}"
} }

View File

@ -242,6 +242,7 @@
"realmMemberAddDescription": "给当前领域添加新成员。", "realmMemberAddDescription": "给当前领域添加新成员。",
"realmMemberAdded": "领域成员已添加。", "realmMemberAdded": "领域成员已添加。",
"fieldChatMessage": "在 {} 中发消息", "fieldChatMessage": "在 {} 中发消息",
"fieldChatMessageDirect": "给 {} 发消息",
"eventResourceTag": "消息 {}", "eventResourceTag": "消息 {}",
"messageDelete": "删除消息 {}", "messageDelete": "删除消息 {}",
"messageDeleteDescription": "你确定要删除这个消息吗?该操作不可撤销。同时您将留下一条删除消息的记录。", "messageDeleteDescription": "你确定要删除这个消息吗?该操作不可撤销。同时您将留下一条删除消息的记录。",
@ -402,5 +403,8 @@
"accountDeletion": "删除帐户", "accountDeletion": "删除帐户",
"accountDeletionDescription": "你确定要删除这个帐户吗?该操作不可撤销,其隶属于该帐户的所有资源(帖子、聊天频道、发布者、制品等)都将被永久删除。三思而后行!", "accountDeletionDescription": "你确定要删除这个帐户吗?该操作不可撤销,其隶属于该帐户的所有资源(帖子、聊天频道、发布者、制品等)都将被永久删除。三思而后行!",
"accountDeletionActionDescription": "删除你的 Solarpass 帐户。", "accountDeletionActionDescription": "删除你的 Solarpass 帐户。",
"accountDeletionSubmitted": "帐户删除申请已发出,你可以检查你的收件箱并根据邮件内的指示完成删除操作。" "accountDeletionSubmitted": "帐户删除申请已发出,你可以检查你的收件箱并根据邮件内的指示完成删除操作。",
"channelNewChannel": "新建频道",
"channelNewDirectMessage": "发起私信",
"channelDirectMessageDescription": "与 {} 的私聊"
} }

View File

@ -1,5 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -7,9 +9,14 @@ import 'package:surface/providers/channel.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_select.dart';
import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/loading_indicator.dart';
import 'package:uuid/uuid.dart';
import '../providers/sn_network.dart';
import '../providers/userinfo.dart';
class ChatScreen extends StatefulWidget { class ChatScreen extends StatefulWidget {
const ChatScreen({super.key}); const ChatScreen({super.key});
@ -19,6 +26,8 @@ class ChatScreen extends StatefulWidget {
} }
class _ChatScreenState extends State<ChatScreen> { class _ChatScreenState extends State<ChatScreen> {
final _fabKey = GlobalKey<ExpandableFabState>();
bool _isBusy = true; bool _isBusy = true;
List<SnChannel>? _channels; List<SnChannel>? _channels;
@ -30,17 +39,29 @@ class _ChatScreenState extends State<ChatScreen> {
final lastMessages = await chan.getLastMessages(channels); final lastMessages = await chan.getLastMessages(channels);
_lastMessages = {for (final val in lastMessages) val.channelId: val}; _lastMessages = {for (final val in lastMessages) val.channelId: val};
channels.sort((a, b) { channels.sort((a, b) {
if (_lastMessages!.containsKey(a.id) && if (_lastMessages!.containsKey(a.id) && _lastMessages!.containsKey(b.id)) {
_lastMessages!.containsKey(b.id)) { return _lastMessages![b.id]!.createdAt.compareTo(_lastMessages![a.id]!.createdAt);
return _lastMessages![b.id]!
.createdAt
.compareTo(_lastMessages![a.id]!.createdAt);
} }
if (_lastMessages!.containsKey(a.id)) return -1; if (_lastMessages!.containsKey(a.id)) return -1;
if (_lastMessages!.containsKey(b.id)) return 1; if (_lastMessages!.containsKey(b.id)) return 1;
return 0; return 0;
}); });
if (!mounted) return;
final ud = context.read<UserDirectoryProvider>();
for (final channel in channels) {
if (channel.type == 1) {
await ud.listAccount(
channel.members
?.cast<SnChannelMember?>()
.map((ele) => ele?.accountId)
.where((ele) => ele != null)
.toSet() ??
{},
);
}
}
if (mounted) setState(() => _channels = channels); if (mounted) setState(() => _channels = channels);
}) })
..onError((err) { ..onError((err) {
@ -54,6 +75,32 @@ class _ChatScreenState extends State<ChatScreen> {
}); });
} }
void _newDirectMessage() async {
final user = await showModalBottomSheet(
context: context,
builder: (context) => AccountSelect(title: 'channelNewDirectMessage'.tr()),
);
if (user == null) return;
if (!mounted) return;
try {
const uuid = Uuid();
final sn = context.read<SnNetworkProvider>();
final ua = context.read<UserProvider>();
final resp = await sn.client.post('/cgi/im/channels/global/dm', data: {
'alias': uuid.v4().replaceAll('-', '').substring(0, 12),
'name': 'DM',
'description': 'A direct message channel between @${ua.user?.name} and @${user.name}',
'related_user': user.id,
});
_fabKey.currentState!.toggle();
_refreshChannels();
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -63,19 +110,67 @@ class _ChatScreenState extends State<ChatScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>(); final ud = context.read<UserDirectoryProvider>();
final ua = context.read<UserProvider>();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: AutoAppBarLeading(), leading: AutoAppBarLeading(),
title: Text('screenChat').tr(), title: Text('screenChat').tr(),
), ),
floatingActionButton: FloatingActionButton( floatingActionButtonLocation: ExpandableFab.location,
child: const Icon(Symbols.chat_add_on), floatingActionButton: ExpandableFab(
key: _fabKey,
distance: 75,
type: ExpandableFabType.up,
childrenAnimation: ExpandableFabAnimation.none,
overlayStyle: ExpandableFabOverlayStyle(
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
),
openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(Symbols.add, size: 28),
fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
),
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28),
fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
),
children: [
Row(
children: [
Text('channelNewChannel').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'channelNewChannel'.tr(),
onPressed: () { onPressed: () {
_fabKey.currentState!.toggle();
GoRouter.of(context).pushNamed('chatManage').then((value) { GoRouter.of(context).pushNamed('chatManage').then((value) {
if (value != null && context.mounted) _refreshChannels(); if (value != null && context.mounted) _refreshChannels();
}); });
}, },
child: const Icon(Symbols.chat_add_on),
),
],
),
Row(
children: [
Text('channelNewDirectMessage').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'channelNewDirectMessage'.tr(),
onPressed: _newDirectMessage,
child: const Icon(Symbols.communication),
),
],
),
],
), ),
body: Column( body: Column(
children: [ children: [
@ -88,6 +183,46 @@ class _ChatScreenState extends State<ChatScreen> {
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final channel = _channels![idx]; final channel = _channels![idx];
final lastMessage = _lastMessages?[channel.id]; final lastMessage = _lastMessages?[channel.id];
if (channel.type == 1) {
final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere(
(ele) => ele?.accountId != ua.user?.id,
orElse: () => null,
);
return ListTile(
title: Text(ud.getAccountFromCache(otherMember?.accountId)?.nick ?? channel.name),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: Text(
'channelDirectMessageDescription'.tr(args: [
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
]),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: ud.getAccountFromCache(otherMember?.accountId)?.avatar,
),
onTap: () {
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (value == true) _refreshChannels();
});
},
);
}
return ListTile( return ListTile(
title: Text(channel.name), title: Text(channel.name),
subtitle: lastMessage != null subtitle: lastMessage != null

View File

@ -23,9 +23,13 @@ import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/loading_indicator.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart';
import '../../providers/user_directory.dart';
import '../../providers/userinfo.dart';
class ChatRoomScreen extends StatefulWidget { class ChatRoomScreen extends StatefulWidget {
final String scope; final String scope;
final String alias; final String alias;
const ChatRoomScreen({super.key, required this.scope, required this.alias}); const ChatRoomScreen({super.key, required this.scope, required this.alias});
@override @override
@ -37,6 +41,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
bool _isCalling = false; bool _isCalling = false;
SnChannel? _channel; SnChannel? _channel;
SnChannelMember? _otherMember;
SnChatCall? _ongoingCall; SnChatCall? _ongoingCall;
final GlobalKey<ChatMessageInputState> _inputGlobalKey = GlobalKey(); final GlobalKey<ChatMessageInputState> _inputGlobalKey = GlobalKey();
@ -50,6 +55,24 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
try { try {
final chan = context.read<ChatChannelProvider>(); final chan = context.read<ChatChannelProvider>();
_channel = await chan.getChannel('${widget.scope}:${widget.alias}'); _channel = await chan.getChannel('${widget.scope}:${widget.alias}');
if (!mounted || _channel == null) return;
final ud = context.read<UserDirectoryProvider>();
final ua = context.read<UserProvider>();
if (_channel!.type == 1) {
await ud.listAccount(
_channel!.members
?.cast<SnChannelMember?>()
.map((ele) => ele?.accountId)
.where((ele) => ele != null && ele != ua.user?.id)
.toSet() ??
{},
);
_otherMember = _channel!.members?.cast<SnChannelMember?>().firstWhere(
(ele) => ele?.accountId != ua.user?.id,
orElse: () => null,
);
}
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -183,15 +206,18 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final call = context.watch<ChatCallProvider>(); final call = context.watch<ChatCallProvider>();
final ud = context.read<UserDirectoryProvider>();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(_channel?.name ?? 'loading'.tr()), title: Text(
_channel?.type == 1
? ud.getAccountFromCache(_otherMember?.accountId)?.nick ?? _channel!.name
: _channel?.name ?? 'loading'.tr(),
),
actions: [ actions: [
IconButton( IconButton(
icon: _ongoingCall == null icon: _ongoingCall == null ? const Icon(Symbols.call) : const Icon(Symbols.call_end),
? const Icon(Symbols.call)
: const Icon(Symbols.call_end),
onPressed: _isCalling onPressed: _isCalling
? null ? null
: _ongoingCall == null : _ongoingCall == null
@ -241,9 +267,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
) )
], ],
), ),
).height(_ongoingCall != null ? 54 : 0, animate: true).animate( )
const Duration(milliseconds: 300), .height(_ongoingCall != null ? 54 : 0, animate: true)
Curves.fastLinearToSlowEaseIn), .animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
if (_messageController.isPending) if (_messageController.isPending)
Expanded( Expanded(
child: const CircularProgressIndicator().center(), child: const CircularProgressIndicator().center(),
@ -284,8 +310,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
data: message, data: message,
isMerged: canMerge, isMerged: canMerge,
hasMerged: canMergePrevious, hasMerged: canMergePrevious,
isPending: _messageController.unconfirmedMessages isPending: _messageController.unconfirmedMessages.contains(message.uuid),
.contains(message.uuid),
onReply: (value) { onReply: (value) {
_inputGlobalKey.currentState?.setReply(value); _inputGlobalKey.currentState?.setReply(value);
}, },
@ -304,6 +329,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
elevation: 2, elevation: 2,
child: ChatMessageInput( child: ChatMessageInput(
key: _inputGlobalKey, key: _inputGlobalKey,
otherMember: _otherMember,
controller: _messageController, controller: _messageController,
).padding(bottom: MediaQuery.of(context).padding.bottom), ).padding(bottom: MediaQuery.of(context).padding.bottom),
), ),

View File

@ -21,7 +21,7 @@ class SnChannel with _$SnChannel {
@HiveField(4) required String alias, @HiveField(4) required String alias,
@HiveField(5) required String name, @HiveField(5) required String name,
@HiveField(6) required String description, @HiveField(6) required String description,
@HiveField(7) required List<dynamic>? members, @HiveField(7) required List<SnChannelMember>? members,
List<SnChatMessage>? messages, List<SnChatMessage>? messages,
@HiveField(8) required int type, @HiveField(8) required int type,
@HiveField(9) required int accountId, @HiveField(9) required int accountId,

View File

@ -35,7 +35,7 @@ mixin _$SnChannel {
@HiveField(6) @HiveField(6)
String get description => throw _privateConstructorUsedError; String get description => throw _privateConstructorUsedError;
@HiveField(7) @HiveField(7)
List<dynamic>? get members => throw _privateConstructorUsedError; List<SnChannelMember>? get members => throw _privateConstructorUsedError;
List<SnChatMessage>? get messages => throw _privateConstructorUsedError; List<SnChatMessage>? get messages => throw _privateConstructorUsedError;
@HiveField(8) @HiveField(8)
int get type => throw _privateConstructorUsedError; int get type => throw _privateConstructorUsedError;
@ -73,7 +73,7 @@ abstract class $SnChannelCopyWith<$Res> {
@HiveField(4) String alias, @HiveField(4) String alias,
@HiveField(5) String name, @HiveField(5) String name,
@HiveField(6) String description, @HiveField(6) String description,
@HiveField(7) List<dynamic>? members, @HiveField(7) List<SnChannelMember>? members,
List<SnChatMessage>? messages, List<SnChatMessage>? messages,
@HiveField(8) int type, @HiveField(8) int type,
@HiveField(9) int accountId, @HiveField(9) int accountId,
@ -148,7 +148,7 @@ class _$SnChannelCopyWithImpl<$Res, $Val extends SnChannel>
members: freezed == members members: freezed == members
? _value.members ? _value.members
: members // ignore: cast_nullable_to_non_nullable : members // ignore: cast_nullable_to_non_nullable
as List<dynamic>?, as List<SnChannelMember>?,
messages: freezed == messages messages: freezed == messages
? _value.messages ? _value.messages
: messages // ignore: cast_nullable_to_non_nullable : messages // ignore: cast_nullable_to_non_nullable
@ -211,7 +211,7 @@ abstract class _$$SnChannelImplCopyWith<$Res>
@HiveField(4) String alias, @HiveField(4) String alias,
@HiveField(5) String name, @HiveField(5) String name,
@HiveField(6) String description, @HiveField(6) String description,
@HiveField(7) List<dynamic>? members, @HiveField(7) List<SnChannelMember>? members,
List<SnChatMessage>? messages, List<SnChatMessage>? messages,
@HiveField(8) int type, @HiveField(8) int type,
@HiveField(9) int accountId, @HiveField(9) int accountId,
@ -285,7 +285,7 @@ class __$$SnChannelImplCopyWithImpl<$Res>
members: freezed == members members: freezed == members
? _value._members ? _value._members
: members // ignore: cast_nullable_to_non_nullable : members // ignore: cast_nullable_to_non_nullable
as List<dynamic>?, as List<SnChannelMember>?,
messages: freezed == messages messages: freezed == messages
? _value._messages ? _value._messages
: messages // ignore: cast_nullable_to_non_nullable : messages // ignore: cast_nullable_to_non_nullable
@ -330,7 +330,7 @@ class _$SnChannelImpl extends _SnChannel {
@HiveField(4) required this.alias, @HiveField(4) required this.alias,
@HiveField(5) required this.name, @HiveField(5) required this.name,
@HiveField(6) required this.description, @HiveField(6) required this.description,
@HiveField(7) required final List<dynamic>? members, @HiveField(7) required final List<SnChannelMember>? members,
final List<SnChatMessage>? messages, final List<SnChatMessage>? messages,
@HiveField(8) required this.type, @HiveField(8) required this.type,
@HiveField(9) required this.accountId, @HiveField(9) required this.accountId,
@ -366,10 +366,10 @@ class _$SnChannelImpl extends _SnChannel {
@override @override
@HiveField(6) @HiveField(6)
final String description; final String description;
final List<dynamic>? _members; final List<SnChannelMember>? _members;
@override @override
@HiveField(7) @HiveField(7)
List<dynamic>? get members { List<SnChannelMember>? get members {
final value = _members; final value = _members;
if (value == null) return null; if (value == null) return null;
if (_members is EqualUnmodifiableListView) return _members; if (_members is EqualUnmodifiableListView) return _members;
@ -484,7 +484,7 @@ abstract class _SnChannel extends SnChannel {
@HiveField(4) required final String alias, @HiveField(4) required final String alias,
@HiveField(5) required final String name, @HiveField(5) required final String name,
@HiveField(6) required final String description, @HiveField(6) required final String description,
@HiveField(7) required final List<dynamic>? members, @HiveField(7) required final List<SnChannelMember>? members,
final List<SnChatMessage>? messages, final List<SnChatMessage>? messages,
@HiveField(8) required final int type, @HiveField(8) required final int type,
@HiveField(9) required final int accountId, @HiveField(9) required final int accountId,
@ -520,7 +520,7 @@ abstract class _SnChannel extends SnChannel {
String get description; String get description;
@override @override
@HiveField(7) @HiveField(7)
List<dynamic>? get members; List<SnChannelMember>? get members;
@override @override
List<SnChatMessage>? get messages; List<SnChatMessage>? get messages;
@override @override

View File

@ -24,7 +24,7 @@ class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> {
alias: fields[4] as String, alias: fields[4] as String,
name: fields[5] as String, name: fields[5] as String,
description: fields[6] as String, description: fields[6] as String,
members: (fields[7] as List?)?.cast<dynamic>(), members: (fields[7] as List?)?.cast<SnChannelMember>(),
type: fields[8] as int, type: fields[8] as int,
accountId: fields[9] as int, accountId: fields[9] as int,
realm: fields[10] as SnRealm?, realm: fields[10] as SnRealm?,
@ -223,7 +223,9 @@ _$SnChannelImpl _$$SnChannelImplFromJson(Map<String, dynamic> json) =>
alias: json['alias'] as String, alias: json['alias'] as String,
name: json['name'] as String, name: json['name'] as String,
description: json['description'] as String, description: json['description'] as String,
members: json['members'] as List<dynamic>?, members: (json['members'] as List<dynamic>?)
?.map((e) => SnChannelMember.fromJson(e as Map<String, dynamic>))
.toList(),
messages: (json['messages'] as List<dynamic>?) messages: (json['messages'] as List<dynamic>?)
?.map((e) => SnChatMessage.fromJson(e as Map<String, dynamic>)) ?.map((e) => SnChatMessage.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
@ -246,7 +248,7 @@ Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) =>
'alias': instance.alias, 'alias': instance.alias,
'name': instance.name, 'name': instance.name,
'description': instance.description, 'description': instance.description,
'members': instance.members, 'members': instance.members?.map((e) => e.toJson()).toList(),
'messages': instance.messages?.map((e) => e.toJson()).toList(), 'messages': instance.messages?.map((e) => e.toJson()).toList(),
'type': instance.type, 'type': instance.type,
'account_id': instance.accountId, 'account_id': instance.accountId,

View File

@ -17,10 +17,13 @@ import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/markdown_content.dart'; import 'package:surface/widgets/markdown_content.dart';
import 'package:surface/widgets/post/post_media_pending_list.dart'; import 'package:surface/widgets/post/post_media_pending_list.dart';
import '../../providers/user_directory.dart';
class ChatMessageInput extends StatefulWidget { class ChatMessageInput extends StatefulWidget {
final ChatMessageController controller; final ChatMessageController controller;
final SnChannelMember? otherMember;
const ChatMessageInput({super.key, required this.controller}); const ChatMessageInput({super.key, required this.controller, this.otherMember});
@override @override
State<ChatMessageInput> createState() => ChatMessageInputState(); State<ChatMessageInput> createState() => ChatMessageInputState();
@ -170,6 +173,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>();
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -279,7 +284,11 @@ class ChatMessageInputState extends State<ChatMessageInput> {
controller: _contentController, controller: _contentController,
decoration: InputDecoration( decoration: InputDecoration(
isCollapsed: true, isCollapsed: true,
hintText: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]), hintText: widget.otherMember != null
? 'fieldChatMessageDirect'.tr(args: [
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
])
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
border: InputBorder.none, border: InputBorder.none,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),