diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e10409..c5c2697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,4 +11,7 @@ ### 1.0.5 * Optimize processing of incorrect data ### 1.0.6 - * Update sending messages without notification to refresh conversation messsage \ No newline at end of file + * Update sending messages without notification to refresh conversation messsage +### 1.0.7 + * Support message receipts + \ No newline at end of file diff --git a/README.md b/README.md index 72142a8..9a02ffa 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ #### 安装 ``` dependencies: - wukongimfluttersdk: ^1.0.6 + wukongimfluttersdk: ^1.0.7 ``` #### 引入 ```dart diff --git a/example/lib/chat.dart b/example/lib/chat.dart index c17974a..8afa4c6 100644 --- a/example/lib/chat.dart +++ b/example/lib/chat.dart @@ -1,11 +1,13 @@ import 'package:example/const.dart'; import 'package:flutter/material.dart'; import 'package:wukongimfluttersdk/entity/channel.dart'; +import 'package:wukongimfluttersdk/entity/msg.dart'; import 'package:wukongimfluttersdk/model/wk_card_content.dart'; import 'package:wukongimfluttersdk/model/wk_image_content.dart'; import 'package:wukongimfluttersdk/model/wk_text_content.dart'; import 'package:wukongimfluttersdk/model/wk_video_content.dart'; import 'package:wukongimfluttersdk/model/wk_voice_content.dart'; +import 'package:wukongimfluttersdk/proto/proto.dart'; import 'package:wukongimfluttersdk/type/const.dart'; import 'package:wukongimfluttersdk/wkim.dart'; @@ -72,6 +74,10 @@ class ChatListDataState extends State { WKIM.shared.messageManager.addOnNewMsgListener('chat', (msgs) { setState(() { for (var i = 0; i < msgs.length; i++) { + if (msgs[i].setting.receipt == 1) { + // 消息需要回执 + testReceipt(msgs[i]); + } msgList.add(UIMsg(msgs[i])); } }); @@ -85,6 +91,7 @@ class ChatListDataState extends State { msgList[i].wkMsg.messageID = wkMsg.messageID; msgList[i].wkMsg.messageSeq = wkMsg.messageSeq; msgList[i].wkMsg.status = wkMsg.status; + msgList[i].wkMsg.wkMsgExtra = wkMsg.wkMsgExtra; break; } } @@ -92,6 +99,24 @@ class ChatListDataState extends State { }); } + // 模拟同步消息扩展后保存到db + testReceipt(WKMsg wkMsg) async { + if (wkMsg.viewed == 0) { + var maxVersion = await WKIM.shared.messageManager + .getMaxExtraVersionWithChannel(channelID, channelType); + var extra = WKMsgExtra(); + extra.messageID = wkMsg.messageID; + extra.channelID = channelID; + extra.channelType = channelType; + extra.readed = 1; + extra.readedCount = 1; + extra.extraVersion = maxVersion + 1; + List list = []; + list.add(extra); + WKIM.shared.messageManager.saveRemoteExtraMsg(list); + } + } + getMsgList() { WKIM.shared.messageManager.getOrSyncHistoryMessages( channelID, channelType, 0, true, 0, 100, 0, (list) { @@ -274,10 +299,11 @@ class ChatListDataState extends State { onPressed: () { if (content != '') { _textEditingController.text = ''; - + Setting setting = Setting(); + setting.receipt = 1; //开启回执 WKTextContent text = WKTextContent(content); - WKIM.shared.messageManager - .sendMessage(text, WKChannel(channelID, channelType)); + WKIM.shared.messageManager.sendMessageWithSetting( + text, WKChannel(channelID, channelType), setting); // WKImageContent imageContent = WKImageContent(100, 200); // imageContent.localPath = 'addskds'; // WKIM.shared.messageManager.sendMessage( diff --git a/example/lib/im.dart b/example/lib/im.dart index 51ad94f..8adf63b 100644 --- a/example/lib/im.dart +++ b/example/lib/im.dart @@ -42,21 +42,21 @@ class IMUtils { WKImageContent imageContent = wkMsg.messageContent! as WKImageContent; imageContent.url = 'xxxxxx'; wkMsg.messageContent = imageContent; - back(wkMsg); + back(true, wkMsg); } if (wkMsg.contentType == WkMessageContentType.voice) { // todo 上传语音 WKVoiceContent voiceContent = wkMsg.messageContent! as WKVoiceContent; voiceContent.url = 'xxxxxx'; wkMsg.messageContent = voiceContent; - back(wkMsg); + back(true, wkMsg); } else if (wkMsg.contentType == WkMessageContentType.video) { WKVideoContent videoContent = wkMsg.messageContent! as WKVideoContent; // todo 上传封面及视频 videoContent.cover = 'xxxxxx'; videoContent.url = 'ssssss'; wkMsg.messageContent = videoContent; - back(wkMsg); + back(true, wkMsg); } }); } diff --git a/example/lib/msg.dart b/example/lib/msg.dart index bf7c292..6a14734 100644 --- a/example/lib/msg.dart +++ b/example/lib/msg.dart @@ -11,7 +11,11 @@ class UIMsg { if (wkMsg.messageContent == null) { return ''; } - return wkMsg.messageContent!.displayText(); + var readCount = 0; + if (wkMsg.wkMsgExtra != null) { + readCount = wkMsg.wkMsgExtra!.readedCount; + } + return "${wkMsg.messageContent!.displayText()} [是否需要回执:${wkMsg.setting.receipt}],[已读数量:$readCount]"; } String getShowTime() { diff --git a/example/pubspec.lock b/example/pubspec.lock index 33be2e7..c91da82 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -499,7 +499,7 @@ packages: path: ".." relative: true source: path - version: "1.0.6" + version: "1.0.7" x25519: dependency: transitive description: diff --git a/lib/db/message.dart b/lib/db/message.dart index f757af6..fd2925e 100644 --- a/lib/db/message.dart +++ b/lib/db/message.dart @@ -87,6 +87,30 @@ class MessaggeDB { return wkMsg; } + Future> queryWithMessageIds(List messageIds) async { + StringBuffer sb = StringBuffer(); + for (int i = 0, size = messageIds.length; i < size; i++) { + if (i != 0) { + sb.write(","); + } + sb.write("'"); + sb.write(messageIds[i]); + sb.write("'"); + } + + String sql = + "select $messageCols,$extraCols from ${WKDBConst.tableMessage} LEFT JOIN ${WKDBConst.tableMessageExtra} ON ${WKDBConst.tableMessage}.message_id=${WKDBConst.tableMessageExtra}.message_id WHERE ${WKDBConst.tableMessage}.message_id in (${sb.toString()})"; + List list = []; + List> results = + await WKDBHelper.shared.getDB().rawQuery(sql); + if (results.isNotEmpty) { + for (Map data in results) { + list.add(WKDBConst.serializeWKMsg(data)); + } + } + return list; + } + Future> queryReactions(String messageID) async { String sql = "select * from ${WKDBConst.tableMessageReaction} where message_id='$messageID' and is_deleted=0 ORDER BY created_at desc"; @@ -536,7 +560,7 @@ class MessaggeDB { return msgs; } - insertOrUpdateMsgExtras(List list) async { + Future insertOrUpdateMsgExtras(List list) async { List msgIds = []; for (int i = 0, size = list.length; i < size; i++) { if (list[i].messageID != '') { @@ -574,6 +598,21 @@ class MessaggeDB { } }); } + return true; + } + + Future queryMaxExtraVersionWithChannel( + String channelID, int channelType) async { + int extraVersion = 0; + String sql = + "select max(extra_version) extra_version from ${WKDBConst.tableMessageExtra} where channel_id ='$channelID' and channel_type=$channelType"; + List> list = + await WKDBHelper.shared.getDB().rawQuery(sql); + if (list.isNotEmpty) { + dynamic data = list[0]; + extraVersion = WKDBConst.readInt(data, 'extra_version'); + } + return extraVersion; } Future> queryMsgExtrasWithMsgIds(List msgIds) async { @@ -720,7 +759,6 @@ class MessaggeDB { map['is_mutual_deleted'] = extra.isMutualDeleted; map['content_edit'] = extra.contentEdit; map['edited_at'] = extra.editedAt; - map['edited_at'] = extra.editedAt; map['need_upload'] = extra.needUpload; map['message_id'] = extra.messageID; return map; diff --git a/lib/manager/connect_manager.dart b/lib/manager/connect_manager.dart index 984773d..846f48c 100644 --- a/lib/manager/connect_manager.dart +++ b/lib/manager/connect_manager.dart @@ -343,10 +343,15 @@ class WKConnectionManager { sendMessage(WKMsg wkMsg) { SendPacket packet = SendPacket(); + packet.setting = wkMsg.setting; + packet.header.noPersist = wkMsg.header.noPersist; + packet.header.showUnread = wkMsg.header.redDot; + packet.header.syncOnce = wkMsg.header.syncOnce; packet.channelID = wkMsg.channelID; packet.channelType = wkMsg.channelType; packet.clientSeq = wkMsg.clientSeq; packet.clientMsgNO = wkMsg.clientMsgNO; + packet.topic = wkMsg.topicID; packet.payload = wkMsg.content; _addSendingMsg(packet); _sendPacket(packet); @@ -382,6 +387,7 @@ class WKConnectionManager { msg.header.redDot = recvMsg.header.showUnread; msg.header.noPersist = recvMsg.header.noPersist; msg.header.syncOnce = recvMsg.header.syncOnce; + msg.setting = recvMsg.setting; msg.channelType = recvMsg.channelType; msg.channelID = recvMsg.channelID; msg.content = recvMsg.payload; @@ -389,8 +395,6 @@ class WKConnectionManager { msg.messageSeq = recvMsg.messageSeq; msg.timestamp = recvMsg.messageTime; msg.fromUID = recvMsg.fromUID; - msg.setting = recvMsg.setting; - msg.clientMsgNO = recvMsg.clientMsgNO; msg.status = WKSendMsgResult.sendSuccess; msg.topicID = recvMsg.topic; msg.orderSeq = await WKIM.shared.messageManager diff --git a/lib/manager/message_manager.dart b/lib/manager/message_manager.dart index 200248b..1c16472 100644 --- a/lib/manager/message_manager.dart +++ b/lib/manager/message_manager.dart @@ -7,6 +7,7 @@ import 'package:wukongimfluttersdk/db/const.dart'; import 'package:wukongimfluttersdk/db/message.dart'; import 'package:wukongimfluttersdk/entity/msg.dart'; import 'package:wukongimfluttersdk/model/wk_media_message_content.dart'; +import 'package:wukongimfluttersdk/proto/proto.dart'; import 'package:wukongimfluttersdk/type/const.dart'; import '../entity/channel.dart'; @@ -23,7 +24,8 @@ class WKMessageManager { final Map _msgContentList = HashMap(); - Function(WKMsg wkMsg, Function(WKMsg wkMsg))? _uploadAttachmentBack; + Function(WKMsg wkMsg, Function(bool isSuccess, WKMsg wkMsg))? + _uploadAttachmentBack; Function(WKMsg liMMsg)? _msgInsertedBack; HashMap)>? _newMsgBack; HashMap? _refreshMsgBack; @@ -105,6 +107,52 @@ class WKMessageManager { return messageSeq * wkOrderSeqFactor; } + Future updateViewedAt(int viewedAt, String clientMsgNO) async { + dynamic json = {}; + json['viewed'] = 1; + json['viewed_at'] = viewedAt; + return MessaggeDB.shared + .updateMsgWithFieldAndClientMsgNo(json, clientMsgNO); + } + + Future getMaxExtraVersionWithChannel( + String channelID, int channelType) async { + return MessaggeDB.shared + .queryMaxExtraVersionWithChannel(channelID, channelType); + } + + saveRemoteExtraMsg(List list) async { + MessaggeDB.shared.insertOrUpdateMsgExtras(list); + List msgIds = []; + for (var extra in list) { + msgIds.add(extra.messageID); + } + var msgList = await MessaggeDB.shared.queryWithMessageIds(msgIds); + for (var msg in msgList) { + for (var extra in list) { + msg.wkMsgExtra ??= WKMsgExtra(); + if (msg.messageID == extra.messageID) { + msg.wkMsgExtra!.readed = extra.readed; + msg.wkMsgExtra!.readedCount = extra.readedCount; + msg.wkMsgExtra!.unreadCount = extra.unreadCount; + msg.wkMsgExtra!.revoke = extra.revoke; + msg.wkMsgExtra!.revoker = extra.revoker; + msg.wkMsgExtra!.isMutualDeleted = extra.isMutualDeleted; + msg.wkMsgExtra!.editedAt = extra.editedAt; + msg.wkMsgExtra!.contentEdit = extra.contentEdit; + msg.wkMsgExtra!.extraVersion = extra.extraVersion; + if (extra.contentEdit != '') { + dynamic contentJson = jsonDecode(extra.contentEdit); + msg.wkMsgExtra!.messageContent = WKIM.shared.messageManager + .getMessageModel(WkMessageContentType.text, contentJson); + } + break; + } + } + setRefreshMsg(msg); + } + } + void setSyncChannelMsgListener( String channelID, int channelType, @@ -317,13 +365,30 @@ class WKMessageManager { _msgInsertedBack = insertListener; } - addOnUploadAttachmentListener(Function(WKMsg, Function(WKMsg)) back) { + addOnUploadAttachmentListener(Function(WKMsg, Function(bool, WKMsg)) back) { _uploadAttachmentBack = back; } sendMessage(WKMessageContent messageContent, WKChannel channel) async { + var header = MessageHeader(); + header.redDot = true; + sendMessageWithSettingAndHeader(messageContent, channel, Setting(), header); + } + + sendMessageWithSetting( + WKMessageContent messageContent, WKChannel channel, Setting setting) { + var header = MessageHeader(); + header.redDot = true; + sendMessageWithSettingAndHeader(messageContent, channel, setting, header); + } + + sendMessageWithSettingAndHeader(WKMessageContent messageContent, + WKChannel channel, Setting setting, MessageHeader header) async { WKMsg wkMsg = WKMsg(); + wkMsg.setting = setting; + wkMsg.header = header; wkMsg.messageContent = messageContent; + wkMsg.topicID = messageContent.topicId; wkMsg.channelID = channel.channelID; wkMsg.channelType = channel.channelType; wkMsg.fromUID = WKIM.shared.options.uid!; @@ -347,7 +412,12 @@ class WKMessageManager { if (wkMsg.messageContent is WKMediaMessageContent) { // 附件消息 if (_uploadAttachmentBack != null) { - _uploadAttachmentBack!(wkMsg, (uploadedMsg) { + _uploadAttachmentBack!(wkMsg, (isSuccess, uploadedMsg) { + if (!isSuccess) { + wkMsg.status = WKSendMsgResult.sendFail; + updateMsgStatusFail(wkMsg.clientSeq); + return; + } // 重新编码消息正文 Map json = uploadedMsg.messageContent!.encodeJson(); json['type'] = uploadedMsg.contentType; diff --git a/lib/model/wk_message_content.dart b/lib/model/wk_message_content.dart index 1427539..b1c1263 100644 --- a/lib/model/wk_message_content.dart +++ b/lib/model/wk_message_content.dart @@ -1,6 +1,7 @@ class WKMessageContent { var contentType = 0; String content = ""; + String topicId = ""; Map encodeJson() { return {}; } diff --git a/lib/model/wk_video_content.dart b/lib/model/wk_video_content.dart index 82b2af8..8f5d67a 100644 --- a/lib/model/wk_video_content.dart +++ b/lib/model/wk_video_content.dart @@ -22,7 +22,8 @@ class WKVideoContent extends WKMediaMessageContent { 'size': size, 'width': width, 'height': height, - 'second': second + 'second': second, + 'url': url }; } @@ -35,6 +36,7 @@ class WKVideoContent extends WKMediaMessageContent { width = readInt(json, 'width'); height = readInt(json, 'height'); second = readInt(json, 'second'); + url = readString(json, 'url'); return this; } diff --git a/pubspec.yaml b/pubspec.yaml index fde42ac..5e97f28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ description: wukong IM flutter sdk # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.6 +version: 1.0.7 homepage: https://github.com/WuKongIM/WuKongIMFlutterSDK environment: