From 66fb59ac6740b2155228dc2e7cbd6b2e003d6730 Mon Sep 17 00:00:00 2001 From: SL Date: Sat, 26 Apr 2025 12:59:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E6=96=B0=E5=A2=9E=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=A2=91=E9=81=93=E5=A4=B4=E5=83=8F=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +- CHANGELOG.md | 4 +- example/lib/http.dart | 516 +++++++++++------------- example/lib/ui_conversation.dart | 21 +- lib/manager/channel_manager.dart | 46 ++- lib/manager/channel_member_manager.dart | 96 ++--- lib/manager/cmd_manager.dart | 46 ++- lib/manager/connect_manager.dart | 1 + lib/manager/conversation_manager.dart | 222 ++++++---- pubspec.yaml | 2 +- 10 files changed, 512 insertions(+), 445 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 173967d..88b3e04 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "DockerRun.DisableDockerrc": true + "DockerRun.DisableDockerrc": true, + "java.compile.nullAnalysis.mode": "automatic" } diff --git a/CHANGELOG.md b/CHANGELOG.md index bdda252..9ca0380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -125,4 +125,6 @@ ### 1.6.2 * fix: 修改数据库解码错误数据导致oom ### 1.6.3 - * fix: 优化在未收到服务端心跳消息时主动断开重连 \ No newline at end of file + * fix: 优化在未收到服务端心跳消息时主动断开重连 +### 1.6.4 + * fix: 新增监听头像改变事件 \ No newline at end of file diff --git a/example/lib/http.dart b/example/lib/http.dart index ce211d2..deeec3c 100644 --- a/example/lib/http.dart +++ b/example/lib/http.dart @@ -13,125 +13,126 @@ class HttpUtils { // static String apiURL = "https://api.githubim.com"; static String apiURL = "http://62.234.8.38:7090/v1"; // static String apiURL = "http://175.27.245.108:15001"; - static getAvatarUrl(String uid) { + + static Dio? _dio; + + /// Get Dio instance with trust all certificates configuration + static Dio get dio { + if (_dio == null) { + final httpClient = HttpClient(); + httpClient.badCertificateCallback = + (X509Certificate cert, String host, int port) => true; // Trust all certificates + + _dio = Dio(BaseOptions( + baseUrl: apiURL, + // 允许所有状态码,避免自动抛出异常 + validateStatus: (status) => true, + )); + (_dio!.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = + (client) => httpClient; + } + return _dio!; + } + + static String getAvatarUrl(String uid) { return "$apiURL/users/$uid/avatar"; } - static getGroupAvatarUrl(String gid) { + static String getGroupAvatarUrl(String gid) { return "$apiURL/groups/$gid/avatar"; } static Future login(String uid, String token) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; - final response = await dio.post("$apiURL/user/login", data: { - 'uid': uid, - 'token': token, - 'device_flag': 0, - 'device_level': 1 - }); try { + final response = await dio.post("/user/login", data: { + 'uid': uid, + 'token': token, + 'device_flag': 0, + 'device_level': 1 + }); + if (response.statusCode == HttpStatus.ok) { UserInfo.name = response.data['name']; } + return response.statusCode ?? HttpStatus.badRequest; } catch (e) { - print('获取用户信息失败'); + print('Login error: $e'); + return HttpStatus.internalServerError; } - - return response.statusCode!; } static Future getIP(String uid) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; - String ip = ''; try { - final response = await dio.get('$apiURL/users/$uid/route'); + final response = await dio.get('/users/$uid/route'); if (response.statusCode == HttpStatus.ok) { - ip = response.data['tcp_addr']; + return response.data['tcp_addr'] ?? ''; } } catch (e) { - ip = ''; + print('Get IP error: $e'); } - - return ip; + return ''; } - static syncConversation(String lastSsgSeqs, int msgCount, int version, + static Future syncConversation(String lastSsgSeqs, int msgCount, int version, Function(WKSyncConversation) back) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; - - final response = await dio.post('$apiURL/conversation/sync', data: { - "login_uid": UserInfo.uid, // 当前登录用户uid - "version": version, // 当前客户端的会话最大版本号(从保存的结果里取最大的version,如果本地没有数据则传0), - "last_msg_seqs": - lastSsgSeqs, // 客户端所有频道会话的最后一条消息序列号拼接出来的同步串 格式: channelID:channelType:last_msg_seq|channelID:channelType:last_msg_seq (此字段非必填,如果不填就获取全量数据,填写了获取增量数据,看你自己的需求。) - "msg_count": 10, // 每个会话获取最大的消息数量,一般为app点进去第一屏的数据 - "device_uuid": UserInfo.uid, - }); - // print(response.data); - WKSyncConversation conversation = WKSyncConversation(); - conversation.conversations = []; - - if (response.statusCode == HttpStatus.ok) { - try { - var list = response.data['conversations']; - // var list = jsonDecode(response.data); - for (int i = 0; i < list.length; i++) { - var json = list[i]; - WKSyncConvMsg convMsg = WKSyncConvMsg(); - convMsg.channelID = json['channel_id']; - convMsg.channelType = json['channel_type']; - convMsg.unread = json['unread'] ?? 0; - convMsg.timestamp = json['timestamp']; - convMsg.lastMsgSeq = json['last_msg_seq']; - convMsg.lastClientMsgNO = json['last_client_msg_no']; - convMsg.version = json['version']; - var msgListJson = json['recents'] as List; - List msgList = []; - if (msgListJson.isNotEmpty) { - for (int j = 0; j < msgListJson.length; j++) { - var msgJson = msgListJson[j]; - msgList.add(getWKSyncMsg(msgJson)); - } - } - - convMsg.recents = msgList; - conversation.conversations!.add(convMsg); - } - } catch (e) { - print('同步最近会话错误'); + try { + // 检查是否已登录 + if (UserInfo.uid.isEmpty) { + print('请先登录'); + back(WKSyncConversation()..conversations = []); + return; } + + final response = await dio.post('/conversation/sync', data: { + "login_uid": UserInfo.uid, + "version": version, + "last_msg_seqs": lastSsgSeqs, + "msg_count": msgCount, + "device_uuid": UserInfo.uid, + }); + + WKSyncConversation conversation = WKSyncConversation(); + conversation.conversations = []; + + if (response.statusCode == HttpStatus.ok) { + try { + var list = response.data['conversations']; + for (int i = 0; i < list.length; i++) { + var json = list[i]; + WKSyncConvMsg convMsg = WKSyncConvMsg(); + convMsg.channelID = json['channel_id']; + convMsg.channelType = json['channel_type']; + convMsg.unread = json['unread'] ?? 0; + convMsg.timestamp = json['timestamp']; + convMsg.lastMsgSeq = json['last_msg_seq']; + convMsg.lastClientMsgNO = json['last_client_msg_no']; + convMsg.version = json['version']; + var msgListJson = json['recents'] as List; + List msgList = []; + if (msgListJson.isNotEmpty) { + for (int j = 0; j < msgListJson.length; j++) { + var msgJson = msgListJson[j]; + msgList.add(getWKSyncMsg(msgJson)); + } + } + + convMsg.recents = msgList; + conversation.conversations!.add(convMsg); + } + } catch (e) { + print('解析会话数据错误: $e'); + } + } else { + print('同步会话失败: HTTP ${response.statusCode}'); + if (response.data != null && response.data is Map) { + print('错误信息: ${response.data['message'] ?? response.data}'); + } + } + back(conversation); + } catch (e) { + print('同步会话错误: $e'); + back(WKSyncConversation()..conversations = []); } - back(conversation); } static syncChannelMsg( @@ -142,42 +143,37 @@ class HttpUtils { int limit, int pullMode, Function(WKSyncChannelMsg) back) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; - final response = await dio.post('$apiURL/message/channel/sync', data: { - "login_uid": UserInfo.uid, // 当前登录用户uid - "channel_id": channelID, // 频道ID - "channel_type": channelType, // 频道类型 - "start_message_seq": startMsgSeq, // 开始消息列号(结果包含start_message_seq的消息) - "end_message_seq": endMsgSeq, // 结束消息列号(结果不包含end_message_seq的消息) - "limit": limit, // 消息数量限制 - "pull_mode": pullMode // 拉取模式 0:向下拉取 1:向上拉取 - }); - if (response.statusCode == HttpStatus.ok) { - var data = response.data; - WKSyncChannelMsg msg = WKSyncChannelMsg(); - msg.startMessageSeq = data['start_message_seq']; - msg.endMessageSeq = data['end_message_seq']; - msg.more = data['more']; - var messages = data['messages']; + try { + final response = await dio.post('/message/channel/sync', data: { + "login_uid": UserInfo.uid, + "channel_id": channelID, + "channel_type": channelType, + "start_message_seq": startMsgSeq, + "end_message_seq": endMsgSeq, + "limit": limit, + "pull_mode": pullMode + }); + + if (response.statusCode == HttpStatus.ok) { + var data = response.data; + WKSyncChannelMsg msg = WKSyncChannelMsg(); + msg.startMessageSeq = data['start_message_seq']; + msg.endMessageSeq = data['end_message_seq']; + msg.more = data['more']; + var messages = data['messages']; - List msgList = []; - for (int i = 0; i < messages.length; i++) { - dynamic json = messages[i]; - msgList.add(getWKSyncMsg(json)); + List msgList = []; + for (int i = 0; i < messages.length; i++) { + dynamic json = messages[i]; + msgList.add(getWKSyncMsg(json)); + } + print('同步channel消息数量:${msgList.length}'); + msg.messages = msgList; + back(msg); } - print('同步channel消息数量:${msgList.length}'); - msg.messages = msgList; - back(msg); + } catch (e) { + print('Sync channel message error: $e'); + back(WKSyncChannelMsg()); } } @@ -221,71 +217,90 @@ class HttpUtils { return extra; } - static getGroupInfo(String groupId) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; - final response = await dio.get('$apiURL/groups/$groupId'); - if (response.statusCode == HttpStatus.ok) { - var json = response.data; - var channel = WKChannel(groupId, WKChannelType.group); - channel.channelName = json['name']; - channel.avatar = json['avatar']; - WKIM.shared.channelManager.addOrUpdateChannel(channel); - } else { - print('获取群信息失败'); + static Future getGroupInfo(String groupId) async { + try { + // 检查群ID是否有效 + if (groupId.isEmpty) { + print('群ID不能为空'); + return; + } + + // 检查是否已登录 + if (UserInfo.uid.isEmpty) { + print('请先登录'); + return; + } + + final response = await dio.get('/groups/$groupId'); + + if (response.statusCode == HttpStatus.ok) { + var json = response.data; + var channel = WKChannel(groupId, WKChannelType.group); + channel.channelName = json['name']; + channel.avatar = json['avatar']; + WKIM.shared.channelManager.addOrUpdateChannel(channel); + } else { + print('获取群信息失败: HTTP ${response.statusCode}'); + // 如果服务器返回了错误消息,打印出来 + if (response.data != null && response.data is Map) { + print('错误信息: ${response.data['message'] ?? response.data}'); + } + } + } catch (e) { + print('获取群信息错误: $e'); } } - static getUserInfo(String uid) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; + static Future getUserInfo(String uid) async { try { - final response = await dio.get('$apiURL/users/$uid'); + // 检查UID是否有效 + if (uid.isEmpty) { + print('用户ID不能为空'); + return; + } + + // 检查是否已登录 + if (UserInfo.uid.isEmpty) { + print('请先登录'); + return; + } + + final response = await dio.get('/users/$uid'); + if (response.statusCode == HttpStatus.ok) { var json = response.data; var channel = WKChannel(uid, WKChannelType.personal); channel.channelName = json['name']; channel.avatar = json['avatar']; WKIM.shared.channelManager.addOrUpdateChannel(channel); + } else { + print('获取用户信息失败: HTTP ${response.statusCode}'); + // 如果服务器返回了错误消息,打印出来 + if (response.data != null && response.data is Map) { + print('错误信息: ${response.data['message'] ?? response.data}'); + } } } catch (e) { - print('获取用户信息失败$e'); + print('获取用户信息错误: $e'); } } - static revokeMsg(String clientMsgNo, String channelId, int channelType, + static Future revokeMsg(String clientMsgNo, String channelId, int channelType, int msgSeq, String msgId) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; try { - final response = await dio.post('$apiURL/message/revoke', data: { + // 检查必要参数 + if (clientMsgNo.isEmpty || channelId.isEmpty || msgId.isEmpty) { + print('撤回消息需提供完整的消息信息'); + return false; + } + + // 检查是否已登录 + if (UserInfo.uid.isEmpty) { + print('请先登录'); + return false; + } + + final response = await dio.post('/message/revoke', data: { 'login_uid': UserInfo.uid, 'channel_id': channelId, 'channel_type': channelType, @@ -293,57 +308,66 @@ class HttpUtils { 'message_seq': msgSeq, 'message_id': msgId, }); + if (response.statusCode == HttpStatus.ok) { - print('撤回消息成功'); + print('消息撤回成功'); + return true; + } else { + print('撤回消息失败: HTTP ${response.statusCode}'); + if (response.data != null && response.data is Map) { + print('错误信息: ${response.data['message'] ?? response.data}'); + } + return false; } } catch (e) { - print('获取用户信息失败$e'); + print('撤回消息错误: $e'); + return false; } } - static deleteMsg(String clientMsgNo, String channelId, int channelType, + static Future deleteMsg(String clientMsgNo, String channelId, int channelType, int msgSeq, String msgId) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; try { - final response = await dio.post('$apiURL/message/delete', data: { + // 检查必要参数 + if (clientMsgNo.isEmpty || channelId.isEmpty || msgId.isEmpty) { + print('删除消息需提供完整的消息信息'); + return false; + } + + // 检查是否已登录 + if (UserInfo.uid.isEmpty) { + print('请先登录'); + return false; + } + + final response = await dio.post('/message/delete', data: { 'login_uid': UserInfo.uid, 'channel_id': channelId, 'channel_type': channelType, 'message_seq': msgSeq, 'message_id': msgId, }); + if (response.statusCode == HttpStatus.ok) { WKIM.shared.messageManager.deleteWithClientMsgNo(clientMsgNo); + print('消息删除成功'); + return true; + } else { + print('删除消息失败: HTTP ${response.statusCode}'); + if (response.data != null && response.data is Map) { + print('错误信息: ${response.data['message'] ?? response.data}'); + } + return false; } } catch (e) { - print('删除消息失败$e'); + print('删除消息错误: $e'); + return false; } } - static syncMsgExtra(String channelId, int channelType, int version) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; + static Future syncMsgExtra(String channelId, int channelType, int version) async { try { - final response = await dio.post('$apiURL/message/extra/sync', data: { + final response = await dio.post('/message/extra/sync', data: { 'login_uid': UserInfo.uid, 'channel_id': channelId, 'channel_type': channelType, @@ -351,6 +375,7 @@ class HttpUtils { 'limit': 100, 'extra_version': version, }); + if (response.statusCode == HttpStatus.ok) { var arrJson = response.data; if (arrJson != null && arrJson.length > 0) { @@ -371,122 +396,73 @@ class HttpUtils { } } } catch (e) { - print('同步消息扩展失败$e'); + print('Sync message extra error: $e'); } } // 清空红点 - static clearUnread(String channelId, int channelType) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; + static Future clearUnread(String channelId, int channelType) async { try { - final response = await dio.put('$apiURL/conversation/clearUnread', data: { + final response = await dio.put('/conversation/clearUnread', data: { 'login_uid': UserInfo.uid, 'channel_id': channelId, 'channel_type': channelType, 'unread': 0, }); + if (response.statusCode == HttpStatus.ok) { - print('清空红点成功'); + print('Unread count cleared successfully'); } } catch (e) { - print('清空红点失败$e'); + print('Clear unread count error: $e'); } } // 清除频道消息 - static clearChannelMsg(String channelId, int channelType) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; + static Future clearChannelMsg(String channelId, int channelType) async { try { int maxSeq = await WKIM.shared.messageManager .getMaxMessageSeq(channelId, channelType); - final response = await dio.post('$apiURL/message/offset', data: { + + final response = await dio.post('/message/offset', data: { 'login_uid': UserInfo.uid, 'channel_id': channelId, 'channel_type': channelType, 'message_seq': maxSeq }); + if (response.statusCode == HttpStatus.ok) { WKIM.shared.messageManager.clearWithChannel(channelId, channelType); } } catch (e) { - print('清除频道消息失败$e'); + print('Clear channel message error: $e'); } } // 创建群 static Future createGroup(String groupNo) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; try { - final response = await dio.post('$apiURL/group/create', data: { + final response = await dio.post('/group/create', data: { 'login_uid': UserInfo.uid, 'group_no': groupNo, }); - if (response.statusCode == HttpStatus.ok) { - return true; - } else { - return false; - } + return response.statusCode == HttpStatus.ok; } catch (e) { - print('创建群失败$e'); + print('Create group error: $e'); return false; } } // 修改群名称 static Future updateGroupName(String groupNo, String groupName) async { - final httpClient = HttpClient(); - httpClient.badCertificateCallback = - (X509Certificate cert, String host, int port) { - // 信任所有证书 - return true; - }; - final dio = Dio(); - dio.httpClientAdapter = DefaultHttpClientAdapter() - ..onHttpClientCreate = (client) { - return httpClient; - }; try { - final response = await dio.put('$apiURL/groups/$groupNo', data: { + final response = await dio.put('/groups/$groupNo', data: { 'login_uid': UserInfo.uid, 'name': groupName, }); - if (response.statusCode == HttpStatus.ok) { - return true; - } else { - return false; - } + return response.statusCode == HttpStatus.ok; } catch (e) { - print('修改群名称失败$e'); + print('Update group name error: $e'); return false; } } diff --git a/example/lib/ui_conversation.dart b/example/lib/ui_conversation.dart index d913d97..c203ba9 100644 --- a/example/lib/ui_conversation.dart +++ b/example/lib/ui_conversation.dart @@ -81,6 +81,10 @@ class ListViewShowDataState extends State { for (var i = 0; i < msgList.length; i++) { msgList[i].msg.unreadCount = 0; } + + // 清除红点后也重新排序,保持列表排序的一致性 + _sortMessagesByTimestamp(); + setState(() {}); }); WKIM.shared.conversationManager @@ -107,6 +111,9 @@ class ListViewShowDataState extends State { if (list.isNotEmpty) { msgList.addAll(list); } + + _sortMessagesByTimestamp(); + if (mounted) { setState(() {}); } @@ -120,6 +127,10 @@ class ListViewShowDataState extends State { msgList[i].msg.setWkChannel(channel); msgList[i].channelAvatar = "${HttpUtils.apiURL}/${channel.avatar}"; msgList[i].channelName = channel.channelName; + + // 刷新频道信息后重新排序(虽然时间戳没变,但保持一致性) + _sortMessagesByTimestamp(); + setState(() {}); break; } @@ -127,6 +138,11 @@ class ListViewShowDataState extends State { }); } + /// 对会话列表按时间戳排序,最新的会话排在前面 + void _sortMessagesByTimestamp() { + msgList.sort((a, b) => b.msg.lastMsgTimestamp.compareTo(a.msg.lastMsgTimestamp)); + } + void _getDataList() { Future> list = WKIM.shared.conversationManager.getAll(); @@ -134,7 +150,8 @@ class ListViewShowDataState extends State { for (var i = 0; i < result.length; i++) { msgList.add(UIConversation(result[i])); } - + + _sortMessagesByTimestamp(); setState(() {}); }); } @@ -315,7 +332,7 @@ class ListViewShowDataState extends State { style: const TextStyle(color: Colors.black, fontSize: 16), ) ], - ) + ), ], ), ), diff --git a/lib/manager/channel_manager.dart b/lib/manager/channel_manager.dart index d5608d2..406c947 100644 --- a/lib/manager/channel_manager.dart +++ b/lib/manager/channel_manager.dart @@ -4,13 +4,17 @@ import '../db/channel.dart'; import '../entity/channel.dart'; class WKChannelManager { - WKChannelManager._privateConstructor(); + WKChannelManager._privateConstructor() { + _refreshChannelMap = HashMap(); + _refreshChannelAvatarMap = HashMap(); + } static final WKChannelManager _instance = WKChannelManager._privateConstructor(); static WKChannelManager get shared => _instance; final List _list = []; - HashMap? _refeshChannelMap; + late final HashMap _refreshChannelMap; + late final HashMap _refreshChannelAvatarMap; Function(String channelID, int channelType, Function(WKChannel) back)? _getChannelInfoBack; @@ -70,6 +74,21 @@ class WKChannelManager { .searchWithChannelTypeAndFollow(searchKey, channelType, follow); } + // 修改头像 + updateAvatarCacheKey( + String channelID, int channelType, String avatarCacheKey) async { + WKChannel? channel = await getChannel(channelID, channelType); + if (channel == null) { + return; + } + channel.avatarCacheKey = avatarCacheKey; + _updateChannel(channel); + ChannelDB.shared.saveOrUpdate(channel); + _refreshChannelAvatarMap.forEach((key, back) { + back(channel); + }); + } + addOrUpdateChannel(WKChannel channel) { _updateChannel(channel); _setRefresh(channel); @@ -115,25 +134,28 @@ class WKChannelManager { } _setRefresh(WKChannel liMChannel) { - if (_refeshChannelMap != null) { - _refeshChannelMap!.forEach((key, back) { - back(liMChannel); - }); - } + _refreshChannelMap.forEach((key, back) { + back(liMChannel); + }); } addOnRefreshListener(String key, Function(WKChannel) back) { - _refeshChannelMap ??= HashMap(); - _refeshChannelMap![key] = back; + _refreshChannelMap[key] = back; } removeOnRefreshListener(String key) { - if (_refeshChannelMap != null) { - _refeshChannelMap!.remove(key); - } + _refreshChannelMap.remove(key); } addOnGetChannelListener(Function(String, int, Function(WKChannel)) back) { _getChannelInfoBack = back; } + + addOnRefreshAvatarListener(String key, Function(WKChannel) back) { + _refreshChannelAvatarMap[key] = back; + } + + removeOnRefreshAvatarListener(String key) { + _refreshChannelAvatarMap.remove(key); + } } diff --git a/lib/manager/channel_member_manager.dart b/lib/manager/channel_member_manager.dart index 9fdfe5c..c5f2829 100644 --- a/lib/manager/channel_member_manager.dart +++ b/lib/manager/channel_member_manager.dart @@ -5,14 +5,18 @@ import 'package:wukongimfluttersdk/db/channel_member.dart'; import '../entity/channel_member.dart'; class WKChannelMemberManager { - WKChannelMemberManager._privateConstructor(); + WKChannelMemberManager._privateConstructor() { + _newMembersBack = HashMap)>(); + _refreshMembersBack = HashMap(); + _deleteMembersBack = HashMap)>(); + } static final WKChannelMemberManager _instance = WKChannelMemberManager._privateConstructor(); static WKChannelMemberManager get shared => _instance; - HashMap)>? _newMembersBack; - HashMap? _refreshMembersBack; - HashMap)>? _deleteMembersBack; + late final HashMap)> _newMembersBack; + late final HashMap _refreshMembersBack; + late final HashMap)> _deleteMembersBack; Future getMaxVersion(String channelID, int channelType) async { return ChannelMemberDB.shared.getMaxVersion(channelID, channelType); @@ -29,8 +33,9 @@ class WKChannelMemberManager { .queryWithUID(channelID, channelType, memberUID); } - saveOrUpdateList(List list) async { + Future saveOrUpdateList(List list) async { if (list.isEmpty) return; + String channelID = list[0].channelID; int channelType = list[0].channelType; @@ -40,7 +45,9 @@ class WKChannelMemberManager { List existList = []; List uidList = []; + for (WKChannelMember channelMember in list) { + // 分批处理,每200个UID查询一次数据库 if (uidList.length == 200) { List tempList = await ChannelMemberDB.shared .queryWithUIDs( @@ -54,16 +61,16 @@ class WKChannelMemberManager { uidList.add(channelMember.memberUID); } + // 处理剩余的UID if (uidList.isNotEmpty) { List tempList = await ChannelMemberDB.shared .queryWithUIDs(channelID, channelType, uidList); if (tempList.isNotEmpty) { existList.addAll(tempList); } - - uidList.clear(); } + // 分类处理成员:新增、更新或删除 for (WKChannelMember channelMember in list) { bool isNewMember = true; for (int i = 0, size = existList.length; i < size; i++) { @@ -86,77 +93,62 @@ class WKChannelMemberManager { } } - // 先保存或修改成员 - ChannelMemberDB.shared.insertList(list); + // 保存或修改成员 + await ChannelMemberDB.shared.insertList(list); + // 触发回调通知 if (addList.isNotEmpty) { - setOnNewChannelMember(addList); + _notifyNewChannelMembers(addList); } if (deleteList.isNotEmpty) { - setDeleteChannelMember(deleteList); + _notifyDeleteChannelMembers(deleteList); } - if (updateList.isNotEmpty) { for (int i = 0, size = updateList.length; i < size; i++) { - setRefreshChannelMember(updateList[i], i == updateList.length - 1); + _notifyRefreshChannelMember(updateList[i], i == updateList.length - 1); } } } - setRefreshChannelMember(WKChannelMember member, bool isEnd) { - if (_refreshMembersBack != null) { - _refreshMembersBack!.forEach((key, back) { - back(member, isEnd); - }); - } + void _notifyRefreshChannelMember(WKChannelMember member, bool isEnd) { + _refreshMembersBack.forEach((key, back) { + back(member, isEnd); + }); } - addOnRefreshMemberListener(String key, Function(WKChannelMember, bool) back) { - _refreshMembersBack ??= HashMap(); - _refreshMembersBack![key] = back; + void addOnRefreshMemberListener(String key, Function(WKChannelMember, bool) back) { + _refreshMembersBack[key] = back; } - removeRefreshMemberListener(String key) { - if (_refreshMembersBack != null) { - _refreshMembersBack!.remove(key); - } + void removeRefreshMemberListener(String key) { + _refreshMembersBack.remove(key); } - setDeleteChannelMember(List list) { - if (_deleteMembersBack != null) { - _deleteMembersBack!.forEach((key, back) { - back(list); - }); - } + void _notifyDeleteChannelMembers(List list) { + _deleteMembersBack.forEach((key, back) { + back(list); + }); } - addOnDeleteMemberListener(String key, Function(List) back) { - _deleteMembersBack ??= HashMap(); - _deleteMembersBack![key] = back; + void addOnDeleteMemberListener(String key, Function(List) back) { + _deleteMembersBack[key] = back; } - removeDeleteMemberListener(String key) { - if (_deleteMembersBack != null) { - _deleteMembersBack!.remove(key); - } + void removeDeleteMemberListener(String key) { + _deleteMembersBack.remove(key); } - setOnNewChannelMember(List list) { - if (_newMembersBack != null) { - _newMembersBack!.forEach((key, back) { - back(list); - }); - } + void _notifyNewChannelMembers(List list) { + _newMembersBack.forEach((key, back) { + back(list); + }); } - addOnNewMemberListener(String key, Function(List) back) { - _newMembersBack ??= HashMap(); - _newMembersBack![key] = back; + void addOnNewMemberListener(String key, Function(List) back) { + _newMembersBack[key] = back; } - removeNewMemberListener(String key) { - if (_newMembersBack != null) { - _newMembersBack!.remove(key); - } + void removeNewMemberListener(String key) { + _newMembersBack.remove(key); } } diff --git a/lib/manager/cmd_manager.dart b/lib/manager/cmd_manager.dart index 69fa36a..dc5d734 100644 --- a/lib/manager/cmd_manager.dart +++ b/lib/manager/cmd_manager.dart @@ -4,43 +4,55 @@ import 'package:wukongimfluttersdk/db/const.dart'; import '../entity/cmd.dart'; +/// 命令管理器,处理和分发命令 class WKCMDManager { - WKCMDManager._privateConstructor(); + WKCMDManager._privateConstructor() { + _cmdListeners = HashMap(); + } static final WKCMDManager _instance = WKCMDManager._privateConstructor(); static WKCMDManager get shared => _instance; - HashMap? _cmdback; - handleCMD(dynamic json) { + /// 命令监听器集合 + late final HashMap _cmdListeners; + + /// 处理从服务器接收的命令 + void handleCMD(dynamic json) { + // 解析命令 String cmd = WKDBConst.readString(json, 'cmd'); dynamic param = json['param']; + + // 补充频道信息(如果缺失) if (param != null && param is Map) { if (!param.containsKey('channel_id')) { param['channel_id'] = json['channel_id']; param['channel_type'] = json['channel_type']; } } + + // 创建命令对象并分发 WKCMD wkcmd = WKCMD(); wkcmd.cmd = cmd; wkcmd.param = param; - pushCMD(wkcmd); + _notifyListeners(wkcmd); } - pushCMD(WKCMD wkcmd) { - if (_cmdback != null) { - _cmdback!.forEach((key, back) { - back(wkcmd); - }); - } + /// 分发命令到所有监听器 + void _notifyListeners(WKCMD wkcmd) { + _cmdListeners.forEach((key, listener) { + listener(wkcmd); + }); } - addOnCmdListener(String key, Function(WKCMD) back) { - _cmdback ??= HashMap(); - _cmdback![key] = back; + /// 添加命令监听器 + /// [key] 监听器唯一标识 + /// [listener] 命令回调函数 + void addOnCmdListener(String key, Function(WKCMD) listener) { + _cmdListeners[key] = listener; } - removeCmdListener(String key) { - if (_cmdback != null) { - _cmdback!.remove(key); - } + /// 移除命令监听器 + /// [key] 监听器唯一标识 + void removeCmdListener(String key) { + _cmdListeners.remove(key); } } diff --git a/lib/manager/connect_manager.dart b/lib/manager/connect_manager.dart index bdc5f2e..745eac3 100644 --- a/lib/manager/connect_manager.dart +++ b/lib/manager/connect_manager.dart @@ -315,6 +315,7 @@ class WKConnectionManager { } } else if (packet.header.packetType == PacketType.sendack) { var sendack = packet as SendAckPacket; + Logs.debug('发送结果:${sendack.reasonCode}'); WKIM.shared.messageManager.updateSendResult(sendack.messageID, sendack.clientSeq, sendack.messageSeq, sendack.reasonCode); if (_sendingMsgMap.containsKey(sendack.clientSeq)) { diff --git a/lib/manager/conversation_manager.dart b/lib/manager/conversation_manager.dart index 7e6738a..920810f 100644 --- a/lib/manager/conversation_manager.dart +++ b/lib/manager/conversation_manager.dart @@ -9,37 +9,54 @@ import '../db/conversation.dart'; import '../entity/conversation.dart'; import '../type/const.dart'; +/// 会话管理器,负责管理和维护会话数据 class WKConversationManager { - WKConversationManager._privateConstructor(); + WKConversationManager._privateConstructor() { + _refreshMsgMap = HashMap(); + _refreshMsgListMap = HashMap)>(); + _deleteMsgMap = HashMap(); + _clearAllRedDotMap = HashMap(); + } + static final WKConversationManager _instance = WKConversationManager._privateConstructor(); static WKConversationManager get shared => _instance; - HashMap? _refeshMsgMap; - HashMap)>? _refreshMsgListMap; - HashMap? _deleteMsgMap; - HashMap? _clearAllRedDotMap; + /// 单个会话刷新监听器 + late final HashMap _refreshMsgMap; + + /// 会话列表刷新监听器 + late final HashMap)> _refreshMsgListMap; + + /// 会话删除监听器 + late final HashMap _deleteMsgMap; + + /// 清除所有红点监听器 + late final HashMap _clearAllRedDotMap; + /// 同步会话回调 Function(String lastSsgSeqs, int msgCount, int version, - Function(WKSyncConversation))? _syncConersationBack; + Function(WKSyncConversation))? _syncConversationBack; + /// 获取所有会话 Future> getAll() async { return await ConversationDB.shared.queryAll(); } + /// 删除指定频道的会话 Future deleteMsg(String channelID, int channelType) async { bool result = await ConversationDB.shared.delete(channelID, channelType); if (result) { - _setDeleteMsg(channelID, channelType); + _notifyDeleteMsg(channelID, channelType); } - return result; } + /// 根据消息保存会话 Future saveWithLiMMsg(WKMsg msg, int redDot) async { WKConversationMsg wkConversationMsg = WKConversationMsg(); if (msg.channelType == WKChannelType.communityTopic && - msg.channelID != '') { + msg.channelID.isNotEmpty) { if (msg.channelID.contains("@")) { var str = msg.channelID.split("@"); wkConversationMsg.parentChannelID = str[0]; @@ -57,14 +74,17 @@ class WKConversationManager { return uiMsg; } + /// 获取所有未读消息总数 Future getAllUnreadCount() async { return ConversationDB.shared.queryAllUnreadCount(); } + /// 获取扩展信息的最大版本号 Future getExtraMaxVersion() async { return ConversationDB.shared.queryExtraMaxVersion(); } + /// 获取指定频道的会话 Future getWithChannel( String channelID, int channelType) async { var msg = await ConversationDB.shared @@ -75,165 +95,172 @@ class WKConversationManager { return null; } - clearAll() { - ConversationDB.shared.clearAll(); + /// 清除所有会话 + Future clearAll() async { + await ConversationDB.shared.clearAll(); } - clearAllRedDot() async { + /// 清除所有会话的未读数 + Future clearAllRedDot() async { int row = await ConversationDB.shared.clearAllRedDot(); if (row > 0) { - _setClearAllRedDot(); + _notifyClearAllRedDot(); } } - updateRedDot(String channelID, int channelType, int redDot) async { + /// 更新指定频道的未读数 + Future updateRedDot(String channelID, int channelType, int redDot) async { var map = {}; map['unread_count'] = redDot; var result = await ConversationDB.shared .updateWithField(map, channelID, channelType); if (result > 0) { - _refreshMsg(channelID, channelType); + await _refreshChannelMsg(channelID, channelType); } } - _refreshMsg(String channelID, int channelType) async { + /// 刷新指定频道的会话 + Future _refreshChannelMsg(String channelID, int channelType) async { var msg = await ConversationDB.shared .queryMsgByMsgChannelId(channelID, channelType); if (msg != null) { var uiMsg = ConversationDB.shared.getUIMsg(msg); - List uiMsgs = []; - uiMsgs.add(uiMsg); + List uiMsgs = [uiMsg]; setRefreshUIMsgs(uiMsgs); } } - addOnClearAllRedDotListener(String key, Function() back) { - _clearAllRedDotMap ??= HashMap(); - _clearAllRedDotMap![key] = back; + /// 添加清除所有红点监听器 + void addOnClearAllRedDotListener(String key, Function() listener) { + _clearAllRedDotMap[key] = listener; } - removeClearAllRedDotListener(String key) { - if (_clearAllRedDotMap != null) { - _clearAllRedDotMap!.remove(key); - } + /// 移除清除所有红点监听器 + void removeClearAllRedDotListener(String key) { + _clearAllRedDotMap.remove(key); } - _setClearAllRedDot() { - if (_clearAllRedDotMap != null) { - _clearAllRedDotMap!.forEach((key, back) { - back(); - }); - } + /// 通知清除所有红点 + void _notifyClearAllRedDot() { + _clearAllRedDotMap.forEach((_, listener) { + listener(); + }); } - addOnDeleteMsgListener(String key, Function(String, int) back) { - _deleteMsgMap ??= HashMap(); - _deleteMsgMap![key] = back; + /// 添加会话删除监听器 + void addOnDeleteMsgListener(String key, Function(String, int) listener) { + _deleteMsgMap[key] = listener; } - removeDeleteMsgListener(String key) { - if (_deleteMsgMap != null) { - _deleteMsgMap!.remove(key); - } + /// 移除会话删除监听器 + void removeDeleteMsgListener(String key) { + _deleteMsgMap.remove(key); } - _setDeleteMsg(String channelID, int channelType) { - if (_deleteMsgMap != null) { - _deleteMsgMap!.forEach((key, back) { - back(channelID, channelType); - }); - } + /// 通知会话已删除 + void _notifyDeleteMsg(String channelID, int channelType) { + _deleteMsgMap.forEach((_, listener) { + listener(channelID, channelType); + }); } - _setRefreshMsg(WKUIConversationMsg msg, bool isEnd) { - if (_refeshMsgMap != null) { - _refeshMsgMap!.forEach((key, back) { - back(msg, isEnd); - }); - } + /// 通知刷新单个会话 + void _notifyRefreshMsg(WKUIConversationMsg msg, bool isEnd) { + _refreshMsgMap.forEach((_, listener) { + listener(msg, isEnd); + }); } - @Deprecated("Please replace with `addOnRefreshMsgListListener` method") - addOnRefreshMsgListener( - String key, Function(WKUIConversationMsg, bool) back) { - _refeshMsgMap ??= HashMap(); - _refeshMsgMap![key] = back; + /// 添加会话刷新监听器(已废弃) + @Deprecated("请使用 addOnRefreshMsgListListener 方法替代") + void addOnRefreshMsgListener( + String key, Function(WKUIConversationMsg, bool) listener) { + _refreshMsgMap[key] = listener; } - removeOnRefreshMsg(String key) { - if (_refeshMsgMap != null) { - _refeshMsgMap!.remove(key); - } + /// 移除会话刷新监听器 + void removeOnRefreshMsg(String key) { + _refreshMsgMap.remove(key); } - setRefreshUIMsgs(List msgs) { - _setRefreshMsgList(msgs); + /// 刷新会话UI列表 + void setRefreshUIMsgs(List msgs) { + _notifyRefreshMsgList(msgs); for (int i = 0, size = msgs.length; i < size; i++) { - _setRefreshMsg(msgs[i], i == msgs.length - 1); + _notifyRefreshMsg(msgs[i], i == msgs.length - 1); } } - _setRefreshMsgList(List msgs) { - if (_refreshMsgListMap != null) { - _refreshMsgListMap!.forEach((key, back) { - back(msgs); - }); - } + /// 通知刷新会话列表 + void _notifyRefreshMsgList(List msgs) { + _refreshMsgListMap.forEach((_, listener) { + listener(msgs); + }); } - addOnRefreshMsgListListener( - String key, Function(List) back) { - _refreshMsgListMap ??= HashMap(); - _refreshMsgListMap![key] = back; + /// 添加会话列表刷新监听器 + void addOnRefreshMsgListListener( + String key, Function(List) listener) { + _refreshMsgListMap[key] = listener; } - removeOnRefreshMsgListListener(String key) { - if (_refreshMsgListMap != null) { - _refreshMsgListMap!.remove(key); - } + /// 移除会话列表刷新监听器 + void removeOnRefreshMsgListListener(String key) { + _refreshMsgListMap.remove(key); } - addOnSyncConversationListener( + /// 添加同步会话监听器 + void addOnSyncConversationListener( Function(String lastSsgSeqs, int msgCount, int version, Function(WKSyncConversation)) - back) { - _syncConersationBack = back; + listener) { + _syncConversationBack = listener; } - setSyncConversation(Function() back) async { + /// 触发同步会话操作 + Future setSyncConversation(Function() callback) async { WKIM.shared.connectionManager.setConnectionStatus(WKConnectStatus.syncMsg); - if (_syncConersationBack != null) { + if (_syncConversationBack != null) { int version = await ConversationDB.shared.getMaxVersion(); String lastMsgSeqStr = await ConversationDB.shared.getLastMsgSeqs(); - _syncConersationBack!(lastMsgSeqStr, 200, version, (msgs) { - _saveSyncCoversation(msgs); - back(); + _syncConversationBack!(lastMsgSeqStr, 200, version, (msgs) { + _saveSyncConversation(msgs); + callback(); }); } } - _saveSyncCoversation(WKSyncConversation? syncChat) { + /// 保存同步的会话数据 + void _saveSyncConversation(WKSyncConversation? syncChat) { if (syncChat == null || syncChat.conversations == null || syncChat.conversations!.isEmpty) { return; } + + // 初始化数据集合 List conversationMsgList = []; List msgList = []; List msgReactionList = []; List msgExtraList = []; List uiMsgList = []; + + // 处理同步的会话数据 if (syncChat.conversations != null && syncChat.conversations!.isNotEmpty) { for (int i = 0, size = syncChat.conversations!.length; i < size; i++) { WKConversationMsg conversationMsg = WKConversationMsg(); int channelType = syncChat.conversations![i].channelType; String channelID = syncChat.conversations![i].channelID; + + // 处理社区主题频道 if (channelType == WKChannelType.communityTopic) { var str = channelID.split("@"); conversationMsg.parentChannelID = str[0]; conversationMsg.parentChannelType = WKChannelType.community; } + + // 设置会话属性 conversationMsg.channelID = syncChat.conversations![i].channelID; conversationMsg.channelType = syncChat.conversations![i].channelType; conversationMsg.lastMsgSeq = syncChat.conversations![i].lastMsgSeq; @@ -242,57 +269,74 @@ class WKConversationManager { conversationMsg.lastMsgTimestamp = syncChat.conversations![i].timestamp; conversationMsg.unreadCount = syncChat.conversations![i].unread; conversationMsg.version = syncChat.conversations![i].version; + WKUIConversationMsg uiMsg = ConversationDB.shared.getUIMsg(conversationMsg); - //聊天消息对象 + + // 处理最近消息 if (syncChat.conversations![i].recents != null && syncChat.conversations![i].recents!.isNotEmpty) { for (WKSyncMsg wkSyncRecent in syncChat.conversations![i].recents!) { WKMsg msg = wkSyncRecent.getWKMsg(); + + // 处理反应列表 if (msg.reactionList != null && msg.reactionList!.isNotEmpty) { msgReactionList.addAll(msg.reactionList!); } - //判断会话列表的fromUID + + // 判断会话列表的fromUID if (conversationMsg.lastClientMsgNO == msg.clientMsgNO) { conversationMsg.isDeleted = msg.isDeleted; uiMsg.isDeleted = conversationMsg.isDeleted; uiMsg.setWkMsg(msg); } + + // 处理消息扩展信息 if (wkSyncRecent.messageExtra != null) { WKMsgExtra extra = WKIM.shared.messageManager .wkSyncExtraMsg2WKMsgExtra(msg.channelID, msg.channelType, wkSyncRecent.messageExtra!); msgExtraList.add(extra); } + msgList.add(msg); } } + conversationMsgList.add(conversationMsg); uiMsgList.add(uiMsg); } } + + // 保存各类数据到数据库 if (msgExtraList.isNotEmpty) { MessageDB.shared.insertMsgExtras(msgExtraList); - // MessageDB.shared.insertOrUpdateMsgExtras(msgExtraList); } if (msgList.isNotEmpty) { MessageDB.shared.insertMsgList(msgList); } + if (conversationMsgList.isNotEmpty) { - // ConversationDB.shared.insertMsgList(conversationMsgList); ConversationDB.shared.insetMsgs(conversationMsgList); } + if (msgReactionList.isNotEmpty) { ReactionDB.shared.insertOrUpdateReactionList(msgReactionList); } + + // 消息少于20条时,按顺序推送新消息 if (msgList.isNotEmpty && msgList.length < 20) { msgList.sort((a, b) => a.messageSeq.compareTo(b.messageSeq)); WKIM.shared.messageManager.pushNewMsg(msgList); } + + // 刷新会话UI if (uiMsgList.isNotEmpty) { setRefreshUIMsgs(uiMsgList); } + + // 处理命令 if (syncChat.cmds != null && syncChat.cmds!.isNotEmpty) { for (int i = 0, size = syncChat.cmds!.length; i < size; i++) { dynamic json = {}; diff --git a/pubspec.yaml b/pubspec.yaml index b9c1729..d793a94 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.6.3 +version: 1.6.4 homepage: https://github.com/WuKongIM/WuKongIMFlutterSDK environment: