✨ Reply / forward mesage
This commit is contained in:
parent
7edfd56bf7
commit
6fd61b547e
@ -87,5 +87,7 @@
|
||||
},
|
||||
"permissionOwner": "Owner",
|
||||
"permissionModerator": "Moderator",
|
||||
"permissionMember": "Member"
|
||||
"permissionMember": "Member",
|
||||
"reply": "Reply",
|
||||
"forward": "Forward"
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import 'package:island/database/drift_db.dart';
|
||||
import 'package:island/database/message.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
@ -169,11 +171,16 @@ class MessageRepository {
|
||||
}
|
||||
|
||||
Future<LocalChatMessage> sendMessage(
|
||||
String atk,
|
||||
String baseUrl,
|
||||
int roomId,
|
||||
String content,
|
||||
String nonce, {
|
||||
List<SnCloudFile>? attachments,
|
||||
required List<UniversalFile> attachments,
|
||||
Map<String, dynamic>? meta,
|
||||
SnChatMessage? replyingTo,
|
||||
SnChatMessage? forwardingTo,
|
||||
SnChatMessage? editingTo,
|
||||
}) async {
|
||||
// Generate a unique nonce for this message
|
||||
final nonce = const Uuid().v4();
|
||||
@ -200,15 +207,44 @@ class MessageRepository {
|
||||
await _database.saveMessage(_database.messageToCompanion(localMessage));
|
||||
|
||||
try {
|
||||
var cloudAttachments = List.empty(growable: true);
|
||||
// Upload files
|
||||
for (var idx = 0; idx < attachments.length; idx++) {
|
||||
final cloudFile =
|
||||
await putMediaToCloud(
|
||||
fileData: attachments[idx].data,
|
||||
atk: atk,
|
||||
baseUrl: baseUrl,
|
||||
filename: attachments[idx].data.name ?? 'Post media',
|
||||
mimetype:
|
||||
attachments[idx].data.mimeType ??
|
||||
switch (attachments[idx].type) {
|
||||
UniversalFileType.image => 'image/unknown',
|
||||
UniversalFileType.video => 'video/unknown',
|
||||
UniversalFileType.audio => 'audio/unknown',
|
||||
UniversalFileType.file => 'application/octet-stream',
|
||||
},
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
}
|
||||
cloudAttachments.add(cloudFile);
|
||||
}
|
||||
|
||||
// Send to server
|
||||
final response = await _apiClient.post(
|
||||
'/chat/$roomId/messages',
|
||||
final response = await _apiClient.request(
|
||||
editingTo == null
|
||||
? '/chat/$roomId/messages'
|
||||
: '/chat/$roomId/messages/${editingTo.id}',
|
||||
data: {
|
||||
'content': content,
|
||||
'attachments_id': attachments,
|
||||
'attachments_id': cloudAttachments.map((e) => e.id).toList(),
|
||||
'replied_message_id': replyingTo?.id,
|
||||
'forwarded_message_id': forwardingTo?.id,
|
||||
'meta': meta,
|
||||
'nonce': nonce,
|
||||
},
|
||||
options: Options(method: editingTo == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
|
||||
// Update with server response
|
||||
@ -380,4 +416,33 @@ class MessageRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<LocalChatMessage?> getMessageById(String messageId) async {
|
||||
try {
|
||||
// Attempt to get the message from the local database
|
||||
final localMessage =
|
||||
await (_database.select(_database.chatMessages)
|
||||
..where((tbl) => tbl.id.equals(messageId))).getSingleOrNull();
|
||||
if (localMessage != null) {
|
||||
return _database.companionToMessage(localMessage);
|
||||
}
|
||||
|
||||
// If not found locally, fetch from the server
|
||||
final response = await _apiClient.get(
|
||||
'/chat/${room.id}/messages/$messageId',
|
||||
);
|
||||
final remoteMessage = SnChatMessage.fromJson(response.data);
|
||||
final message = LocalChatMessage.fromRemoteMessage(
|
||||
remoteMessage,
|
||||
MessageStatus.sent,
|
||||
);
|
||||
|
||||
// Save the fetched message to the local database
|
||||
await _database.saveMessage(_database.messageToCompanion(message));
|
||||
return message;
|
||||
} catch (e) {
|
||||
// Handle errors
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +43,7 @@ abstract class SnChatMessage with _$SnChatMessage {
|
||||
@Default([]) List<SnCloudFile> attachments,
|
||||
@Default([]) List<SnChatReaction> reactions,
|
||||
String? repliedMessageId,
|
||||
SnChatMessage? repliedMessage,
|
||||
String? forwardedMessageId,
|
||||
SnChatMessage? forwardedMessage,
|
||||
required String senderId,
|
||||
required SnChatMember sender,
|
||||
required int chatRoomId,
|
||||
|
@ -260,7 +260,7 @@ $SnRealmCopyWith<$Res>? get realm {
|
||||
/// @nodoc
|
||||
mixin _$SnChatMessage {
|
||||
|
||||
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String? get content; String? get nonce; Map<String, dynamic> get meta; List<String> get membersMetioned; DateTime? get editedAt; List<SnCloudFile> get attachments; List<SnChatReaction> get reactions; String? get repliedMessageId; SnChatMessage? get repliedMessage; String? get forwardedMessageId; SnChatMessage? get forwardedMessage; String get senderId; SnChatMember get sender; int get chatRoomId;
|
||||
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String? get content; String? get nonce; Map<String, dynamic> get meta; List<String> get membersMetioned; DateTime? get editedAt; List<SnCloudFile> get attachments; List<SnChatReaction> get reactions; String? get repliedMessageId; String? get forwardedMessageId; String get senderId; SnChatMember get sender; int get chatRoomId;
|
||||
/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -273,16 +273,16 @@ $SnChatMessageCopyWith<SnChatMessage> get copyWith => _$SnChatMessageCopyWithImp
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other.meta, meta)&&const DeepCollectionEquality().equals(other.membersMetioned, membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.repliedMessage, repliedMessage) || other.repliedMessage == repliedMessage)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.forwardedMessage, forwardedMessage) || other.forwardedMessage == forwardedMessage)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other.meta, meta)&&const DeepCollectionEquality().equals(other.membersMetioned, membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(meta),const DeepCollectionEquality().hash(membersMetioned),editedAt,const DeepCollectionEquality().hash(attachments),const DeepCollectionEquality().hash(reactions),repliedMessageId,repliedMessage,forwardedMessageId,forwardedMessage,senderId,sender,chatRoomId);
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(meta),const DeepCollectionEquality().hash(membersMetioned),editedAt,const DeepCollectionEquality().hash(attachments),const DeepCollectionEquality().hash(reactions),repliedMessageId,forwardedMessageId,senderId,sender,chatRoomId);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, repliedMessage: $repliedMessage, forwardedMessageId: $forwardedMessageId, forwardedMessage: $forwardedMessage, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)';
|
||||
return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, forwardedMessageId: $forwardedMessageId, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)';
|
||||
}
|
||||
|
||||
|
||||
@ -293,11 +293,11 @@ abstract mixin class $SnChatMessageCopyWith<$Res> {
|
||||
factory $SnChatMessageCopyWith(SnChatMessage value, $Res Function(SnChatMessage) _then) = _$SnChatMessageCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, SnChatMessage? repliedMessage, String? forwardedMessageId, SnChatMessage? forwardedMessage, String senderId, SnChatMember sender, int chatRoomId
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, String? forwardedMessageId, String senderId, SnChatMember sender, int chatRoomId
|
||||
});
|
||||
|
||||
|
||||
$SnChatMessageCopyWith<$Res>? get repliedMessage;$SnChatMessageCopyWith<$Res>? get forwardedMessage;$SnChatMemberCopyWith<$Res> get sender;
|
||||
$SnChatMemberCopyWith<$Res> get sender;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
@ -310,7 +310,7 @@ class _$SnChatMessageCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? repliedMessage = freezed,Object? forwardedMessageId = freezed,Object? forwardedMessage = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? forwardedMessageId = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
@ -324,10 +324,8 @@ as List<String>,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ign
|
||||
as DateTime?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnCloudFile>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnChatReaction>,repliedMessageId: freezed == repliedMessageId ? _self.repliedMessageId : repliedMessageId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,repliedMessage: freezed == repliedMessage ? _self.repliedMessage : repliedMessage // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMessage?,forwardedMessageId: freezed == forwardedMessageId ? _self.forwardedMessageId : forwardedMessageId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,forwardedMessage: freezed == forwardedMessage ? _self.forwardedMessage : forwardedMessage // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMessage?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,forwardedMessageId: freezed == forwardedMessageId ? _self.forwardedMessageId : forwardedMessageId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable
|
||||
as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMember,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
@ -337,30 +335,6 @@ as int,
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMessageCopyWith<$Res>? get repliedMessage {
|
||||
if (_self.repliedMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnChatMessageCopyWith<$Res>(_self.repliedMessage!, (value) {
|
||||
return _then(_self.copyWith(repliedMessage: value));
|
||||
});
|
||||
}/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMessageCopyWith<$Res>? get forwardedMessage {
|
||||
if (_self.forwardedMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnChatMessageCopyWith<$Res>(_self.forwardedMessage!, (value) {
|
||||
return _then(_self.copyWith(forwardedMessage: value));
|
||||
});
|
||||
}/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMemberCopyWith<$Res> get sender {
|
||||
|
||||
return $SnChatMemberCopyWith<$Res>(_self.sender, (value) {
|
||||
@ -374,7 +348,7 @@ $SnChatMemberCopyWith<$Res> get sender {
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnChatMessage implements SnChatMessage {
|
||||
const _SnChatMessage({required this.createdAt, required this.updatedAt, this.deletedAt, required this.id, this.content, this.nonce, final Map<String, dynamic> meta = const {}, final List<String> membersMetioned = const [], this.editedAt, final List<SnCloudFile> attachments = const [], final List<SnChatReaction> reactions = const [], this.repliedMessageId, this.repliedMessage, this.forwardedMessageId, this.forwardedMessage, required this.senderId, required this.sender, required this.chatRoomId}): _meta = meta,_membersMetioned = membersMetioned,_attachments = attachments,_reactions = reactions;
|
||||
const _SnChatMessage({required this.createdAt, required this.updatedAt, this.deletedAt, required this.id, this.content, this.nonce, final Map<String, dynamic> meta = const {}, final List<String> membersMetioned = const [], this.editedAt, final List<SnCloudFile> attachments = const [], final List<SnChatReaction> reactions = const [], this.repliedMessageId, this.forwardedMessageId, required this.senderId, required this.sender, required this.chatRoomId}): _meta = meta,_membersMetioned = membersMetioned,_attachments = attachments,_reactions = reactions;
|
||||
factory _SnChatMessage.fromJson(Map<String, dynamic> json) => _$SnChatMessageFromJson(json);
|
||||
|
||||
@override final DateTime createdAt;
|
||||
@ -413,9 +387,7 @@ class _SnChatMessage implements SnChatMessage {
|
||||
}
|
||||
|
||||
@override final String? repliedMessageId;
|
||||
@override final SnChatMessage? repliedMessage;
|
||||
@override final String? forwardedMessageId;
|
||||
@override final SnChatMessage? forwardedMessage;
|
||||
@override final String senderId;
|
||||
@override final SnChatMember sender;
|
||||
@override final int chatRoomId;
|
||||
@ -433,16 +405,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other._meta, _meta)&&const DeepCollectionEquality().equals(other._membersMetioned, _membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.repliedMessage, repliedMessage) || other.repliedMessage == repliedMessage)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.forwardedMessage, forwardedMessage) || other.forwardedMessage == forwardedMessage)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other._meta, _meta)&&const DeepCollectionEquality().equals(other._membersMetioned, _membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(_meta),const DeepCollectionEquality().hash(_membersMetioned),editedAt,const DeepCollectionEquality().hash(_attachments),const DeepCollectionEquality().hash(_reactions),repliedMessageId,repliedMessage,forwardedMessageId,forwardedMessage,senderId,sender,chatRoomId);
|
||||
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(_meta),const DeepCollectionEquality().hash(_membersMetioned),editedAt,const DeepCollectionEquality().hash(_attachments),const DeepCollectionEquality().hash(_reactions),repliedMessageId,forwardedMessageId,senderId,sender,chatRoomId);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, repliedMessage: $repliedMessage, forwardedMessageId: $forwardedMessageId, forwardedMessage: $forwardedMessage, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)';
|
||||
return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, forwardedMessageId: $forwardedMessageId, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)';
|
||||
}
|
||||
|
||||
|
||||
@ -453,11 +425,11 @@ abstract mixin class _$SnChatMessageCopyWith<$Res> implements $SnChatMessageCopy
|
||||
factory _$SnChatMessageCopyWith(_SnChatMessage value, $Res Function(_SnChatMessage) _then) = __$SnChatMessageCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, SnChatMessage? repliedMessage, String? forwardedMessageId, SnChatMessage? forwardedMessage, String senderId, SnChatMember sender, int chatRoomId
|
||||
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map<String, dynamic> meta, List<String> membersMetioned, DateTime? editedAt, List<SnCloudFile> attachments, List<SnChatReaction> reactions, String? repliedMessageId, String? forwardedMessageId, String senderId, SnChatMember sender, int chatRoomId
|
||||
});
|
||||
|
||||
|
||||
@override $SnChatMessageCopyWith<$Res>? get repliedMessage;@override $SnChatMessageCopyWith<$Res>? get forwardedMessage;@override $SnChatMemberCopyWith<$Res> get sender;
|
||||
@override $SnChatMemberCopyWith<$Res> get sender;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
@ -470,7 +442,7 @@ class __$SnChatMessageCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? repliedMessage = freezed,Object? forwardedMessageId = freezed,Object? forwardedMessage = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? forwardedMessageId = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) {
|
||||
return _then(_SnChatMessage(
|
||||
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
@ -484,10 +456,8 @@ as List<String>,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ign
|
||||
as DateTime?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnCloudFile>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnChatReaction>,repliedMessageId: freezed == repliedMessageId ? _self.repliedMessageId : repliedMessageId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,repliedMessage: freezed == repliedMessage ? _self.repliedMessage : repliedMessage // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMessage?,forwardedMessageId: freezed == forwardedMessageId ? _self.forwardedMessageId : forwardedMessageId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,forwardedMessage: freezed == forwardedMessage ? _self.forwardedMessage : forwardedMessage // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMessage?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,forwardedMessageId: freezed == forwardedMessageId ? _self.forwardedMessageId : forwardedMessageId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable
|
||||
as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable
|
||||
as SnChatMember,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
@ -498,30 +468,6 @@ as int,
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMessageCopyWith<$Res>? get repliedMessage {
|
||||
if (_self.repliedMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnChatMessageCopyWith<$Res>(_self.repliedMessage!, (value) {
|
||||
return _then(_self.copyWith(repliedMessage: value));
|
||||
});
|
||||
}/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMessageCopyWith<$Res>? get forwardedMessage {
|
||||
if (_self.forwardedMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnChatMessageCopyWith<$Res>(_self.forwardedMessage!, (value) {
|
||||
return _then(_self.copyWith(forwardedMessage: value));
|
||||
});
|
||||
}/// Create a copy of SnChatMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnChatMemberCopyWith<$Res> get sender {
|
||||
|
||||
return $SnChatMemberCopyWith<$Res>(_self.sender, (value) {
|
||||
|
@ -84,19 +84,7 @@ _SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) =>
|
||||
.toList() ??
|
||||
const [],
|
||||
repliedMessageId: json['replied_message_id'] as String?,
|
||||
repliedMessage:
|
||||
json['replied_message'] == null
|
||||
? null
|
||||
: SnChatMessage.fromJson(
|
||||
json['replied_message'] as Map<String, dynamic>,
|
||||
),
|
||||
forwardedMessageId: json['forwarded_message_id'] as String?,
|
||||
forwardedMessage:
|
||||
json['forwarded_message'] == null
|
||||
? null
|
||||
: SnChatMessage.fromJson(
|
||||
json['forwarded_message'] as Map<String, dynamic>,
|
||||
),
|
||||
senderId: json['sender_id'] as String,
|
||||
sender: SnChatMember.fromJson(json['sender'] as Map<String, dynamic>),
|
||||
chatRoomId: (json['chat_room_id'] as num).toInt(),
|
||||
@ -116,9 +104,7 @@ Map<String, dynamic> _$SnChatMessageToJson(_SnChatMessage instance) =>
|
||||
'attachments': instance.attachments.map((e) => e.toJson()).toList(),
|
||||
'reactions': instance.reactions.map((e) => e.toJson()).toList(),
|
||||
'replied_message_id': instance.repliedMessageId,
|
||||
'replied_message': instance.repliedMessage?.toJson(),
|
||||
'forwarded_message_id': instance.forwardedMessageId,
|
||||
'forwarded_message': instance.forwardedMessage?.toJson(),
|
||||
'sender_id': instance.senderId,
|
||||
'sender': instance.sender.toJson(),
|
||||
'chat_room_id': instance.chatRoomId,
|
||||
|
@ -9,6 +9,7 @@ import 'package:island/database/message.dart';
|
||||
import 'package:island/database/message_repository.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/message.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
@ -17,6 +18,7 @@ import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:super_context_menu/super_context_menu.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'chat.dart';
|
||||
|
||||
@ -94,7 +96,13 @@ class MessagesNotifier
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendMessage(String content) async {
|
||||
Future<void> sendMessage(
|
||||
String content,
|
||||
List<UniversalFile> attachments, {
|
||||
SnChatMessage? replyingTo,
|
||||
SnChatMessage? forwardingTo,
|
||||
SnChatMessage? editingTo,
|
||||
}) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
@ -102,7 +110,28 @@ class MessagesNotifier
|
||||
|
||||
final nonce = const Uuid().v4();
|
||||
|
||||
final messageTask = repository.sendMessage(_roomId, content, nonce);
|
||||
final baseUrl = _ref.read(serverUrlProvider);
|
||||
final atk = await getFreshAtk(
|
||||
_ref.watch(tokenPairProvider),
|
||||
baseUrl,
|
||||
onRefreshed: (atk, rtk) {
|
||||
setTokenPair(_ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||
_ref.invalidate(tokenPairProvider);
|
||||
},
|
||||
);
|
||||
if (atk == null) throw Exception("Unauthorized");
|
||||
|
||||
final messageTask = repository.sendMessage(
|
||||
atk,
|
||||
baseUrl,
|
||||
_roomId,
|
||||
content,
|
||||
nonce,
|
||||
attachments: attachments,
|
||||
replyingTo: replyingTo,
|
||||
forwardingTo: forwardingTo,
|
||||
editingTo: editingTo,
|
||||
);
|
||||
final pendingMessage = repository.pendingMessages.values.firstWhereOrNull(
|
||||
(m) => m.roomId == _roomId && m.nonce == nonce,
|
||||
);
|
||||
@ -288,6 +317,18 @@ class MessagesNotifier
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
|
||||
try {
|
||||
final repository = await _ref.read(
|
||||
messageRepositoryProvider(_roomId).future,
|
||||
);
|
||||
return await repository.getMessageById(messageId);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RoutePage()
|
||||
@ -306,6 +347,10 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
final messageController = useTextEditingController();
|
||||
final scrollController = useScrollController();
|
||||
|
||||
final messageReplyingTo = useState<SnChatMessage?>(null);
|
||||
final messageForwardingTo = useState<SnChatMessage?>(null);
|
||||
final messageEditingTo = useState<SnChatMessage?>(null);
|
||||
|
||||
// Add scroll listener for pagination
|
||||
useEffect(() {
|
||||
void onScroll() {
|
||||
@ -340,9 +385,17 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
return () => subscription.cancel();
|
||||
}, [ws, chatRoom]);
|
||||
|
||||
final attachments = useState<List<UniversalFile>>([]);
|
||||
|
||||
void sendMessage() {
|
||||
if (messageController.text.trim().isNotEmpty) {
|
||||
messagesNotifier.sendMessage(messageController.text.trim());
|
||||
messagesNotifier.sendMessage(
|
||||
messageController.text.trim(),
|
||||
attachments.value,
|
||||
editingTo: messageEditingTo.value,
|
||||
forwardingTo: messageForwardingTo.value,
|
||||
replyingTo: messageReplyingTo.value,
|
||||
);
|
||||
messageController.clear();
|
||||
}
|
||||
}
|
||||
@ -410,11 +463,29 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
message: message,
|
||||
isCurrentUser:
|
||||
identity?.id == message.senderId,
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
case _MessageBubbleAction.delete:
|
||||
messagesNotifier.deleteMessage(
|
||||
message.id,
|
||||
);
|
||||
case _MessageBubbleAction.edit:
|
||||
messageEditingTo.value =
|
||||
message.toRemoteMessage();
|
||||
case _MessageBubbleAction.forward:
|
||||
messageForwardingTo.value =
|
||||
message.toRemoteMessage();
|
||||
case _MessageBubbleAction.reply:
|
||||
messageReplyingTo.value =
|
||||
message.toRemoteMessage();
|
||||
}
|
||||
},
|
||||
),
|
||||
loading:
|
||||
() => _MessageBubble(
|
||||
message: message,
|
||||
isCurrentUser: false,
|
||||
onAction: null,
|
||||
),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
);
|
||||
@ -442,6 +513,17 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
messageController: messageController,
|
||||
chatRoom: room!,
|
||||
onSend: sendMessage,
|
||||
onClear: () {
|
||||
if (messageEditingTo.value != null) {
|
||||
messageController.clear();
|
||||
}
|
||||
messageEditingTo.value = null;
|
||||
messageReplyingTo.value = null;
|
||||
messageForwardingTo.value = null;
|
||||
},
|
||||
messageEditingTo: messageEditingTo.value,
|
||||
messageReplyingTo: messageReplyingTo.value,
|
||||
messageForwardingTo: messageForwardingTo.value,
|
||||
),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
@ -456,11 +538,19 @@ class _ChatInput extends StatelessWidget {
|
||||
final TextEditingController messageController;
|
||||
final SnChat chatRoom;
|
||||
final VoidCallback onSend;
|
||||
final VoidCallback onClear;
|
||||
final SnChatMessage? messageReplyingTo;
|
||||
final SnChatMessage? messageForwardingTo;
|
||||
final SnChatMessage? messageEditingTo;
|
||||
|
||||
const _ChatInput({
|
||||
required this.messageController,
|
||||
required this.chatRoom,
|
||||
required this.onSend,
|
||||
required this.onClear,
|
||||
required this.messageReplyingTo,
|
||||
required this.messageForwardingTo,
|
||||
required this.messageEditingTo,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -468,7 +558,52 @@ class _ChatInput extends StatelessWidget {
|
||||
return Material(
|
||||
elevation: 8,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
child: Column(
|
||||
children: [
|
||||
if (messageReplyingTo != null ||
|
||||
messageForwardingTo != null ||
|
||||
messageEditingTo != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
messageReplyingTo != null
|
||||
? Symbols.reply
|
||||
: messageForwardingTo != null
|
||||
? Symbols.forward
|
||||
: Symbols.edit,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
messageReplyingTo != null
|
||||
? 'Replying to ${messageReplyingTo?.sender.account.nick}'
|
||||
: messageForwardingTo != null
|
||||
? 'Forwarding message'
|
||||
: 'Editing message',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, size: 20),
|
||||
onPressed: onClear,
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
@ -498,19 +633,80 @@ class _ChatInput extends StatelessWidget {
|
||||
],
|
||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MessageBubble extends StatelessWidget {
|
||||
class _MessageBubbleAction {
|
||||
static const String edit = "edit";
|
||||
static const String delete = "delete";
|
||||
static const String reply = "reply";
|
||||
static const String forward = "forward";
|
||||
}
|
||||
|
||||
class _MessageBubble extends HookConsumerWidget {
|
||||
final LocalChatMessage message;
|
||||
final bool isCurrentUser;
|
||||
final Function(String action)? onAction;
|
||||
|
||||
const _MessageBubble({required this.message, required this.isCurrentUser});
|
||||
const _MessageBubble({
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
required this.onAction,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final messagesNotifier = ref.watch(
|
||||
messagesProvider(message.roomId).notifier,
|
||||
);
|
||||
|
||||
final textColor = isCurrentUser ? Colors.white : Colors.black;
|
||||
|
||||
return ContextMenuWidget(
|
||||
menuProvider: (_) {
|
||||
if (onAction == null) return Menu(children: []);
|
||||
return Menu(
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
MenuAction(
|
||||
title: 'edit'.tr(),
|
||||
image: MenuImage.icon(Symbols.edit),
|
||||
callback: () {
|
||||
onAction!.call(_MessageBubbleAction.edit);
|
||||
},
|
||||
),
|
||||
if (isCurrentUser)
|
||||
MenuAction(
|
||||
title: 'delete'.tr(),
|
||||
image: MenuImage.icon(Symbols.delete),
|
||||
callback: () {
|
||||
onAction!.call(_MessageBubbleAction.delete);
|
||||
},
|
||||
),
|
||||
if (isCurrentUser) MenuSeparator(),
|
||||
MenuAction(
|
||||
title: 'reply'.tr(),
|
||||
image: MenuImage.icon(Symbols.reply),
|
||||
callback: () {
|
||||
onAction!.call(_MessageBubbleAction.reply);
|
||||
},
|
||||
),
|
||||
MenuAction(
|
||||
title: 'forward'.tr(),
|
||||
image: MenuImage.icon(Symbols.forward),
|
||||
callback: () {
|
||||
onAction!.call(_MessageBubbleAction.forward);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
@ -519,28 +715,48 @@ class _MessageBubble extends StatelessWidget {
|
||||
if (!isCurrentUser)
|
||||
ProfilePictureWidget(
|
||||
fileId:
|
||||
message.toRemoteMessage().sender.account.profile.pictureId,
|
||||
message
|
||||
.toRemoteMessage()
|
||||
.sender
|
||||
.account
|
||||
.profile
|
||||
.pictureId,
|
||||
radius: 18,
|
||||
),
|
||||
const Gap(8),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isCurrentUser
|
||||
? Theme.of(context).colorScheme.primary.withOpacity(0.8)
|
||||
? Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.8)
|
||||
: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (message.toRemoteMessage().repliedMessageId != null)
|
||||
_MessageQuoteWidget(
|
||||
message: message,
|
||||
textColor: textColor,
|
||||
isReply: true,
|
||||
),
|
||||
if (message.toRemoteMessage().forwardedMessageId != null)
|
||||
_MessageQuoteWidget(
|
||||
message: message,
|
||||
textColor: textColor,
|
||||
isReply: false,
|
||||
),
|
||||
Text(
|
||||
message.toRemoteMessage().content ?? '',
|
||||
style: TextStyle(
|
||||
color: isCurrentUser ? Colors.white : Colors.black,
|
||||
),
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
const Gap(4),
|
||||
Row(
|
||||
@ -548,11 +764,7 @@ class _MessageBubble extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.Hm().format(message.createdAt.toLocal()),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color:
|
||||
isCurrentUser ? Colors.white70 : Colors.black54,
|
||||
),
|
||||
style: TextStyle(fontSize: 10, color: textColor),
|
||||
),
|
||||
const Gap(4),
|
||||
if (isCurrentUser)
|
||||
@ -567,11 +779,18 @@ class _MessageBubble extends StatelessWidget {
|
||||
if (isCurrentUser)
|
||||
ProfilePictureWidget(
|
||||
fileId:
|
||||
message.toRemoteMessage().sender.account.profile.pictureId,
|
||||
message
|
||||
.toRemoteMessage()
|
||||
.sender
|
||||
.account
|
||||
.profile
|
||||
.pictureId,
|
||||
radius: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -600,3 +819,73 @@ class _MessageBubble extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _MessageQuoteWidget extends HookConsumerWidget {
|
||||
final LocalChatMessage message;
|
||||
final Color textColor;
|
||||
final bool isReply;
|
||||
|
||||
const _MessageQuoteWidget({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.textColor,
|
||||
required this.isReply,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final messagesNotifier = ref.watch(
|
||||
messagesProvider(message.roomId).notifier,
|
||||
);
|
||||
|
||||
return FutureBuilder<LocalChatMessage?>(
|
||||
future: messagesNotifier.fetchMessageById(
|
||||
isReply
|
||||
? message.toRemoteMessage().repliedMessageId!
|
||||
: message.toRemoteMessage().forwardedMessageId!,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 6),
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.2),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isReply)
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.reply, size: 16, color: textColor),
|
||||
Text(
|
||||
'Replying to ${snapshot.data!.toRemoteMessage().sender.account.nick}',
|
||||
).textColor(textColor).bold(),
|
||||
],
|
||||
)
|
||||
else
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.forward, size: 16, color: textColor),
|
||||
Text(
|
||||
'Forwarded from ${snapshot.data!.toRemoteMessage().sender.account.nick}',
|
||||
).textColor(textColor).bold(),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
snapshot.data!.toRemoteMessage().content ?? "",
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
).padding(bottom: 4);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user