diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dbd61e..42f702b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,4 +109,8 @@ ### 1.5.4 * fix: Update sync channel message ### 1.5.5 - * fix: After successfully modifying the synchronization channel message, no problem was returned \ No newline at end of file + * fix: After successfully modifying the synchronization channel message, no problem was returned + ### 1.5.6 + * fix: Optimize reconnection and resend messages + ### 1.5.7 + * fix: Optimize channel fields in cmd messages \ No newline at end of file diff --git a/example/lib/chatview.dart b/example/lib/chatview.dart new file mode 100644 index 0000000..7f0875b --- /dev/null +++ b/example/lib/chatview.dart @@ -0,0 +1,280 @@ +import 'package:example/const.dart'; +import 'package:example/http.dart'; +import 'package:example/msg.dart'; +import 'package:example/order_message_content.dart'; +import 'package:flutter/material.dart'; +import 'package:wukongimfluttersdk/type/const.dart'; +import 'package:wukongimfluttersdk/wkim.dart'; + +import 'popmenu_util.dart'; +import 'popup_item.dart'; + +getChannelAvatarURL(UIMsg uiMsg) { + var fromChannel = uiMsg.wkMsg.getFrom(); + if (fromChannel != null && fromChannel.avatar != '') { + return HttpUtils.getAvatarUrl(fromChannel.channelID); + } + WKIM.shared.channelManager + .fetchChannelInfo(uiMsg.wkMsg.fromUID, WKChannelType.personal); + return ''; +} + +Widget chatAvatar(UIMsg uiMsg) { + return Image.network( + getChannelAvatarURL(uiMsg), + height: 40, + width: 40, + fit: BoxFit.cover, + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset( + 'assets/ic_default_avatar.png', + width: 40, + height: 40, + ); + }, + ); +} + +longClick( + UIMsg uiMsg, BuildContext context, LongPressStartDetails details) async { + List items = []; + items.add(PopupItem( + text: '删除', + onTap: () { + HttpUtils.deleteMsg( + uiMsg.wkMsg.clientMsgNO, + uiMsg.wkMsg.channelID, + uiMsg.wkMsg.channelType, + uiMsg.wkMsg.messageSeq, + uiMsg.wkMsg.messageID); + }, + )); + if (uiMsg.wkMsg.fromUID == UserInfo.uid && + uiMsg.wkMsg.status == WKSendMsgResult.sendSuccess) { + items.add(PopupItem( + text: '撤回', + onTap: () { + HttpUtils.revokeMsg( + uiMsg.wkMsg.clientMsgNO, + uiMsg.wkMsg.channelID, + uiMsg.wkMsg.channelType, + uiMsg.wkMsg.messageSeq, + uiMsg.wkMsg.messageID); + }, + )); + } + await PopmenuUtil.showPopupMenu(context, details, items); +} + +Widget orderView(UIMsg uiMsg, BuildContext context) { + var leftMargin = 60.0; + var rightMargin = 5.0; + if (uiMsg.wkMsg.fromUID != UserInfo.uid) { + leftMargin = 10.0; + rightMargin = 60.0; + } + var orderContent = uiMsg.wkMsg.messageContent as OrderMsg; + return Expanded( + child: GestureDetector( + onLongPressStart: (details) { + longClick(uiMsg, context, details); + }, + child: Container( + padding: const EdgeInsets.only(left: 5, top: 5, right: 5, bottom: 5), + margin: EdgeInsets.only( + left: leftMargin, top: 0, right: rightMargin, bottom: 0), + decoration: const BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(12)), + color: Color.fromARGB(255, 250, 250, 250)), + alignment: Alignment.centerLeft, + child: Column( + children: [ + Container( + margin: + const EdgeInsets.only(left: 5, top: 5, right: 5, bottom: 5), + alignment: Alignment.centerLeft, + child: Text( + '订单号:${orderContent.orderNo}', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + Container( + margin: + const EdgeInsets.only(left: 5, top: 5, right: 5, bottom: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + orderContent.imgUrl, + height: 80, + width: 80, + fit: BoxFit.contain, + errorBuilder: (BuildContext context, Object exception, + StackTrace? stackTrace) { + return Image.asset('assets/ic_default_avatar.png'); + }, + ), + Expanded( + child: Container( + margin: const EdgeInsets.only( + left: 10, top: 5, right: 0, bottom: 5), + child: Column( + children: [ + Text( + '商品名称:${orderContent.title}', + softWrap: true, + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold), + ), + Row( + children: [ + Text("\$${orderContent.price}", + style: const TextStyle( + color: Colors.red, + fontSize: 14, + fontWeight: FontWeight.bold)), + const SizedBox(width: 100.0), + Text('共${orderContent.num}件', + style: const TextStyle( + color: Colors.red, + fontSize: 14, + fontWeight: FontWeight.bold)), + ], + ) + ], + ), + )) + ], + ), + ), + ], + ), + ), + ), + ); +} + +Widget getRevokedView(UIMsg uiMsg, BuildContext context) { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.only(left: 60, top: 10, right: 60, bottom: 10), + child: const Text('消息被撤回', + style: TextStyle( + color: Colors.black, fontSize: 14, fontWeight: FontWeight.bold)), + ); +} + +Widget getSendView(UIMsg uiMsg, BuildContext context) { + if (uiMsg.wkMsg.contentType == 56) { + return orderView(uiMsg, context); + } else { + return sendTextView(uiMsg, context); + } +} + +Widget getRecvView(UIMsg uiMsg, BuildContext context) { + if (uiMsg.wkMsg.contentType == 56) { + return orderView(uiMsg, context); + } else { + return recvTextView(uiMsg, context); + } +} + +Widget recvTextView(UIMsg uiMsg, BuildContext context) { + return Expanded( + child: GestureDetector( + onLongPressStart: (details) { + longClick(uiMsg, context, details); + }, + child: Container( + alignment: Alignment.centerLeft, + margin: const EdgeInsets.only(left: 10, top: 0, right: 60, bottom: 0), + child: Container( + padding: + const EdgeInsets.only(left: 10, top: 5, right: 10, bottom: 5), + decoration: const BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(12)), + color: Color.fromARGB(255, 163, 33, 243)), + child: Column( + children: [ + Container( + alignment: Alignment.topLeft, + child: Text( + uiMsg.getShowContent(), + style: const TextStyle(color: Colors.white, fontSize: 16), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + uiMsg.getShowTime(), + style: const TextStyle( + color: Color.fromARGB(255, 226, 215, 215), + fontSize: 12), + ) + ], + ) + ], + ), + ), + ), + ), + ); +} + +Widget sendTextView(UIMsg uiMsg, BuildContext context) { + var alignment = Alignment.bottomRight; + if (uiMsg.wkMsg.fromUID != UserInfo.uid) { + alignment = Alignment.centerLeft; + } + return Expanded( + child: GestureDetector( + onLongPressStart: (details) { + longClick(uiMsg, context, details); + }, + child: Container( + padding: const EdgeInsets.only(left: 5, top: 3, right: 5, bottom: 3), + margin: const EdgeInsets.only(left: 60, top: 0, right: 5, bottom: 0), + decoration: const BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(12)), + color: Color.fromARGB(255, 9, 75, 243)), + alignment: alignment, + child: Column( + children: [ + Container( + alignment: Alignment.centerLeft, + child: Text( + uiMsg.getShowContent(), + style: const TextStyle(color: Colors.white, fontSize: 16), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + uiMsg.getShowTime(), + style: const TextStyle( + color: Color.fromARGB(255, 226, 215, 215), fontSize: 12), + ), + Image( + image: AssetImage(uiMsg.getStatusIV()), + width: 30, + height: 30) + ], + ), + ], + ), + ), + ), + ); +} diff --git a/example/lib/contestation.dart b/example/lib/conversation_msg.dart similarity index 100% rename from example/lib/contestation.dart rename to example/lib/conversation_msg.dart diff --git a/example/lib/custom_message.dart b/example/lib/custom_message.dart deleted file mode 100644 index 82da3df..0000000 --- a/example/lib/custom_message.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:wukongimfluttersdk/model/wk_message_content.dart'; - -class CustomMsg extends WKMessageContent { - var name = ""; - CustomMsg(this.name) { - contentType = 12; - } - @override - Map encodeJson() { - return {"name": name}; - } - - @override - WKMessageContent decodeJson(Map json) { - name = json["name"]; - return this; - } - - @override - String displayText() { - return "我是自定义消息:$name"; - } -} diff --git a/example/lib/http.dart b/example/lib/http.dart index fd734a5..e48cd96 100644 --- a/example/lib/http.dart +++ b/example/lib/http.dart @@ -1,15 +1,25 @@ -import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:example/const.dart'; +import 'package:wukongimfluttersdk/entity/channel.dart'; import 'package:wukongimfluttersdk/entity/conversation.dart'; import 'package:wukongimfluttersdk/entity/msg.dart'; +import 'package:wukongimfluttersdk/type/const.dart'; +import 'package:wukongimfluttersdk/wkim.dart'; class HttpUtils { // static String apiURL = "https://api.githubim.com"; - static String apiURL = "http://175.27.245.108:15001"; + static String apiURL = "http://62.234.8.38:7090/v1"; + // static String apiURL = "http://175.27.245.108:15001"; + static getAvatarUrl(String uid) { + return "$apiURL/users/$uid/avatar"; + } + + static getGroupAvatarUrl(String gid) { + return "$apiURL/groups/$gid/avatar"; + } static Future login(String uid, String token) async { final httpClient = HttpClient(); @@ -23,7 +33,7 @@ class HttpUtils { ..onHttpClientCreate = (client) { return httpClient; }; - final response = await dio.post("$apiURL/user/token", data: { + final response = await dio.post("$apiURL/user/login", data: { 'uid': uid, 'token': token, 'device_flag': 0, @@ -32,7 +42,7 @@ class HttpUtils { return response.statusCode!; } - static Future getIP() async { + static Future getIP(String uid) async { final httpClient = HttpClient(); httpClient.badCertificateCallback = (X509Certificate cert, String host, int port) { @@ -45,7 +55,7 @@ class HttpUtils { return httpClient; }; String ip = ''; - final response = await dio.get('$apiURL/route'); + final response = await dio.get('$apiURL/users/$uid/route'); if (response.statusCode == HttpStatus.ok) { ip = response.data['tcp_addr']; } @@ -65,26 +75,29 @@ class HttpUtils { ..onHttpClientCreate = (client) { return httpClient; }; + final response = await dio.post('$apiURL/conversation/sync', data: { - "uid": UserInfo.uid, // 当前登录用户uid + "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点进去第一屏的数据 + "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; + 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']; + convMsg.unread = json['unread'] ?? 0; convMsg.timestamp = json['timestamp']; convMsg.lastMsgSeq = json['last_msg_seq']; convMsg.lastClientMsgNO = json['last_client_msg_no']; @@ -127,7 +140,7 @@ class HttpUtils { ..onHttpClientCreate = (client) { return httpClient; }; - final response = await dio.post('$apiURL/channel/messagesync', data: { + final response = await dio.post('$apiURL/message/channel/sync', data: { "login_uid": UserInfo.uid, // 当前登录用户uid "channel_id": channelID, // 频道ID "channel_type": channelType, // 频道类型 @@ -163,14 +176,303 @@ class HttpUtils { msg.clientMsgNO = json['client_msg_no']; msg.messageSeq = json['message_seq']; msg.fromUID = json['from_uid']; + msg.isDeleted = json['is_deleted']; msg.timestamp = json['timestamp']; // msg.payload = json['payload']; - String payload = json['payload']; + + // String payload = json['payload']; try { - msg.payload = jsonDecode(utf8.decode(base64Decode(payload))); + msg.payload = json['payload']; + // msg.payload = jsonDecode(utf8.decode(base64Decode(payload))); } catch (e) { // print('异常了'); } + // 解析扩展 + var extraJson = json['message_extra']; + if (extraJson != null) { + var extra = getMsgExtra(extraJson); + msg.messageExtra = extra; + } return msg; } + + static WKSyncExtraMsg getMsgExtra(dynamic extraJson) { + var extra = WKSyncExtraMsg(); + extra.messageID = extraJson['message_id']; + extra.messageIdStr = extraJson['message_id_str']; + extra.revoke = extraJson['revoke'] ?? 0; + extra.revoker = extraJson['revoker'] ?? ''; + extra.readed = extraJson['readed'] ?? 0; + extra.readedCount = extraJson['readed_count'] ?? 0; + extra.isMutualDeleted = extraJson['is_mutual_deleted'] ?? 0; + 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 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; + }; + try { + final response = await dio.get('$apiURL/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); + } + } catch (e) { + print('获取用户信息失败$e'); + } + } + + static 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: { + 'login_uid': UserInfo.uid, + 'channel_id': channelId, + 'channel_type': channelType, + 'client_msg_no': clientMsgNo, + 'message_id': msgId, + }); + if (response.statusCode == HttpStatus.ok) { + print('撤回消息成功'); + } + } catch (e) { + print('获取用户信息失败$e'); + } + } + + static 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.delete('$apiURL/message', 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); + } + } catch (e) { + print('删除消息失败$e'); + } + } + + 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; + }; + try { + final response = await dio.post('$apiURL/message/extra/sync', data: { + 'login_uid': UserInfo.uid, + 'channel_id': channelId, + 'channel_type': channelType, + 'source': UserInfo.uid, + 'limit': 100, + 'extra_version': version, + }); + if (response.statusCode == HttpStatus.ok) { + var arrJson = response.data; + if (arrJson != null && arrJson.length > 0) { + List list = []; + for (int i = 0; i < arrJson.length; i++) { + var extraJson = arrJson[i]; + WKMsgExtra extra = WKMsgExtra(); + extra.messageID = extraJson['message_id_str']; + extra.revoke = extraJson['revoke'] ?? 0; + extra.revoker = extraJson['revoker'] ?? ''; + extra.readed = extraJson['readed'] ?? 0; + extra.readedCount = extraJson['readed_count'] ?? 0; + extra.isMutualDeleted = extraJson['is_mutual_deleted'] ?? 0; + list.add(extra); + } + WKIM.shared.messageManager.saveRemoteExtraMsg(list); + } + } + } catch (e) { + print('同步消息扩展失败$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; + }; + try { + final response = await dio.put('$apiURL/conversation/clearUnread', data: { + 'login_uid': UserInfo.uid, + 'channel_id': channelId, + 'channel_type': channelType, + 'unread': 0, + }); + if (response.statusCode == HttpStatus.ok) { + print('清空红点成功'); + } + } catch (e) { + print('清空红点失败$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; + }; + try { + int maxSeq = await WKIM.shared.messageManager + .getMaxMessageSeq(channelId, channelType); + final response = await dio.post('$apiURL/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'); + } + } + + // 创建群 + 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: { + 'login_uid': UserInfo.uid, + 'group_no': groupNo, + }); + if (response.statusCode == HttpStatus.ok) { + return true; + } else { + return false; + } + } catch (e) { + print('创建群失败$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: { + 'login_uid': UserInfo.uid, + 'name': groupName, + }); + if (response.statusCode == HttpStatus.ok) { + return true; + } else { + return false; + } + } catch (e) { + print('修改群名称失败$e'); + return false; + } + } } diff --git a/example/lib/im.dart b/example/lib/im.dart index aa92ab5..af5e32f 100644 --- a/example/lib/im.dart +++ b/example/lib/im.dart @@ -1,13 +1,12 @@ import 'package:example/const.dart'; import 'package:wukongimfluttersdk/common/options.dart'; -import 'package:wukongimfluttersdk/entity/channel.dart'; import 'package:wukongimfluttersdk/model/wk_image_content.dart'; import 'package:wukongimfluttersdk/model/wk_video_content.dart'; import 'package:wukongimfluttersdk/model/wk_voice_content.dart'; import 'package:wukongimfluttersdk/type/const.dart'; import 'package:wukongimfluttersdk/wkim.dart'; -import 'custom_message.dart'; +import 'order_message_content.dart'; import 'http.dart'; class IMUtils { @@ -15,7 +14,7 @@ class IMUtils { bool result = await WKIM.shared .setup(Options.newDefault(UserInfo.uid, UserInfo.token)); WKIM.shared.options.getAddr = (Function(String address) complete) async { - String ip = await HttpUtils.getIP(); + String ip = await HttpUtils.getIP(UserInfo.uid); complete(ip); }; if (result) { @@ -24,57 +23,64 @@ class IMUtils { } // 注册自定义消息 WKIM.shared.messageManager - .registerMsgContent(12, (data) => CustomMsg("").decodeJson(data)); + .registerMsgContent(56, (data) => OrderMsg().decodeJson(data)); return result; } + // 监听sdk事件 + // 以下事件必须得实现 static initListener() { - var imgs = [ - "https://lmg.jj20.com/up/allimg/tx29/06052048151752929.png", - "https://pic.imeitou.com/uploads/allimg/2021061715/aqg1wx3nsds.jpg", - "https://lmg.jj20.com/up/allimg/tx30/10121138219844229.jpg", - "https://lmg.jj20.com/up/allimg/tx30/10121138219844229.jpg", - "https://lmg.jj20.com/up/allimg/tx28/430423183653303.jpg", - "https://lmg.jj20.com/up/allimg/tx23/520420024834916.jpg", - "https://himg.bdimg.com/sys/portraitn/item/public.1.a535a65d.tJe8MgWmP8zJ456B73Kzfg", - "https://img2.baidu.com/it/u=3324164588,1070151830&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500", - "https://img1.baidu.com/it/u=3916753633,2634890492&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400", - "https://img0.baidu.com/it/u=4210586523,443489101&fm=253&fmt=auto&app=138&f=JPEG?w=304&h=304", - "https://img2.baidu.com/it/u=2559320899,1546883787&fm=253&fmt=auto&app=138&f=JPEG?w=441&h=499", - "https://img0.baidu.com/it/u=2952429745,3806929819&fm=253&fmt=auto&app=138&f=JPEG?w=380&h=380", - "https://img2.baidu.com/it/u=3783923022,668713258&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", - ]; - + // 监听同步消息扩展 + WKIM.shared.cmdManager.addOnCmdListener('sys_im', (wkcmd) async { + if (wkcmd.cmd == 'messageRevoke') { + var channelID = wkcmd.param['channel_id']; + var channelType = wkcmd.param['channel_type']; + if (channelID != '') { + // 同步消息扩展 + var maxVersion = await WKIM.shared.messageManager + .getMaxExtraVersionWithChannel(channelID, channelType); + HttpUtils.syncMsgExtra(channelID, channelType, maxVersion); + } + } else if (wkcmd.cmd == 'channelUpdate') { + var channelID = wkcmd.param['channel_id']; + var channelType = wkcmd.param['channel_type']; + if (channelID != '') { + if (channelType == WKChannelType.personal) { + // 同步个人信息 + HttpUtils.getUserInfo(channelID); + } else if (channelType == WKChannelType.group) { + // 同步群信息 + HttpUtils.getGroupInfo(channelID); + } + } + } else if (wkcmd.cmd == 'unreadClear') { + print('清空红点的cmd'); + // 未读消息清除 + var channelID = wkcmd.param['channel_id']; + var channelType = wkcmd.param['channel_type']; + var unread = wkcmd.param['unread']; + if (channelID != '') { + WKIM.shared.conversationManager + .updateRedDot(channelID, channelType, unread); + } + } + }); + // 监听同步某个频道的消息 WKIM.shared.messageManager.addOnSyncChannelMsgListener((channelID, channelType, startMessageSeq, endMessageSeq, limit, pullMode, back) { - // 同步某个频道的消息 HttpUtils.syncChannelMsg(channelID, channelType, startMessageSeq, endMessageSeq, limit, pullMode, (p0) => back(p0)); }); - // 获取channel资料 + // 监听获取channel资料(群/个人信息) WKIM.shared.channelManager .addOnGetChannelListener((channelId, channelType, back) { - print('获取频道资料'); if (channelType == WKChannelType.personal) { // 获取个人资料 - // 这里直接返回了 - // todo 实际情况可通过API请求后返回 - var channel = WKChannel(channelId, channelType); - channel.channelName = "【单聊】${channel.channelID}"; - var index = channel.channelID.hashCode % imgs.length; - channel.avatar = imgs[index]; - channel.remoteExtraMap = {'status': 1, 'notice': 'xx'}; - channel.localExtra = {'localStatus': 1, 'localNotice': 'nxx'}; - back(channel); + print('获取个人资料$channelId'); + HttpUtils.getUserInfo(channelId); } else if (channelType == WKChannelType.group) { // 获取群资料 - var channel = WKChannel(channelId, channelType); - channel.channelName = "【群聊】${channel.channelID}"; - var index = channel.channelID.hashCode % imgs.length; - channel.avatar = imgs[index]; - channel.remoteExtraMap = {'status': 2, 'notice': 'ss'}; - channel.localExtra = {'localStatus': 2, 'localNotice': 'nss'}; - back(channel); + HttpUtils.getGroupInfo(channelId); } }); // 监听同步最近会话 diff --git a/example/lib/msg.dart b/example/lib/msg.dart index 65a3c78..e52f6c8 100644 --- a/example/lib/msg.dart +++ b/example/lib/msg.dart @@ -1,6 +1,5 @@ import 'package:wukongimfluttersdk/entity/msg.dart'; import 'package:wukongimfluttersdk/type/const.dart'; -import 'package:wukongimfluttersdk/wkim.dart'; import 'const.dart'; @@ -12,10 +11,6 @@ class UIMsg { if (wkMsg.messageContent == null) { return ''; } - var readCount = 0; - if (wkMsg.wkMsgExtra != null) { - readCount = wkMsg.wkMsgExtra!.readedCount; - } return wkMsg.messageContent!.displayText(); // return "${wkMsg.messageContent!.displayText()} [是否需要回执:${wkMsg.setting.receipt}],[已读数量:$readCount]"; } diff --git a/example/lib/order_message_content.dart b/example/lib/order_message_content.dart new file mode 100644 index 0000000..77dee6e --- /dev/null +++ b/example/lib/order_message_content.dart @@ -0,0 +1,37 @@ +import 'package:wukongimfluttersdk/model/wk_message_content.dart'; + +class OrderMsg extends WKMessageContent { + var orderNo = ""; + var title = ''; + var imgUrl = ''; + var num = 0; + var price = 0; + OrderMsg() { + contentType = 56; + } + @override + Map encodeJson() { + return { + "orderNo": orderNo, + 'title': title, + 'imgUrl': imgUrl, + 'num': num, + 'price': price + }; + } + + @override + WKMessageContent decodeJson(Map json) { + title = json["title"]; + orderNo = json["orderNo"]; + imgUrl = json["imgUrl"]; + num = json["num"]; + price = json["price"]; + return this; + } + + @override + String displayText() { + return "[订单消息]"; + } +} diff --git a/example/lib/popmenu_util.dart b/example/lib/popmenu_util.dart new file mode 100644 index 0000000..a3aaf44 --- /dev/null +++ b/example/lib/popmenu_util.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; + +import 'popup_item.dart'; + +class PopmenuUtil { + static Future showPopupMenu(BuildContext context, + LongPressStartDetails details, List items) { + final List popupMenuItems = []; + for (PopupItem item in items) { + PopupMenuItem popupMenuItem = PopupMenuItem( + // PopupMenuItem 的坑,默认为8,点击到边矩的地方会无反应 + padding: const EdgeInsets.all(0), + onTap: item.onTap, + child: Builder(builder: (context0) { + // 这里需要使用 新的 context ,不然点击会无反应。 + // 区分现有的 context + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.all(8.0), + child: Text( + item.text, + style: const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.bold), + ), + ), + ); + }), + ); + + popupMenuItems.add(popupMenuItem); + } + + RenderBox? renderBox = + Overlay.of(context).context.findRenderObject() as RenderBox; + + // 表示位置(在画面边缘会自动调整位置) + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx + 110, // 菜单显示位置X轴坐标 + details.globalPosition.dy - 40, // 菜单显示位置Y轴坐标 + ), + Offset.zero & renderBox.size, + ); + + return showMenu( + color: Colors.white, + context: context, + position: position, + items: popupMenuItems, + useRootNavigator: true); + } +} diff --git a/example/lib/popup_item.dart b/example/lib/popup_item.dart new file mode 100644 index 0000000..1ca895c --- /dev/null +++ b/example/lib/popup_item.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class PopupItem { + final String text; + final GestureTapCallback? onTap; + PopupItem({this.text = '', this.onTap}); +} diff --git a/example/lib/chat.dart b/example/lib/ui_chat.dart similarity index 62% rename from example/lib/chat.dart rename to example/lib/ui_chat.dart index dc01692..4e2be5f 100644 --- a/example/lib/chat.dart +++ b/example/lib/ui_chat.dart @@ -1,4 +1,6 @@ import 'package:example/const.dart'; +import 'package:example/http.dart'; +import 'package:example/order_message_content.dart'; import 'package:flutter/material.dart'; import 'package:wukongimfluttersdk/entity/channel.dart'; import 'package:wukongimfluttersdk/entity/msg.dart'; @@ -7,7 +9,9 @@ import 'package:wukongimfluttersdk/proto/proto.dart'; import 'package:wukongimfluttersdk/type/const.dart'; import 'package:wukongimfluttersdk/wkim.dart'; +import 'chatview.dart'; import 'msg.dart'; +import 'ui_input_dialog.dart'; class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -45,14 +49,8 @@ class ChatListDataState extends State { WKIM.shared.channelManager .getChannel(channelID, channelType) .then((channel) { - print(channel?.localExtra); - print(channel?.remoteExtraMap); WKIM.shared.channelManager.fetchChannelInfo(channelID, channelType); - if (channelType == WKChannelType.group) { - title = '${channel?.channelName}'; - } else { - title = '${channel?.channelName}'; - } + title = '${channel?.channelName}'; }); } List msgList = []; @@ -66,6 +64,20 @@ class ChatListDataState extends State { } initListener() { + // 监听刷新频道 + WKIM.shared.channelManager.addOnRefreshListener('chat', (channel) { + if (channelID == channel.channelID) { + title = channel.channelName; + } + for (var i = 0; i < msgList.length; i++) { + if (msgList[i].wkMsg.fromUID == channel.channelID) { + msgList[i].wkMsg.setFrom(channel); + } + } + setState(() {}); + }); + + // 监听发送消息入库返回 WKIM.shared.messageManager.addOnMsgInsertedListener((wkMsg) { setState(() { msgList.add(UIMsg(wkMsg)); @@ -74,6 +86,8 @@ class ChatListDataState extends State { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); }); }); + + // 监听新消息 WKIM.shared.messageManager.addOnNewMsgListener('chat', (msgs) { setState(() { for (var i = 0; i < msgs.length; i++) { @@ -90,6 +104,8 @@ class ChatListDataState extends State { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); }); }); + + // 监听消息刷新 WKIM.shared.messageManager.addOnRefreshMsgListener('chat', (wkMsg) { for (var i = 0; i < msgList.length; i++) { if (msgList[i].wkMsg.clientMsgNO == wkMsg.clientMsgNO) { @@ -102,6 +118,19 @@ class ChatListDataState extends State { } setState(() {}); }); + + // 监听删除消息 + WKIM.shared.messageManager.addOnDeleteMsgListener('chat', (clientMsgNo) { + for (var i = 0; i < msgList.length; i++) { + if (msgList[i].wkMsg.clientMsgNO == clientMsgNo) { + setState(() { + msgList.removeAt(i); + }); + break; + } + } + }); + // 清除聊天记录 WKIM.shared.messageManager.addOnClearChannelMsgListener("chat", (channelId, channelType) { @@ -178,67 +207,16 @@ class ChatListDataState extends State { } Widget _buildRow(UIMsg uiMsg) { + if (uiMsg.wkMsg.wkMsgExtra?.revoke == 1) { + return getRevokedView(uiMsg, context); + } if (uiMsg.wkMsg.fromUID == UserInfo.uid) { return Container( padding: const EdgeInsets.only(left: 0, top: 5, right: 0, bottom: 5), child: Row( children: [ - Expanded( - child: Container( - padding: - const EdgeInsets.only(left: 5, top: 3, right: 5, bottom: 3), - margin: const EdgeInsets.only( - left: 60, top: 0, right: 5, bottom: 0), - decoration: const BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(12)), - color: Colors.blue), - alignment: Alignment.bottomRight, - child: Column( - children: [ - Container( - alignment: Alignment.centerRight, - child: Text( - uiMsg.getShowContent(), - style: - const TextStyle(color: Colors.white, fontSize: 16), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - uiMsg.getShowTime(), - style: - const TextStyle(color: Colors.grey, fontSize: 12), - ), - Image( - image: AssetImage(uiMsg.getStatusIV()), - width: 30, - height: 30) - ], - ), - ], - ), - ), - ), - Container( - decoration: const BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(20)), - color: Color.fromARGB(255, 243, 33, 131)), - width: 50, - alignment: Alignment.center, - height: 50, - margin: const EdgeInsets.fromLTRB(0, 0, 10, 0), - child: Text( - CommonUtils.getAvatar(uiMsg.wkMsg.fromUID), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold), - ), - ), + getSendView(uiMsg, context), + chatAvatar(uiMsg), ], ), ); @@ -246,62 +224,7 @@ class ChatListDataState extends State { return Container( padding: const EdgeInsets.only(left: 0, top: 5, right: 0, bottom: 5), child: Row( - children: [ - Container( - decoration: const BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(20)), - color: Color.fromARGB(255, 215, 80, 1)), - width: 50, - alignment: Alignment.center, - height: 50, - margin: const EdgeInsets.fromLTRB(0, 0, 10, 0), - child: Text( - CommonUtils.getAvatar(uiMsg.wkMsg.fromUID), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold), - ), - ), - Expanded( - child: Container( - alignment: Alignment.centerLeft, - margin: const EdgeInsets.only( - left: 0, top: 0, right: 60, bottom: 0), - child: Container( - padding: const EdgeInsets.only( - left: 10, top: 3, right: 10, bottom: 3), - decoration: const BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(12)), - color: Color.fromARGB(255, 163, 33, 243)), - child: Column( - children: [ - Container( - alignment: Alignment.topLeft, - child: Text( - uiMsg.getShowContent(), - style: const TextStyle( - color: Colors.white, fontSize: 16), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - uiMsg.getShowTime(), - style: const TextStyle( - color: Colors.grey, fontSize: 12), - ) - ], - ) - ], - ), - ), - ), - ) - ], + children: [chatAvatar(uiMsg), getRecvView(uiMsg, context)], ), ); } @@ -315,23 +238,90 @@ class ChatListDataState extends State { appBar: AppBar( title: Text(title), actions: [ - MaterialButton( - child: const Text( - '清空记录', - style: TextStyle(color: Color.fromARGB(255, 4, 80, 194)), - ), - onPressed: () async { - var v = await WKIM.shared.messageManager - .getMaxExtraVersionWithChannel(channelID, channelType); - print(v); - // WKIM.shared.messageManager - // .clearWithChannel(channelID, channelType); - }), + PopupMenuButton( + onSelected: (value) => { + if (value == '清空聊天记录') + { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + backgroundColor: Colors.white, + title: const Text('确认清空聊天记录?'), + content: const Text('清空后将无法恢复,确定要清空吗?'), + actions: [ + GestureDetector( + child: const Text( + '取消', + style: TextStyle( + color: Color.fromARGB(255, 113, 112, 112), + fontSize: 16, + fontWeight: FontWeight.bold), + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + GestureDetector( + child: Container( + margin: const EdgeInsets.only(left: 20), + child: const Text('确认', + style: TextStyle( + color: Colors.red, + fontSize: 16, + fontWeight: FontWeight.bold)), + ), + onTap: () { + Navigator.of(context).pop(); + HttpUtils.clearChannelMsg( + channelID, channelType); + }, + ), + ], + ); + }) + } + else if (value == '修改群名称') + {showUpdateChannelNameDialog(context)} + }, + itemBuilder: (context) { + return getPopuMenuItems(); + }, + ) ], ), + floatingActionButton: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: 'previous', + onPressed: () { + getPrevious(); + }, + tooltip: '上一页', + backgroundColor: Colors.transparent, + child: const Icon(Icons.vertical_align_top), + ), + const SizedBox(height: 10.0), + FloatingActionButton( + heroTag: 'next', + onPressed: () { + getLast(); + }, + backgroundColor: Colors.transparent, + tooltip: '下一页', + child: const Icon(Icons.vertical_align_bottom), + ), + const SizedBox(height: 20.0), + ]), body: Container( padding: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10), + color: const Color.fromARGB(255, 221, 221, 221), child: Column( children: [ Expanded( @@ -356,36 +346,22 @@ class ChatListDataState extends State { ), MaterialButton( onPressed: () { - getPrevious(); + DateTime now = DateTime.now(); + var orderMsg = OrderMsg(); + orderMsg.title = "问界M9纯电版 旗舰SUV 2024款 3.0L 自动 豪华版"; + orderMsg.num = 300; + orderMsg.price = 20; + orderMsg.orderNo = '${now.millisecondsSinceEpoch}'; + orderMsg.imgUrl = + "https://img0.baidu.com/it/u=4245434814,3643211003&fm=253&fmt=auto&app=120&f=JPEG?w=674&h=500"; + WKIM.shared.messageManager.sendMessage( + orderMsg, WKChannel(channelID, channelType)); }, color: Colors.brown, - child: - const Text("上一页", style: TextStyle(color: Colors.white)), - ), - MaterialButton( - onPressed: () { - WKMsgExtra extra = WKMsgExtra(); - extra.messageID = "112"; - extra.channelID = channelID; - extra.channelType = channelType; - extra.readed = 1; - extra.extraVersion = 100871; - List list = []; - list.add(extra); - WKMsgExtra extra1 = WKMsgExtra(); - extra1.messageID = "1122"; - extra1.channelID = channelID; - extra1.channelType = channelType; - extra1.readed = 1; - extra1.extraVersion = 100872; - list.add(extra1); - WKIM.shared.messageManager.saveRemoteExtraMsg(list); - // getLast(); - }, - color: Colors.brown, - child: - const Text("下一页", style: TextStyle(color: Colors.white)), + child: const Text("自定义消息", + style: TextStyle(color: Colors.white)), ), + const SizedBox(width: 10.0), MaterialButton( onPressed: () { if (content != '') { @@ -458,10 +434,42 @@ class ChatListDataState extends State { ); } + getPopuMenuItems() { + var list = >[]; + list.add(const PopupMenuItem( + value: '清空聊天记录', + child: Text('清空聊天记录'), + )); + if (channelType == WKChannelType.group) { + list.add(const PopupMenuItem( + value: '修改群名称', + child: Text('修改群名称'), + )); + } + return list; + } + + showUpdateChannelNameDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) => InputDialog( + title: const Text("请输入群名称"), + isOnlyText: true, + hintText: '请输入群名称', + back: (name, channelType) { + HttpUtils.updateGroupName(channelID, name); + }, + ), + ); + } + @override void dispose() { super.dispose(); + // 移出监听 WKIM.shared.messageManager.removeNewMsgListener('chat'); WKIM.shared.messageManager.removeOnRefreshMsgListener('chat'); + WKIM.shared.messageManager.removeDeleteMsgListener('chat'); + WKIM.shared.channelManager.removeOnRefreshListener('chat'); } } diff --git a/example/lib/home.dart b/example/lib/ui_conversation.dart similarity index 81% rename from example/lib/home.dart rename to example/lib/ui_conversation.dart index 9ccbe64..19d0185 100644 --- a/example/lib/home.dart +++ b/example/lib/ui_conversation.dart @@ -1,14 +1,16 @@ import 'package:example/const.dart'; +import 'package:example/http.dart'; import 'package:flutter/material.dart'; -import 'package:wukongimfluttersdk/common/logs.dart'; import 'package:wukongimfluttersdk/entity/conversation.dart'; import 'package:wukongimfluttersdk/entity/reminder.dart'; import 'package:wukongimfluttersdk/type/const.dart'; import 'package:wukongimfluttersdk/wkim.dart'; -import 'chat.dart'; -import 'contestation.dart'; -import 'input_dialog.dart'; +import 'popmenu_util.dart'; +import 'popup_item.dart'; +import 'ui_chat.dart'; +import 'conversation_msg.dart'; +import 'ui_input_dialog.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @@ -112,7 +114,7 @@ class ListViewShowDataState extends State { if (msgList[i].msg.channelID == channel.channelID && msgList[i].msg.channelType == channel.channelType) { msgList[i].msg.setWkChannel(channel); - msgList[i].channelAvatar = channel.avatar; + msgList[i].channelAvatar = "${HttpUtils.apiURL}/${channel.avatar}"; msgList[i].channelName = channel.channelName; setState(() {}); break; @@ -173,7 +175,8 @@ class ListViewShowDataState extends State { if (uiConversation.channelAvatar == '') { uiConversation.msg.getWkChannel().then((channel) { if (channel != null) { - uiConversation.channelAvatar = channel.avatar; + uiConversation.channelAvatar = + "${HttpUtils.apiURL}/${channel.avatar}"; } }); } @@ -189,6 +192,10 @@ class ListViewShowDataState extends State { } else { uiConversation.channelName = channel.channelRemark; } + if (uiConversation.channelName == '') { + WKIM.shared.channelManager.fetchChannelInfo( + uiConversation.msg.channelID, uiConversation.msg.channelType); + } } else { WKIM.shared.channelManager.fetchChannelInfo( uiConversation.msg.channelID, uiConversation.msg.channelType); @@ -200,7 +207,8 @@ class ListViewShowDataState extends State { Widget _buildRow(UIConversation uiMsg) { return Container( - margin: const EdgeInsets.all(10), + color: Colors.white, + padding: const EdgeInsets.all(10), child: Row( children: [ Container( @@ -282,7 +290,9 @@ class ListViewShowDataState extends State { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: const Color.fromARGB(255, 221, 221, 221), appBar: AppBar( + backgroundColor: const Color.fromARGB(255, 251, 246, 246), title: Text(_connectionStatusStr), ), body: ListView.builder( @@ -290,6 +300,9 @@ class ListViewShowDataState extends State { itemCount: msgList.length, itemBuilder: (context, pos) { return GestureDetector( + onLongPressStart: (details) { + longClick(msgList[pos], context, details); + }, onTap: () { Navigator.push( context, @@ -315,46 +328,6 @@ class ListViewShowDataState extends State { child: const Icon(Icons.add), ), persistentFooterButtons: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 240, 117, 2), - ), - onPressed: () { - WKIM.shared.conversationManager.clearAllRedDot(); - }, - child: const Text( - '清除未读', - style: TextStyle(color: Colors.white), - ), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 240, 2, 133), - ), - onPressed: () { - if (msgList.isEmpty) { - return; - } - - List list = []; - WKReminder reminder = WKReminder(); - reminder.needUpload = 0; - reminder.type = WKMentionType.wkReminderTypeMentionMe; - reminder.data = '[有人@你]'; - reminder.done = 0; - reminder.reminderID = 11; - reminder.version = 1; - reminder.publisher = "uid_1"; - reminder.channelID = msgList[0].msg.channelID; - reminder.channelType = msgList[0].msg.channelType; - list.add(reminder); - WKIM.shared.reminderManager.saveOrUpdateReminders(list); - }, - child: const Text( - '提醒项', - style: TextStyle(color: Colors.white), - ), - ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, @@ -386,23 +359,57 @@ class ListViewShowDataState extends State { _showDialog(BuildContext context) { showDialog( context: context, - builder: (BuildContext context) => InputDialog( + builder: (BuildContext _context) => InputDialog( title: const Text("创建新的聊天"), - back: (channelID, channelType) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChatPage(), - settings: RouteSettings( - arguments: ChatChannel( - channelID, - channelType, + back: (channelID, channelType) async { + bool isSuccess = await HttpUtils.createGroup(channelID); + if (isSuccess) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatPage(), + settings: RouteSettings( + arguments: ChatChannel( + channelID, + channelType, + ), ), ), - ), - ); + ); + } }, ), ); } + + longClick(UIConversation uiMsg, BuildContext context, + LongPressStartDetails details) { + List items = []; + if (uiMsg.msg.unreadCount > 0) { + items.add(PopupItem( + text: '设置已读', + onTap: () { + HttpUtils.clearUnread(uiMsg.msg.channelID, uiMsg.msg.channelType); + }, + )); + } + items.add(PopupItem( + text: '测试提醒项', + onTap: () { + List list = []; + WKReminder reminder = WKReminder(); + reminder.needUpload = 0; + reminder.type = WKMentionType.wkReminderTypeMentionMe; + reminder.data = '[有人@你]'; + reminder.done = 0; + reminder.reminderID = 11; + reminder.version = 1; + reminder.publisher = "uid_1"; + reminder.channelID = uiMsg.msg.channelID; + reminder.channelType = uiMsg.msg.channelType; + list.add(reminder); + WKIM.shared.reminderManager.saveOrUpdateReminders(list); + })); + PopmenuUtil.showPopupMenu(context, details, items); + } } diff --git a/example/lib/input_dialog.dart b/example/lib/ui_input_dialog.dart similarity index 62% rename from example/lib/input_dialog.dart rename to example/lib/ui_input_dialog.dart index 614ff66..c1f2da0 100644 --- a/example/lib/input_dialog.dart +++ b/example/lib/ui_input_dialog.dart @@ -3,12 +3,16 @@ import 'package:wukongimfluttersdk/type/const.dart'; class InputDialog extends StatefulWidget { const InputDialog( - {Key? key, this.hintText = "请输入对方uid...", this.title, this.back}) + {Key? key, + this.isOnlyText = false, + this.hintText = "请输入对方uid...", + this.title, + this.back}) : super(key: key); final Function(String channelID, int channelType)? back; final Widget? title; // Text('New nickname'.tr) final String? hintText; - + final bool isOnlyText; @override State createState() => _InputDialogState( title: this.title, hintText: this.hintText, back: this.back); @@ -31,6 +35,10 @@ class _InputDialogState extends State { Widget build(BuildContext context) { return AlertDialog( title: title, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + backgroundColor: Colors.white, content: SizedBox( height: 125, child: Column( @@ -43,36 +51,37 @@ class _InputDialogState extends State { }, decoration: InputDecoration(hintText: hintText), autofocus: true), - Row( - children: [ - Radio( - value: RadioValue.personal, - groupValue: _radioValue, - onChanged: (value) { - setState(() { - hintText = '请输入对方uid'; - _radioValue = value!; - }); - }), - const Text( - '单聊', - style: TextStyle(fontSize: 18.0), - ), - Radio( - value: RadioValue.group, - groupValue: _radioValue, - onChanged: (value) { - setState(() { - hintText = '请输入群Id'; - _radioValue = value!; - }); - }), - const Text( - '群聊', - style: TextStyle(fontSize: 18.0), - ), - ], - ) + if (!widget.isOnlyText) + Row( + children: [ + Radio( + value: RadioValue.personal, + groupValue: _radioValue, + onChanged: (value) { + setState(() { + hintText = '请输入对方uid'; + _radioValue = value!; + }); + }), + const Text( + '单聊', + style: TextStyle(fontSize: 18.0), + ), + Radio( + value: RadioValue.group, + groupValue: _radioValue, + onChanged: (value) { + setState(() { + hintText = '请输入群Id'; + _radioValue = value!; + }); + }), + const Text( + '群聊', + style: TextStyle(fontSize: 18.0), + ), + ], + ) ], ), ), diff --git a/example/lib/main.dart b/example/lib/ui_main.dart similarity index 97% rename from example/lib/main.dart rename to example/lib/ui_main.dart index e3b0b44..0dc3ab8 100644 --- a/example/lib/main.dart +++ b/example/lib/ui_main.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:wukongimfluttersdk/wkim.dart'; -import 'home.dart'; +import 'ui_conversation.dart'; void main() { runApp(const MyApp()); @@ -74,7 +74,7 @@ class LoginDemoState extends State { TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ), Text( - '悟空IM演示程序。当前SDK版本:V1.0.1', + '悟空IM演示程序。当前SDK版本:V1.5.6', textAlign: TextAlign.center, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -92,7 +92,7 @@ class LoginDemoState extends State { }, decoration: const InputDecoration( labelText: 'API基地址', - hintText: 'API基地址 默认【https://api.githubim.com】'), + hintText: 'API基地址 默认【http://62.234.8.38:7090/v1】'), ), ), Padding( diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 092d222..3d0a04f 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:example/main.dart'; +import 'package:example/ui_main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { diff --git a/lib/common/logs.dart b/lib/common/logs.dart index 3f0841a..128381b 100644 --- a/lib/common/logs.dart +++ b/lib/common/logs.dart @@ -1,16 +1,24 @@ +import 'package:wukongimfluttersdk/wkim.dart'; + class Logs { static debug(Object msg) { - // ignore: avoid_print - print("debug:$msg"); + if (WKIM.shared.options.debug) { + // ignore: avoid_print + print("debug:$msg"); + } } static info(Object msg) { - // ignore: avoid_print - print("info:$msg"); + if (WKIM.shared.options.debug) { + // ignore: avoid_print + print("info:$msg"); + } } static error(Object msg) { - // ignore: avoid_print - print("error:$msg"); + if (WKIM.shared.options.debug) { + // ignore: avoid_print + print("error:$msg"); + } } } diff --git a/lib/common/options.dart b/lib/common/options.dart index d46ca81..7a1a141 100644 --- a/lib/common/options.dart +++ b/lib/common/options.dart @@ -4,6 +4,8 @@ class Options { String? uid, token; String? addr; // connect address IP:PORT int protoVersion = 0x04; // protocol version + int deviceFlag = 0; + bool debug = true; Function(Function(String addr) complete)? getAddr; // async get connect address Proto proto = Proto(); diff --git a/lib/entity/msg.dart b/lib/entity/msg.dart index c178ebe..25eaf1e 100644 --- a/lib/entity/msg.dart +++ b/lib/entity/msg.dart @@ -50,7 +50,7 @@ class WKMsg { _channelInfo = wkChannel; } - getChannelInfo() { + WKChannel? getChannelInfo() { return _channelInfo; } @@ -58,7 +58,7 @@ class WKMsg { _memberOfFrom = wkChannelMember; } - getMemberOfFrom() { + WKChannelMember? getMemberOfFrom() { return _memberOfFrom; } @@ -66,7 +66,7 @@ class WKMsg { _from = channel; } - getFrom() { + WKChannel? getFrom() { return _from; } } diff --git a/lib/manager/cmd_manager.dart b/lib/manager/cmd_manager.dart index dde5088..69fa36a 100644 --- a/lib/manager/cmd_manager.dart +++ b/lib/manager/cmd_manager.dart @@ -13,6 +13,12 @@ class WKCMDManager { 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; diff --git a/lib/manager/connect_manager.dart b/lib/manager/connect_manager.dart index 7a74b18..4ba73d6 100644 --- a/lib/manager/connect_manager.dart +++ b/lib/manager/connect_manager.dart @@ -36,17 +36,18 @@ class _WKSocket { _isListening = false; _instance = null; try { - _socket?.destroy(); + _socket?.close(); + // _socket?.destroy(); } finally { _socket = null; // 现在可以将 _socket 设置为 null } } - void send(Uint8List data) { + send(Uint8List data) { try { if (_socket?.remotePort != null) { _socket?.add(data); // 使用安全调用操作符 - _socket?.flush(); + return _socket?.flush(); } } catch (e) { Logs.debug('发送消息错误$e'); @@ -78,7 +79,7 @@ class WKConnectionManager { Timer? checkNetworkTimer; final heartIntervalSecond = const Duration(seconds: 60); final checkNetworkSecond = const Duration(seconds: 1); - final HashMap _sendingMsgMap = HashMap(); + final LinkedHashMap _sendingMsgMap = LinkedHashMap(); HashMap? _connectionListenerMap; _WKSocket? _socket; addOnConnectionStatus(String key, Function(int, int?, ConnectionInfo?) back) { @@ -270,15 +271,18 @@ class WKConnectionManager { setConnectionStatus(WKConnectStatus.success, reasoncode: connackPacket.reasonCode, info: ConnectionInfo(connackPacket.nodeId)); + // Future.delayed(Duration(seconds: 1), () { + + // }); try { WKIM.shared.conversationManager.setSyncConversation(() { setConnectionStatus(WKConnectStatus.syncCompleted); + _resendMsg(); }); } catch (e) { Logs.error(e.toString()); } - _resendMsg(); _startHeartTimer(); _startCheckNetworkTimer(); } else { @@ -287,6 +291,7 @@ class WKConnectionManager { Logs.debug('连接失败!错误->${connackPacket.reasonCode}'); } } else if (packet.header.packetType == PacketType.recv) { + Logs.debug('收到消息'); var recvPacket = packet as RecvPacket; _verifyRecvMsg(recvPacket); if (!recvPacket.header.noPersist) { @@ -334,6 +339,7 @@ class WKConnectionManager { } _sendConnectPacket() async { + CryptoUtils.init(); var deviceID = await _getDeviceID(); var connectPacket = ConnectPacket( uid: WKIM.shared.options.uid!, @@ -346,10 +352,10 @@ class WKConnectionManager { _sendPacket(connectPacket); } - _sendPacket(Packet packet) { + _sendPacket(Packet packet) async { var data = WKIM.shared.options.proto.encode(packet); if (!isReconnection) { - _socket?.send(data); + await _socket?.send(data); } } @@ -366,8 +372,8 @@ class WKConnectionManager { setConnectionStatus(WKConnectStatus.noNetwork); } else { if (isReconnection) { - connect(); isReconnection = false; + connect(); } } }); @@ -519,13 +525,13 @@ class WKConnectionManager { return isDelete; } - _resendMsg() { + _resendMsg() async { _removeSendingMsg(); if (_sendingMsgMap.isNotEmpty) { - final it = _sendingMsgMap.entries.iterator; - while (it.moveNext()) { - if (it.current.value.isCanResend) { - _sendPacket(it.current.value.sendPacket); + for (var entry in _sendingMsgMap.entries) { + if (entry.value.isCanResend) { + Logs.debug("重发消息:${entry.value.sendPacket.clientSeq}"); + await _sendPacket(entry.value.sendPacket); } } } diff --git a/lib/manager/message_manager.dart b/lib/manager/message_manager.dart index a32b697..9d2273d 100644 --- a/lib/manager/message_manager.dart +++ b/lib/manager/message_manager.dart @@ -741,6 +741,9 @@ class WKMessageManager { map['order_seq'] = orderSeq; MessageDB.shared.updateMsgWithField(map, clientSeq); setRefreshMsg(wkMsg); + + // 更新最近会话 + WKIM.shared.conversationManager.saveWithLiMMsg(wkMsg, 0); } } diff --git a/lib/wkim.dart b/lib/wkim.dart index 19c0492..31d9573 100644 --- a/lib/wkim.dart +++ b/lib/wkim.dart @@ -1,4 +1,3 @@ -import 'package:wukongimfluttersdk/common/crypto_utils.dart'; import 'package:wukongimfluttersdk/common/mode.dart'; import 'package:wukongimfluttersdk/db/wk_db_helper.dart'; import 'package:wukongimfluttersdk/manager/channel_manager.dart'; @@ -28,7 +27,7 @@ class WKIM { Future setup(Options opts) async { options = opts; - CryptoUtils.init(); + deviceFlagApp = opts.deviceFlag; _initNormalMsgContent(); if (isApp()) { bool result = await WKDBHelper.shared.init(); @@ -68,6 +67,7 @@ class WKIM { }); } + @Deprecated('Use Options deviceFlag') void setDeviceFlag(int deviceFlag) { deviceFlagApp = deviceFlag; } diff --git a/pubspec.yaml b/pubspec.yaml index 4283204..751b908 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.5.5 +version: 1.5.7 homepage: https://github.com/WuKongIM/WuKongIMFlutterSDK environment: