👽 Update follow server side IM changes

This commit is contained in:
2024-11-18 23:59:08 +08:00
parent 6c5377d9fa
commit fd272ead37
9 changed files with 163 additions and 26 deletions

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:provider/provider.dart';
@ -176,9 +177,9 @@ class ChatMessageController extends ChangeNotifier {
switch (message.type) {
case 'messages.edit':
final body = message.body;
if (body['related_event'] != null) {
final idx = messages.indexWhere((x) => x.id == body['related_event']);
if (message.relatedEventId != null) {
final idx =
messages.indexWhere((x) => x.id == message.relatedEventId);
if (idx != -1) {
final newBody = message.body;
newBody.remove('related_event');
@ -186,17 +187,16 @@ class ChatMessageController extends ChangeNotifier {
body: newBody,
updatedAt: message.updatedAt,
);
if (_box!.containsKey(body['related_event'])) {
await _box!.put(body['related_event'], messages[idx]);
if (_box!.containsKey(message.relatedEventId)) {
await _box!.put(message.relatedEventId, messages[idx]);
}
}
}
case 'messages.delete':
final body = message.body;
if (body['related_event'] != null) {
messages.removeWhere((x) => x.id == body['related_event']);
if (_box!.containsKey(body['related_event'])) {
await _box!.delete(body['related_event']);
if (message.relatedEventId != null) {
messages.removeWhere((x) => x.id == message.relatedEventId);
if (_box!.containsKey(message.relatedEventId)) {
await _box!.delete(message.relatedEventId);
}
}
}
@ -208,6 +208,7 @@ class ChatMessageController extends ChangeNotifier {
int? quoteId,
int? relatedId,
List<String>? attachments,
SnChatMessage? editingMessage,
}) async {
if (channel == null) return;
const uuid = Uuid();
@ -216,7 +217,7 @@ class ChatMessageController extends ChangeNotifier {
'text': content,
'algorithm': 'plain',
if (quoteId != null) 'quote_event': quoteId,
if (relatedId != null) 'quote_event': relatedId,
if (relatedId != null) 'related_event': relatedId,
if (attachments != null && attachments.isNotEmpty)
'attachments': attachments,
};
@ -235,21 +236,40 @@ class ChatMessageController extends ChangeNotifier {
sender: profile!,
senderId: profile!.id,
quoteEventId: quoteId,
relatedEventId: relatedId,
);
_addUnconfirmedMessage(message);
// Send to server
try {
await _sn.client.post(
'/cgi/im/channels/${channel!.keyPath}/messages',
await _sn.client.request(
editingMessage != null
? '/cgi/im/channels/${channel!.keyPath}/messages/${editingMessage.id}'
: '/cgi/im/channels/${channel!.keyPath}/messages',
data: {
'type': type,
'uuid': nonce,
'body': body,
},
options: Options(
method: editingMessage != null ? 'PUT' : 'POST',
),
);
} catch (err) {
print(err);
// ignore
}
}
Future<void> deleteMessage(SnChatMessage message) async {
if (message.channelId != channel?.id) return;
try {
await _sn.client.delete(
'/cgi/im/channels/${channel!.keyPath}/messages/${message.id}',
);
messages.removeWhere((x) => x.id == message.id);
} catch (err) {
// ignore
}
}
@ -376,7 +396,11 @@ class ChatMessageController extends ChangeNotifier {
}
// Preload sender accounts
await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
final accountId = out
.where((ele) => ele.sender.accountId >= 0)
.map((ele) => ele.sender.accountId)
.toSet();
await _ud.listAccount(accountId);
return out;
}

View File

@ -121,8 +121,12 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
onReply: (value) {
_inputGlobalKey.currentState?.setReply(value);
},
onEdit: (value) {},
onDelete: (value) {},
onEdit: (value) {
_inputGlobalKey.currentState?.setEdit(value);
},
onDelete: (value) {
_inputGlobalKey.currentState?.deleteMessage(value);
},
);
},
),

View File

@ -74,13 +74,14 @@ class SnChatMessage with _$SnChatMessage {
@HiveField(2) required DateTime updatedAt,
@HiveField(3) required DateTime? deletedAt,
@HiveField(4) required String uuid,
@HiveField(5) required Map<String, dynamic> body,
@HiveField(5) @Default({}) Map<String, dynamic> body,
@HiveField(6) required String type,
@HiveField(7) required SnChannel channel,
@HiveField(8) required SnChannelMember sender,
@HiveField(9) required int channelId,
@HiveField(10) required int senderId,
@HiveField(11) required int? quoteEventId,
@HiveField(12) required int? relatedEventId,
SnChatMessagePreload? preload,
}) = _SnChatMessage;

View File

@ -1085,6 +1085,8 @@ mixin _$SnChatMessage {
int get senderId => throw _privateConstructorUsedError;
@HiveField(11)
int? get quoteEventId => throw _privateConstructorUsedError;
@HiveField(12)
int? get relatedEventId => throw _privateConstructorUsedError;
SnChatMessagePreload? get preload => throw _privateConstructorUsedError;
/// Serializes this SnChatMessage to a JSON map.
@ -1116,6 +1118,7 @@ abstract class $SnChatMessageCopyWith<$Res> {
@HiveField(9) int channelId,
@HiveField(10) int senderId,
@HiveField(11) int? quoteEventId,
@HiveField(12) int? relatedEventId,
SnChatMessagePreload? preload});
$SnChannelCopyWith<$Res> get channel;
@ -1150,6 +1153,7 @@ class _$SnChatMessageCopyWithImpl<$Res, $Val extends SnChatMessage>
Object? channelId = null,
Object? senderId = null,
Object? quoteEventId = freezed,
Object? relatedEventId = freezed,
Object? preload = freezed,
}) {
return _then(_value.copyWith(
@ -1201,6 +1205,10 @@ class _$SnChatMessageCopyWithImpl<$Res, $Val extends SnChatMessage>
? _value.quoteEventId
: quoteEventId // ignore: cast_nullable_to_non_nullable
as int?,
relatedEventId: freezed == relatedEventId
? _value.relatedEventId
: relatedEventId // ignore: cast_nullable_to_non_nullable
as int?,
preload: freezed == preload
? _value.preload
: preload // ignore: cast_nullable_to_non_nullable
@ -1264,6 +1272,7 @@ abstract class _$$SnChatMessageImplCopyWith<$Res>
@HiveField(9) int channelId,
@HiveField(10) int senderId,
@HiveField(11) int? quoteEventId,
@HiveField(12) int? relatedEventId,
SnChatMessagePreload? preload});
@override
@ -1299,6 +1308,7 @@ class __$$SnChatMessageImplCopyWithImpl<$Res>
Object? channelId = null,
Object? senderId = null,
Object? quoteEventId = freezed,
Object? relatedEventId = freezed,
Object? preload = freezed,
}) {
return _then(_$SnChatMessageImpl(
@ -1350,6 +1360,10 @@ class __$$SnChatMessageImplCopyWithImpl<$Res>
? _value.quoteEventId
: quoteEventId // ignore: cast_nullable_to_non_nullable
as int?,
relatedEventId: freezed == relatedEventId
? _value.relatedEventId
: relatedEventId // ignore: cast_nullable_to_non_nullable
as int?,
preload: freezed == preload
? _value.preload
: preload // ignore: cast_nullable_to_non_nullable
@ -1368,13 +1382,14 @@ class _$SnChatMessageImpl extends _SnChatMessage {
@HiveField(2) required this.updatedAt,
@HiveField(3) required this.deletedAt,
@HiveField(4) required this.uuid,
@HiveField(5) required final Map<String, dynamic> body,
@HiveField(5) final Map<String, dynamic> body = const {},
@HiveField(6) required this.type,
@HiveField(7) required this.channel,
@HiveField(8) required this.sender,
@HiveField(9) required this.channelId,
@HiveField(10) required this.senderId,
@HiveField(11) required this.quoteEventId,
@HiveField(12) required this.relatedEventId,
this.preload})
: _body = body,
super._();
@ -1399,6 +1414,7 @@ class _$SnChatMessageImpl extends _SnChatMessage {
final String uuid;
final Map<String, dynamic> _body;
@override
@JsonKey()
@HiveField(5)
Map<String, dynamic> get body {
if (_body is EqualUnmodifiableMapView) return _body;
@ -1425,11 +1441,14 @@ class _$SnChatMessageImpl extends _SnChatMessage {
@HiveField(11)
final int? quoteEventId;
@override
@HiveField(12)
final int? relatedEventId;
@override
final SnChatMessagePreload? preload;
@override
String toString() {
return 'SnChatMessage(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, uuid: $uuid, body: $body, type: $type, channel: $channel, sender: $sender, channelId: $channelId, senderId: $senderId, quoteEventId: $quoteEventId, preload: $preload)';
return 'SnChatMessage(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, uuid: $uuid, body: $body, type: $type, channel: $channel, sender: $sender, channelId: $channelId, senderId: $senderId, quoteEventId: $quoteEventId, relatedEventId: $relatedEventId, preload: $preload)';
}
@override
@ -1455,6 +1474,8 @@ class _$SnChatMessageImpl extends _SnChatMessage {
other.senderId == senderId) &&
(identical(other.quoteEventId, quoteEventId) ||
other.quoteEventId == quoteEventId) &&
(identical(other.relatedEventId, relatedEventId) ||
other.relatedEventId == relatedEventId) &&
(identical(other.preload, preload) || other.preload == preload));
}
@ -1474,6 +1495,7 @@ class _$SnChatMessageImpl extends _SnChatMessage {
channelId,
senderId,
quoteEventId,
relatedEventId,
preload);
/// Create a copy of SnChatMessage
@ -1499,13 +1521,14 @@ abstract class _SnChatMessage extends SnChatMessage {
@HiveField(2) required final DateTime updatedAt,
@HiveField(3) required final DateTime? deletedAt,
@HiveField(4) required final String uuid,
@HiveField(5) required final Map<String, dynamic> body,
@HiveField(5) final Map<String, dynamic> body,
@HiveField(6) required final String type,
@HiveField(7) required final SnChannel channel,
@HiveField(8) required final SnChannelMember sender,
@HiveField(9) required final int channelId,
@HiveField(10) required final int senderId,
@HiveField(11) required final int? quoteEventId,
@HiveField(12) required final int? relatedEventId,
final SnChatMessagePreload? preload}) = _$SnChatMessageImpl;
const _SnChatMessage._() : super._();
@ -1549,6 +1572,9 @@ abstract class _SnChatMessage extends SnChatMessage {
@HiveField(11)
int? get quoteEventId;
@override
@HiveField(12)
int? get relatedEventId;
@override
SnChatMessagePreload? get preload;
/// Create a copy of SnChatMessage

View File

@ -163,13 +163,14 @@ class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
channelId: fields[9] as int,
senderId: fields[10] as int,
quoteEventId: fields[11] as int?,
relatedEventId: fields[12] as int?,
);
}
@override
void write(BinaryWriter writer, _$SnChatMessageImpl obj) {
writer
..writeByte(12)
..writeByte(13)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@ -192,6 +193,8 @@ class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
..write(obj.senderId)
..writeByte(11)
..write(obj.quoteEventId)
..writeByte(12)
..write(obj.relatedEventId)
..writeByte(5)
..write(obj.body);
}
@ -306,13 +309,14 @@ _$SnChatMessageImpl _$$SnChatMessageImplFromJson(Map<String, dynamic> json) =>
? null
: DateTime.parse(json['deleted_at'] as String),
uuid: json['uuid'] as String,
body: json['body'] as Map<String, dynamic>,
body: json['body'] as Map<String, dynamic>? ?? const {},
type: json['type'] as String,
channel: SnChannel.fromJson(json['channel'] as Map<String, dynamic>),
sender: SnChannelMember.fromJson(json['sender'] as Map<String, dynamic>),
channelId: (json['channel_id'] as num).toInt(),
senderId: (json['sender_id'] as num).toInt(),
quoteEventId: (json['quote_event_id'] as num?)?.toInt(),
relatedEventId: (json['related_event_id'] as num?)?.toInt(),
preload: json['preload'] == null
? null
: SnChatMessagePreload.fromJson(
@ -333,6 +337,7 @@ Map<String, dynamic> _$$SnChatMessageImplToJson(_$SnChatMessageImpl instance) =>
'channel_id': instance.channelId,
'sender_id': instance.senderId,
'quote_event_id': instance.quoteEventId,
'related_event_id': instance.relatedEventId,
'preload': instance.preload?.toJson(),
};

View File

@ -147,6 +147,18 @@ class ChatMessage extends StatelessWidget {
content: data.body['text'],
isAutoWarp: true,
),
if (data.type == 'messages.delete' &&
data.relatedEventId != null)
Row(
children: [
const Icon(Symbols.delete),
const Gap(8),
Text(
'messageDeleted'
.tr(args: ['#${data.relatedEventId}']),
),
],
),
],
),
)

View File

@ -25,7 +25,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
bool _isBusy = false;
double? _progress;
SnChatMessage? _replyingMessage;
SnChatMessage? _replyingMessage, _editingMessage;
final TextEditingController _contentController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@ -34,6 +34,26 @@ class ChatMessageInputState extends State<ChatMessageInput> {
setState(() => _replyingMessage = value);
}
void setEdit(SnChatMessage? value) {
setState(() => _editingMessage = value);
}
Future<void> deleteMessage(SnChatMessage message) async {
final confirm = await context.showConfirmDialog(
'messageDelete'.tr(args: ['#${message.id}']),
'messageDeleteDescription'.tr(),
);
if (!confirm) return;
if (!mounted) return;
setState(() => _isBusy = true);
await widget.controller.deleteMessage(message);
if (!mounted) return;
setState(() => _isBusy = false);
}
Future<void> _sendMessage() async {
if (_isBusy) return;
@ -89,10 +109,13 @@ class ChatMessageInputState extends State<ChatMessageInput> {
.where((e) => e.attachment != null)
.map((e) => e.attachment!.rid)
.toList(),
relatedId: _editingMessage?.id,
quoteId: _replyingMessage?.id,
editingMessage: _editingMessage,
);
_contentController.clear();
_attachments.clear();
_editingMessage = null;
_replyingMessage = null;
setState(() => _isBusy = false);
@ -184,6 +207,42 @@ class ChatMessageInputState extends State<ChatMessageInput> {
),
).height(_replyingMessage != null ? 54 + 8 : 0, animate: true).animate(
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Padding(
padding: _editingMessage != null
? const EdgeInsets.only(top: 8)
: EdgeInsets.zero,
child: _editingMessage != null
? MaterialBanner(
padding: const EdgeInsets.only(left: 16.0),
leading: const Icon(Symbols.edit),
content: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_editingMessage?.body['text'] != null)
MarkdownTextContent(
content: _editingMessage?.body['text'],
),
],
),
),
actions: [
TextButton(
child: Text('cancel'.tr()),
onPressed: () {
setState(() => _editingMessage = null);
},
),
],
)
: const SizedBox.shrink(),
),
).height(_editingMessage != null ? 54 + 8 : 0, animate: true).animate(
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
SizedBox(
height: 56,
child: Row(