diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..36d4708 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,65 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "dating_touchme_app", + "request": "launch", + "type": "dart" + }, + { + "name": "dating_touchme_app (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "dating_touchme_app (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "location_plugin", + "cwd": "location_plugin", + "request": "launch", + "type": "dart" + }, + { + "name": "location_plugin (profile mode)", + "cwd": "location_plugin", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "location_plugin (release mode)", + "cwd": "location_plugin", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "example", + "cwd": "location_plugin/example", + "request": "launch", + "type": "dart" + }, + { + "name": "example (profile mode)", + "cwd": "location_plugin/example", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "example (release mode)", + "cwd": "location_plugin/example", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/lib/controller/message/chat_controller.dart b/lib/controller/message/chat_controller.dart index cb69b67..765b704 100644 --- a/lib/controller/message/chat_controller.dart +++ b/lib/controller/message/chat_controller.dart @@ -4,6 +4,8 @@ import 'dart:convert'; import '../../im/im_manager.dart'; import '../../model/home/marriage_data.dart'; +import '../../model/live/gift_product_model.dart'; +import '../../network/network_service.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'conversation_controller.dart'; @@ -30,6 +32,12 @@ class ChatController extends GetxController { final RxBool isSendingVideo = RxBool(false); final RxString sendingStatus = RxString(''); + // 礼物产品列表 + final RxList giftProducts = [].obs; + + // 网络服务 + final NetworkService _networkService = NetworkService(); + ChatController({ required this.userId, MarriageData? userData, @@ -50,14 +58,86 @@ class ChatController extends GetxController { super.onInit(); // 注册到 IMManager,以便接收消息时能通知到此 Controller IMManager.instance.registerChatController(this); + + // 如果有外部传入的用户信息,先同步到 ConversationController 的缓存中 + if (_userData != null) { + _syncUserInfoToConversationController(); + } else { + // 如果没有外部传入的用户信息,尝试从 ConversationController 的缓存中获取 + _loadUserInfoFromConversationCache(); + } + // 初始化时获取用户信息和消息列表 fetchUserInfo(); fetchMessages(); + // 加载礼物产品列表 + loadGiftProducts(); // 如果有外部传入的用户信息,触发 UI 更新 if (_externalNickName != null || _externalAvatarUrl != null) { update(); } } + + /// 从 ConversationController 的缓存中加载用户信息 + void _loadUserInfoFromConversationCache() { + try { + if (Get.isRegistered()) { + final conversationController = Get.find(); + // 尝试从缓存中获取用户信息 + conversationController.loadContact(userId).then((extendedInfo) { + if (extendedInfo != null && (extendedInfo.nickName != null || extendedInfo.avatarUrl != null)) { + // 如果从缓存中获取到了用户信息,设置到外部信息中 + _externalNickName = extendedInfo.nickName; + _externalAvatarUrl = extendedInfo.avatarUrl; + // 触发 UI 更新 + update(); + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 从会话缓存加载用户信息: userId=$userId, nickName=${extendedInfo.nickName}'); + } + } + }); + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 从会话缓存加载用户信息失败: $e'); + } + } + } + + /// 将用户信息同步到 ConversationController 的缓存中 + /// 注意:用户信息会通过消息扩展字段自动传递,这里只是提前缓存以确保聊天列表能立即显示 + void _syncUserInfoToConversationController() { + try { + if (_userData == null) return; + + final nickName = _userData!.nickName; + final avatarUrl = _userData!.profilePhoto.trim().replaceAll('`', ''); // 移除照片URL中的反引号 + + // 更新到 ConversationController 的缓存(确保聊天列表能立即显示) + if (Get.isRegistered()) { + final conversationController = Get.find(); + + // 创建 ExtendedUserInfo 并缓存 + final extendedInfo = ExtendedUserInfo( + userId: userId, + nickName: nickName, + avatarUrl: avatarUrl, + ); + + // 直接更新缓存 + conversationController.cacheUserInfo(userId, extendedInfo); + + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 已将用户信息同步到会话列表缓存: userId=$userId, nickName=$nickName'); + Get.log('ℹ️ [ChatController] 用户信息将通过消息扩展字段自动传递给对方'); + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 同步用户信息失败: $e'); + } + } + } @override void onClose() { @@ -125,20 +205,49 @@ class ChatController extends GetxController { } } - /// 获取用户昵称(优先使用环信用户信息,如果没有则使用外部传入的) + /// 获取用户昵称(优先使用环信用户信息,如果没有或为空则使用外部传入的) String? get userNickName { - return userInfo.value?.nickName ?? _externalNickName; + final hxNickName = userInfo.value?.nickName; + // 只有当环信用户信息的昵称不为空且不为空字符串时,才使用它 + if (hxNickName != null && hxNickName.isNotEmpty) { + return hxNickName; + } + // 否则回退到外部传入的昵称 + return _externalNickName; } - /// 获取用户头像(优先使用环信用户信息,如果没有则使用外部传入的) + /// 获取用户头像(优先使用环信用户信息,如果没有或为空则使用外部传入的) String? get userAvatarUrl { - return userInfo.value?.avatarUrl ?? _externalAvatarUrl; + final hxAvatarUrl = userInfo.value?.avatarUrl; + // 只有当环信用户信息的头像不为空且不为空字符串时,才使用它 + if (hxAvatarUrl != null && hxAvatarUrl.isNotEmpty) { + return hxAvatarUrl; + } + // 否则回退到外部传入的头像 + return _externalAvatarUrl; } /// 发送消息 Future sendMessage(String content) async { EMMessage? tempMessage; try { + if (Get.isLogEnable) { + Get.log('📤 [ChatController] 准备发送消息: content=$content, targetUserId=$userId'); + } else { + print('📤 [ChatController] 准备发送消息: content=$content, targetUserId=$userId'); + } + + // 检查目标用户是否存在于IM系统中 + final userExists = await IMManager.instance.checkUserExists(userId); + if (!userExists) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 警告:目标用户可能不存在于IM系统中: $userId'); + } else { + print('⚠️ [ChatController] 警告:目标用户可能不存在于IM系统中: $userId'); + } + // 继续发送,但记录警告 + } + // 先创建消息对象(即使发送失败也要显示在列表中) tempMessage = EMMessage.createTxtSendMessage( targetId: userId, @@ -152,20 +261,94 @@ class ChatController extends GetxController { // 尝试发送消息 final message = await IMManager.instance.sendTextMessage(content, userId); if (message != null) { + if (Get.isLogEnable) { + Get.log('✅ [ChatController] SDK返回消息: msgId=${message.msgId}, status=${message.status}'); + } else { + print('✅ [ChatController] SDK返回消息: msgId=${message.msgId}, status=${message.status}'); + } + // 发送成功,替换临时消息 + // 注意:SDK 可能会改变消息ID(onMessageIdChanged),所以需要根据原始消息ID或内容来匹配 final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); if (index != -1) { + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 找到临时消息,替换: index=$index, oldMsgId=${tempMessage.msgId}, newMsgId=${message.msgId}'); + } else { + print('✅ [ChatController] 找到临时消息,替换: index=$index, oldMsgId=${tempMessage.msgId}, newMsgId=${message.msgId}'); + } messages[index] = message; + } else { + // 如果找不到原始消息(可能ID改变了),尝试根据内容匹配 + final contentIndex = messages.indexWhere((msg) => + msg.body.type == MessageType.TXT && + (msg.body as EMTextMessageBody).content == content && + msg.direction == MessageDirection.SEND + ); + if (contentIndex != -1) { + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 根据内容找到消息,替换: index=$contentIndex'); + } else { + print('✅ [ChatController] 根据内容找到消息,替换: index=$contentIndex'); + } + messages[contentIndex] = message; + } else { + // 如果还是找不到,直接添加新消息 + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 未找到匹配的消息,添加新消息'); + } else { + print('⚠️ [ChatController] 未找到匹配的消息,添加新消息'); + } + messages.add(message); + } } update(); + if (Get.isLogEnable) { + Get.log('✅ [ChatController] UI已更新,消息状态: ${message.status}'); + } else { + print('✅ [ChatController] UI已更新,消息状态: ${message.status}'); + } + // 更新会话列表 _refreshConversationList(); // 如果消息状态仍然是 PROGRESS,说明 SDK 的状态更新是异步的 // 等待一段时间后再次检查并更新状态 + // 注意:即使 sendMessage 返回成功,SDK 可能随后报错(如日志中的 custom internal error),所以需要持续检查状态 if (message.status == MessageStatus.PROGRESS) { - // 轮询检查消息状态,直到状态不再是 PROGRESS 或超过3秒 - _checkMessageStatusUntilComplete(message.msgId, maxAttempts: 6); + if (Get.isLogEnable) { + Get.log('⏳ [ChatController] 消息状态仍为PROGRESS,将延迟检查状态'); + } else { + print('⏳ [ChatController] 消息状态仍为PROGRESS,将延迟检查状态'); + } + // 延迟一小段时间后检查状态(给 SDK 时间更新状态) + // 由于 SDK 可能在发送后报错,我们需要更频繁地检查状态 + // 注意:SDK 可能会改变消息ID(onMessageIdChanged),所以需要从最新消息列表中查找 + Future.delayed(Duration(milliseconds: 500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + // 再延迟一段时间后再次检查(捕获可能的失败回调) + Future.delayed(Duration(milliseconds: 1500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + // 再延迟一段时间后最后一次检查 + Future.delayed(Duration(milliseconds: 3000), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + } else if (message.status == MessageStatus.FAIL) { + // 如果状态已经是 FAIL,立即更新UI + update(); + if (Get.isLogEnable) { + Get.log('❌ [ChatController] 消息发送失败,状态: ${message.status}'); + } else { + print('❌ [ChatController] 消息发送失败,状态: ${message.status}'); + } + } else { + // 状态不是 PROGRESS,说明已经更新完成 + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 消息发送成功,状态: ${message.status}'); + } else { + print('✅ [ChatController] 消息发送成功,状态: ${message.status}'); + } } return true; @@ -200,9 +383,16 @@ class ChatController extends GetxController { /// 发送图片消息 Future sendImageMessage(String imagePath) async { + EMMessage? tempMessage; try { + if (Get.isLogEnable) { + Get.log('📤 [ChatController] 准备发送图片消息: path=$imagePath, targetUserId=$userId'); + } else { + print('📤 [ChatController] 准备发送图片消息: path=$imagePath, targetUserId=$userId'); + } + // 先创建消息对象(即使发送失败也要显示在列表中) - final tempMessage = EMMessage.createImageSendMessage( + tempMessage = EMMessage.createImageSendMessage( targetId: userId, filePath: imagePath, sendOriginalImage: false, @@ -218,18 +408,52 @@ class ChatController extends GetxController { userId, ); if (message != null) { + if (Get.isLogEnable) { + Get.log('✅ [ChatController] SDK返回图片消息: msgId=${message.msgId}, status=${message.status}'); + } else { + print('✅ [ChatController] SDK返回图片消息: msgId=${message.msgId}, status=${message.status}'); + } + // 发送成功,替换临时消息 - final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId); + final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); if (index != -1) { messages[index] = message; + } else { + // 如果找不到原始消息(可能ID改变了),尝试根据内容匹配 + final contentIndex = messages.indexWhere((msg) => + msg.body.type == MessageType.IMAGE && + msg.direction == MessageDirection.SEND + ); + if (contentIndex != -1) { + messages[contentIndex] = message; + } else { + messages.add(message); + } } update(); // 更新会话列表 _refreshConversationList(); + + // 如果消息状态仍然是 PROGRESS,延迟检查状态 + if (message.status == MessageStatus.PROGRESS) { + Future.delayed(Duration(milliseconds: 500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + Future.delayed(Duration(milliseconds: 1500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + Future.delayed(Duration(milliseconds: 3000), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + } + return true; } else { - // 发送失败,消息状态会自动变为FAIL - update(); + // 发送失败,更新消息状态 + final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); + if (index != -1) { + update(); + } SmartDialog.showToast('图片发送失败,请点击重发'); return false; } @@ -244,9 +468,16 @@ class ChatController extends GetxController { /// 发送语音消息 Future sendVoiceMessage(String filePath, int seconds) async { + EMMessage? tempMessage; try { + if (Get.isLogEnable) { + Get.log('📤 [ChatController] 准备发送语音消息: path=$filePath, duration=$seconds, targetUserId=$userId'); + } else { + print('📤 [ChatController] 准备发送语音消息: path=$filePath, duration=$seconds, targetUserId=$userId'); + } + // 先创建消息对象(即使发送失败也要显示在列表中) - final tempMessage = EMMessage.createVoiceSendMessage( + tempMessage = EMMessage.createVoiceSendMessage( targetId: userId, filePath: filePath, duration: seconds, @@ -263,18 +494,52 @@ class ChatController extends GetxController { seconds, ); if (message != null) { + if (Get.isLogEnable) { + Get.log('✅ [ChatController] SDK返回语音消息: msgId=${message.msgId}, status=${message.status}'); + } else { + print('✅ [ChatController] SDK返回语音消息: msgId=${message.msgId}, status=${message.status}'); + } + // 发送成功,替换临时消息 - final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId); + final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); if (index != -1) { messages[index] = message; + } else { + // 如果找不到原始消息(可能ID改变了),尝试根据内容匹配 + final contentIndex = messages.indexWhere((msg) => + msg.body.type == MessageType.VOICE && + msg.direction == MessageDirection.SEND + ); + if (contentIndex != -1) { + messages[contentIndex] = message; + } else { + messages.add(message); + } } update(); // 更新会话列表 _refreshConversationList(); + + // 如果消息状态仍然是 PROGRESS,延迟检查状态 + if (message.status == MessageStatus.PROGRESS) { + Future.delayed(Duration(milliseconds: 500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + Future.delayed(Duration(milliseconds: 1500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + Future.delayed(Duration(milliseconds: 3000), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + } + return true; } else { - // 发送失败,消息状态会自动变为FAIL - update(); + // 发送失败,更新消息状态 + final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); + if (index != -1) { + update(); + } SmartDialog.showToast('语音发送失败,请点击重发'); return false; } @@ -289,13 +554,16 @@ class ChatController extends GetxController { /// 发送视频消息 Future sendVideoMessage(String filePath, int duration) async { + EMMessage? tempMessage; try { - print('🎬 [ChatController] 准备发送视频消息'); - print('视频路径: $filePath'); - print('视频时长: $duration 秒'); + if (Get.isLogEnable) { + Get.log('🎬 [ChatController] 准备发送视频消息: path=$filePath, duration=$duration, targetUserId=$userId'); + } else { + print('🎬 [ChatController] 准备发送视频消息: path=$filePath, duration=$duration, targetUserId=$userId'); + } // 先创建消息对象(即使发送失败也要显示在列表中) - final tempMessage = EMMessage.createVideoSendMessage( + tempMessage = EMMessage.createVideoSendMessage( targetId: userId, filePath: filePath, duration: duration, @@ -313,19 +581,52 @@ class ChatController extends GetxController { ); if (message != null) { - print('✅ [ChatController] 视频消息发送成功'); + if (Get.isLogEnable) { + Get.log('✅ [ChatController] SDK返回视频消息: msgId=${message.msgId}, status=${message.status}'); + } else { + print('✅ [ChatController] SDK返回视频消息: msgId=${message.msgId}, status=${message.status}'); + } + // 发送成功,替换临时消息 - final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId); + final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); if (index != -1) { messages[index] = message; + } else { + // 如果找不到原始消息(可能ID改变了),尝试根据内容匹配 + final contentIndex = messages.indexWhere((msg) => + msg.body.type == MessageType.VIDEO && + msg.direction == MessageDirection.SEND + ); + if (contentIndex != -1) { + messages[contentIndex] = message; + } else { + messages.add(message); + } } update(); // 更新会话列表 _refreshConversationList(); + + // 如果消息状态仍然是 PROGRESS,延迟检查状态 + if (message.status == MessageStatus.PROGRESS) { + Future.delayed(Duration(milliseconds: 500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + Future.delayed(Duration(milliseconds: 1500), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + Future.delayed(Duration(milliseconds: 3000), () { + _checkMessageStatusAndUpdate(message.msgId); + }); + } + return true; } else { - // 发送失败,消息状态会自动变为FAIL - update(); + // 发送失败,更新消息状态 + final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId); + if (index != -1) { + update(); + } SmartDialog.showToast('视频发送失败,请点击重发'); return false; } @@ -707,26 +1008,581 @@ class ChatController extends GetxController { } } - /// 轮询检查消息状态,直到状态不再是 PROGRESS - void _checkMessageStatusUntilComplete(String messageId, {int maxAttempts = 6, int attempt = 0}) { - if (attempt >= maxAttempts) { - // 超过最大尝试次数,停止检查 - return; + /// 检查消息状态并更新(用于处理发送后状态可能变化的情况) + void _checkMessageStatusAndUpdate(String messageId) async { + final currentIndex = messages.indexWhere((msg) => msg.msgId == messageId); + if (currentIndex != -1) { + final currentMessage = messages[currentIndex]; + // 如果状态仍然是 PROGRESS,尝试从 SDK 重新获取消息状态 + if (currentMessage.status == MessageStatus.PROGRESS) { + // 尝试从 SDK 重新获取消息的最新状态 + try { + // 先尝试通过原始ID获取 + var latestMessage = await IMManager.instance.getMessageById(userId, messageId); + + // 如果获取失败或找不到,可能是消息ID改变了,尝试刷新消息列表 + if (latestMessage == null) { + await _refreshMessageFromSDK(messageId); + // 刷新后再次尝试获取 + latestMessage = await IMManager.instance.getMessageById(userId, messageId); + } + + if (latestMessage != null) { + // 如果从 SDK 获取到的消息状态已更新,替换列表中的消息 + if (latestMessage.status != MessageStatus.PROGRESS) { + messages[currentIndex] = latestMessage; + // 强制刷新UI + update(); + if (Get.isLogEnable) { + if (latestMessage.status == MessageStatus.FAIL) { + Get.log('❌ [ChatController] 从SDK获取到消息失败状态,已更新UI'); + } else { + Get.log('✅ [ChatController] 从SDK获取到消息最新状态: ${latestMessage.status},已更新UI'); + } + } + return; + } else { + // 状态仍然是 PROGRESS,但消息对象可能已更新(SDK内部状态可能已变化) + // 更新消息对象以确保使用最新的消息数据 + messages[currentIndex] = latestMessage; + update(); + } + } else { + // 如果通过ID找不到,尝试通过刷新消息列表来匹配 + await _refreshMessageFromSDK(messageId); + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 从SDK获取消息状态失败: $e'); + } + } + + // 如果从 SDK 获取失败或状态仍然是 PROGRESS,继续轮询检查 + _checkMessageStatusUntilComplete(messageId, maxAttempts: 6); + } else { + // 状态已更新,刷新UI + update(); + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 消息状态已自动更新: ${currentMessage.status}'); + } + // 如果状态是 FAIL,显示提示 + if (currentMessage.status == MessageStatus.FAIL) { + if (Get.isLogEnable) { + Get.log('❌ [ChatController] 消息发送失败,请点击重发'); + } + } + } + } else { + // 如果找不到消息(可能ID改变了),尝试从 SDK 获取最新消息列表 + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 找不到消息ID: $messageId,尝试从SDK获取最新消息'); + } + // 刷新消息列表以获取最新状态(处理消息ID可能改变的情况) + await _refreshMessageFromSDK(messageId); } + } - Future.delayed(Duration(milliseconds: 500), () { + /// 检查消息是否在会话中(用于验证消息是否真的发送成功) + Future _checkMessageInConversation(String messageId) async { + try { + // 先找到当前消息,用于匹配内容和时间戳 + final currentIndex = messages.indexWhere((msg) => msg.msgId == messageId); + if (currentIndex == -1) { + return; + } + final currentMessage = messages[currentIndex]; + + // 从会话中获取最新消息(如果会话不存在,尝试创建) + final conversationMessages = await IMManager.instance.getMessages( + userId, + pageSize: 20, // 检查最近20条消息 + createIfNeed: true, // 如果会话不存在,尝试创建 + ); + + // 过滤掉null消息 + final validMessages = conversationMessages.whereType().toList(); + + if (Get.isLogEnable) { + Get.log('🔍 [ChatController] 检查会话消息,共 ${validMessages.length} 条,查找消息ID: $messageId'); + // 打印会话中的消息详情,用于调试 + for (var msg in validMessages) { + Get.log(' - 消息ID: ${msg.msgId}, 方向: ${msg.direction}, 状态: ${msg.status}, 时间: ${msg.serverTime}'); + if (msg.body.type == MessageType.TXT) { + final body = msg.body as EMTextMessageBody; + Get.log(' 内容: ${body.content}'); + } + } + } + + // 先尝试通过消息ID查找 + EMMessage? foundMessage; + try { + foundMessage = validMessages.firstWhere( + (msg) => msg.msgId == messageId, + ); + } catch (e) { + // 未找到,继续尝试通过内容和时间戳匹配 + } + + // 如果通过ID找不到,尝试通过内容和时间戳匹配 + if (foundMessage == null) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 在会话中未找到消息ID: $messageId,尝试通过内容和时间戳匹配'); + } + + // 通过内容和时间戳匹配 + for (var msg in validMessages) { + // 只匹配发送方向的消息 + if (msg.direction != MessageDirection.SEND) { + continue; + } + + // 匹配条件:相同的内容、相同的时间戳(允许一定误差) + // 放宽时间戳匹配条件到30秒,因为消息可能延迟保存 + final msgTime = msg.serverTime ?? 0; + final currentTime = currentMessage.serverTime ?? 0; + final timeDiff = (msgTime - currentTime).abs(); + + if (timeDiff < 30000) { // 30秒内的误差 + // 进一步检查消息类型和内容是否相同 + if (msg.body.type == currentMessage.body.type) { + bool isMatch = false; + + if (msg.body.type == MessageType.TXT) { + // 文本消息:比较内容 + final msgBody = msg.body as EMTextMessageBody; + final currentBody = currentMessage.body as EMTextMessageBody; + isMatch = msgBody.content == currentBody.content; + if (!isMatch && Get.isLogEnable) { + Get.log(' - 文本内容不匹配: 会话消息="${msgBody.content}", 原始消息="${currentBody.content}"'); + } + } else if (msg.body.type == MessageType.IMAGE) { + // 图片消息:比较本地路径或远程URL + final msgBody = msg.body as EMImageMessageBody; + final currentBody = currentMessage.body as EMImageMessageBody; + // 如果都有本地路径,比较路径;否则比较远程URL + if (msgBody.localPath != null && currentBody.localPath != null) { + isMatch = msgBody.localPath == currentBody.localPath; + } else if (msgBody.remotePath != null && currentBody.remotePath != null) { + isMatch = msgBody.remotePath == currentBody.remotePath; + } else { + // 如果路径都为空,通过时间戳匹配(同一时间发送的图片) + isMatch = true; + } + if (!isMatch && Get.isLogEnable) { + Get.log(' - 图片路径不匹配: 会话消息路径="${msgBody.localPath ?? msgBody.remotePath}", 原始消息路径="${currentBody.localPath ?? currentBody.remotePath}"'); + } + } else if (msg.body.type == MessageType.VOICE) { + // 语音消息:比较时长和本地路径 + final msgBody = msg.body as EMVoiceMessageBody; + final currentBody = currentMessage.body as EMVoiceMessageBody; + if (msgBody.localPath != null && currentBody.localPath != null) { + isMatch = msgBody.localPath == currentBody.localPath; + } else { + // 如果路径都为空,比较时长(同一时间发送的语音,时长应该相同) + isMatch = msgBody.duration == currentBody.duration; + } + if (!isMatch && Get.isLogEnable) { + Get.log(' - 语音不匹配: 会话消息时长=${msgBody.duration}, 原始消息时长=${currentBody.duration}'); + } + } else if (msg.body.type == MessageType.VIDEO) { + // 视频消息:比较时长和本地路径 + final msgBody = msg.body as EMVideoMessageBody; + final currentBody = currentMessage.body as EMVideoMessageBody; + if (msgBody.localPath != null && currentBody.localPath != null) { + isMatch = msgBody.localPath == currentBody.localPath; + } else { + // 如果路径都为空,比较时长(同一时间发送的视频,时长应该相同) + isMatch = msgBody.duration == currentBody.duration; + } + if (!isMatch && Get.isLogEnable) { + Get.log(' - 视频不匹配: 会话消息时长=${msgBody.duration}, 原始消息时长=${currentBody.duration}'); + } + } else { + // 其他类型消息:仅通过时间戳匹配 + isMatch = true; + } + + if (isMatch) { + foundMessage = msg; + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 通过内容和时间戳匹配到消息: 原ID=$messageId, 新ID=${msg.msgId}, 类型=${msg.body.type}, 时间差: ${timeDiff}ms'); + } + break; + } + } else { + if (Get.isLogEnable) { + Get.log('🔍 [ChatController] 消息类型不匹配: 会话消息类型=${msg.body.type}, 原始消息类型=${currentMessage.body.type}'); + } + } + } else { + if (Get.isLogEnable) { + Get.log(' - 时间戳不匹配: 会话消息时间=${msg.serverTime}, 原始消息时间=${currentMessage.serverTime}, 时间差=${timeDiff}ms'); + } + } + } + } + + if (foundMessage != null) { + // 如果找到了消息,更新消息状态 + messages[currentIndex] = foundMessage; + update(); + if (Get.isLogEnable) { + if (foundMessage.status == MessageStatus.FAIL) { + Get.log('❌ [ChatController] 从会话中找到消息,状态为失败'); + } else if (foundMessage.status == MessageStatus.SUCCESS) { + Get.log('✅ [ChatController] 从会话中找到消息,状态为成功,消息ID已更新: ${foundMessage.msgId}'); + } else { + Get.log('⏳ [ChatController] 从会话中找到消息,状态仍为: ${foundMessage.status}'); + } + } + // 找到消息后,返回true表示已找到,不需要继续查找 + return; + } else { + // 如果找不到消息,说明消息可能没有真正发送成功 + if (Get.isLogEnable) { + Get.log('❌ [ChatController] 在会话中未找到消息,可能发送失败。会话消息数量: ${validMessages.length}'); + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 检查会话消息失败: $e'); + } + } + } + + /// 从 SDK 刷新消息列表,查找指定消息ID的消息(处理消息ID可能改变的情况) + Future _refreshMessageFromSDK(String originalMessageId) async { + try { + // 从 SDK 获取最新消息列表 + final List fetchedMessages = await IMManager.instance.getMessages( + userId, + pageSize: 50, // 获取最近50条消息 + startMsgId: null, + ); + + // 过滤掉null消息 + final List validMessages = fetchedMessages.whereType().toList(); + + // 查找对应的消息(可能是原始ID,也可能是新的ID) + // 由于消息ID可能改变,我们通过消息内容和时间戳来匹配 + final currentIndex = messages.indexWhere((msg) => msg.msgId == originalMessageId); + if (currentIndex != -1) { + final originalMessage = messages[currentIndex]; + + // 尝试通过时间戳和内容匹配找到对应的消息 + EMMessage? matchedMessage; + for (var msg in validMessages) { + // 匹配条件:相同的内容、相同的时间戳(允许一定误差)、发送方向相同 + if (msg.direction == originalMessage.direction && + msg.serverTime == originalMessage.serverTime) { + // 进一步检查内容是否相同 + if (msg.body.type == originalMessage.body.type) { + if (msg.body.type == MessageType.TXT) { + final msgBody = msg.body as EMTextMessageBody; + final originalBody = originalMessage.body as EMTextMessageBody; + if (msgBody.content == originalBody.content) { + matchedMessage = msg; + break; + } + } else { + // 对于其他类型的消息,如果时间戳和方向匹配,就认为是同一条消息 + matchedMessage = msg; + break; + } + } + } + } + + if (matchedMessage != null) { + // 找到匹配的消息,更新列表 + messages[currentIndex] = matchedMessage; + update(); + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 从SDK刷新消息成功,新消息ID: ${matchedMessage.msgId},状态: ${matchedMessage.status}'); + } + } else { + // 如果找不到匹配的消息,尝试直接通过消息ID查找(可能ID没有改变) + final directMatch = validMessages.firstWhere( + (msg) => msg.msgId == originalMessageId, + orElse: () => originalMessage, + ); + if (directMatch.msgId == originalMessageId) { + messages[currentIndex] = directMatch; + update(); + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 通过消息ID找到消息,状态: ${directMatch.status}'); + } + } + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 从SDK刷新消息失败: $e'); + } + // 刷新消息列表以获取最新状态 + try { + await fetchMessages(loadMore: false); + update(); + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 刷新消息列表失败: $e'); + } + } + } + } + + /// 轮询检查消息状态,直到状态不再是 PROGRESS + void _checkMessageStatusUntilComplete(String messageId, {int maxAttempts = 6, int attempt = 0}) async { + if (attempt >= maxAttempts) { + // 超过最大尝试次数,尝试从 SDK 最后一次获取消息状态 + // 由于消息ID可能改变,使用刷新消息列表的方式 + try { + await _refreshMessageFromSDK(messageId); + // 如果刷新后仍然找不到,尝试直接通过ID获取 + final index = messages.indexWhere((msg) => msg.msgId == messageId); + if (index != -1) { + final latestMessage = await IMManager.instance.getMessageById(userId, messageId); + if (latestMessage != null) { + messages[index] = latestMessage; + update(); + if (Get.isLogEnable) { + if (latestMessage.status == MessageStatus.FAIL) { + Get.log('❌ [ChatController] 从SDK获取到消息失败状态(最终检查)'); + } else if (latestMessage.status == MessageStatus.PROGRESS) { + Get.log('⚠️ [ChatController] 消息状态检查超时,状态仍为 PROGRESS,可能发送失败'); + } else { + Get.log('✅ [ChatController] 从SDK获取到消息最新状态: ${latestMessage.status}'); + } + } + return; + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 最终检查时从SDK获取消息状态失败: $e'); + } + } + + // 如果从 SDK 获取失败,检查内存中的消息状态 final index = messages.indexWhere((msg) => msg.msgId == messageId); if (index != -1) { final message = messages[index]; - if (message.status != MessageStatus.PROGRESS) { - // 状态已更新,刷新UI - update(); - return; + // 刷新UI,显示当前状态(可能是 PROGRESS、SUCCESS 或 FAIL) + update(); + if (Get.isLogEnable) { + if (message.status == MessageStatus.PROGRESS) { + Get.log('⚠️ [ChatController] 消息状态检查超时,状态仍为 PROGRESS,可能发送失败'); + } else { + Get.log('✅ [ChatController] 消息状态检查完成,最终状态: ${message.status}'); + } + } + // 如果状态仍然是 PROGRESS,可能是发送失败但没有更新状态 + // 这种情况下,用户可以通过重发按钮重新发送 + } + return; + } + + Future.delayed(Duration(milliseconds: 500), () async { + // 每次检查时都尝试从 SDK 获取最新状态 + // 注意:SDK 可能会改变消息ID(onMessageIdChanged),所以需要处理这种情况 + try { + // 先尝试通过原始ID获取 + var latestMessage = await IMManager.instance.getMessageById(userId, messageId); + + // 如果获取失败或找不到,可能是消息ID改变了,尝试刷新消息列表 + if (latestMessage == null) { + await _refreshMessageFromSDK(messageId); + // 刷新后再次尝试获取 + latestMessage = await IMManager.instance.getMessageById(userId, messageId); + } + + // 如果仍然找不到,尝试从会话中检查 + if (latestMessage == null) { + await _checkMessageInConversation(messageId); + } + + if (latestMessage != null) { + final index = messages.indexWhere((msg) => msg.msgId == messageId); + if (index != -1) { + // 更新消息对象(无论状态如何,都更新以确保使用最新的消息数据) + messages[index] = latestMessage; + update(); + + // 如果从 SDK 获取到的消息状态已更新,停止检查 + if (latestMessage.status != MessageStatus.PROGRESS) { + if (Get.isLogEnable) { + if (latestMessage.status == MessageStatus.FAIL) { + Get.log('❌ [ChatController] 从SDK获取到消息失败状态'); + } else { + Get.log('✅ [ChatController] 从SDK获取到消息最新状态: ${latestMessage.status}'); + } + } + return; + } else { + // 状态仍然是 PROGRESS,先检查会话中的消息 + // 如果从会话中找到消息并更新了状态,_checkMessageInConversation 会返回,不会继续执行 + await _checkMessageInConversation(messageId); + // 检查消息状态是否已更新(可能通过 _checkMessageInConversation 更新了) + final updatedIndex = messages.indexWhere((msg) => msg.msgId == messageId); + if (updatedIndex != -1) { + final updatedMessage = messages[updatedIndex]; + // 如果状态已更新为成功或失败,停止检查 + if (updatedMessage.status != MessageStatus.PROGRESS) { + if (Get.isLogEnable) { + Get.log('✅ [ChatController] 消息状态已通过会话检查更新: ${updatedMessage.status}'); + } + return; + } + } + // 继续检查 + _checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1); + } + } else { + // 如果本地找不到消息(可能ID改变了),尝试刷新消息列表 + await _refreshMessageFromSDK(messageId); + // 继续检查 + _checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1); + } } else { - // 状态仍然是 PROGRESS,继续检查 - _checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1); + // 如果从 SDK 获取失败,使用本地消息状态继续检查 + final index = messages.indexWhere((msg) => msg.msgId == messageId); + if (index != -1) { + final message = messages[index]; + if (message.status != MessageStatus.PROGRESS) { + // 状态已更新,刷新UI + update(); + if (Get.isLogEnable) { + if (message.status == MessageStatus.FAIL) { + Get.log('❌ [ChatController] 消息发送失败,状态: ${message.status}'); + } else { + Get.log('✅ [ChatController] 消息状态已更新: ${message.status}'); + } + } + return; + } else { + // 状态仍然是 PROGRESS,继续检查 + _checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1); + } + } else { + // 如果本地找不到消息,尝试刷新消息列表 + await _refreshMessageFromSDK(messageId); + // 继续检查 + _checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1); + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ChatController] 从SDK获取消息状态失败: $e'); } + // 继续检查 + _checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1); } }); } + + /// 加载礼物产品列表 + Future loadGiftProducts() async { + try { + final response = await _networkService.rtcApi.listGiftProduct(); + final base = response.data; + if (base.isSuccess && base.data != null) { + giftProducts.assignAll(base.data!); + if (Get.isLogEnable) { + Get.log('✅ 礼物产品列表加载成功,共 ${giftProducts.length} 个'); + } + } else { + if (Get.isLogEnable) { + Get.log('❌ 加载礼物产品列表失败: ${base.message}'); + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('❌ 加载礼物产品列表异常: $e'); + } + } + } + + /// 发送礼物(聊天界面) + Future sendGift({ + required GiftProductModel gift, + int quantity = 1, + }) async { + try { + // 准备请求参数(聊天界面不需要channelId,type为2表示添加好友/聊天送礼) + final requestData = { + 'type': 2, // 1.送礼(频道) 2.添加好友/聊天送礼 + 'toUId': int.tryParse(userId) ?? 0, + 'productSpecId': int.tryParse(gift.productSpecId) ?? 0, + 'quantity': quantity, + }; + + // 调用消费接口 + final response = await _networkService.rtcApi.costChannelGift( + requestData, + ); + + if (!response.data.isSuccess) { + SmartDialog.showToast( + response.data.message.isNotEmpty ? response.data.message : '发送礼物失败', + ); + return false; + } + + // 发送成功,创建礼物消息 + final giftMessageContent = jsonEncode({ + 'type': 'gift', + 'giftProductId': gift.productId, + 'giftProductTitle': gift.productTitle, + 'giftMainPic': gift.mainPic, + 'giftPrice': gift.unitSellingPrice, + 'quantity': quantity, + }); + + // 创建文本消息,内容为礼物信息JSON + final content = '[GIFT:]$giftMessageContent'; + + // 先创建消息对象(即使发送失败也要显示在列表中) + final tempMessage = EMMessage.createTxtSendMessage( + targetId: userId, + content: content, + ); + + // 将消息添加到列表末尾(显示发送中状态) + messages.add(tempMessage); + update(); + + // 发送消息 + final message = await IMManager.instance.sendTextMessage(content, userId); + if (message != null) { + // 发送成功,替换临时消息 + final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId); + if (index != -1) { + messages[index] = message; + } + update(); + // 更新会话列表 + _refreshConversationList(); + + if (Get.isLogEnable) { + Get.log('✅ 礼物消息发送成功: ${gift.productTitle}'); + } + return true; + } else { + // 发送失败,更新消息状态 + final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId); + if (index != -1) { + update(); + } + SmartDialog.showToast('礼物消息发送失败,请点击重发'); + return false; + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('❌ 发送礼物失败: $e'); + } + SmartDialog.showToast('发送礼物失败: $e'); + return false; + } + } } diff --git a/lib/controller/message/conversation_controller.dart b/lib/controller/message/conversation_controller.dart index 3abc6c6..bd54bbc 100644 --- a/lib/controller/message/conversation_controller.dart +++ b/lib/controller/message/conversation_controller.dart @@ -1,6 +1,38 @@ import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import '../../im/im_manager.dart'; +import '../../model/mine/user_base_data.dart'; + +// 扩展类用于存储用户信息(包括业务系统的信息) +class ExtendedUserInfo { + final String userId; + final String? nickName; + final String? avatarUrl; + + ExtendedUserInfo({ + required this.userId, + this.nickName, + this.avatarUrl, + }); + + // 从 EMUserInfo 创建 + factory ExtendedUserInfo.fromEMUserInfo(EMUserInfo emUserInfo) { + return ExtendedUserInfo( + userId: emUserInfo.userId ?? '', + nickName: emUserInfo.nickName, + avatarUrl: emUserInfo.avatarUrl, + ); + } + + // 从 UserBaseData 创建 + factory ExtendedUserInfo.fromUserBaseData(UserBaseData userBaseData, {String? avatarUrl}) { + return ExtendedUserInfo( + userId: userBaseData.userId, + nickName: userBaseData.nickName, + avatarUrl: avatarUrl, + ); + } +} class ConversationController extends GetxController { // 会话列表数据 @@ -9,18 +41,74 @@ class ConversationController extends GetxController { final isLoading = false.obs; // 错误消息 final errorMessage = ''.obs; + + // 用户信息缓存(userId -> ExtendedUserInfo) + final Map _userInfoCache = {}; + + /// 缓存用户信息(公开方法,供 ChatController 调用) + void cacheUserInfo(String userId, ExtendedUserInfo userInfo) { + _userInfoCache[userId] = userInfo; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 已缓存用户信息: userId=$userId, nickName=${userInfo.nickName}'); + } + } @override void onInit() { super.onInit(); - // 初始化时加载会话列表 - loadConversations(); + // 初始化时检查 IM 登录状态,如果已登录则加载会话列表 + _checkAndLoadConversations(); + } + + /// 检查 IM 登录状态并加载会话列表 + Future _checkAndLoadConversations() async { + // 如果已登录,直接加载 + if (IMManager.instance.isLoggedIn) { + await loadConversations(); + return; + } + + // 如果未登录,等待登录完成(最多等待 10 秒) + if (Get.isLogEnable) { + Get.log('⏳ [ConversationController] IM 未登录,等待登录完成...'); + } + + int retryCount = 0; + const maxRetries = 20; // 最多重试 20 次(每次 500ms,总共 10 秒) + + while (retryCount < maxRetries && !IMManager.instance.isLoggedIn) { + await Future.delayed(Duration(milliseconds: 500)); + retryCount++; + } + + if (IMManager.instance.isLoggedIn) { + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] IM 已登录,开始加载会话列表'); + } + await loadConversations(); + } else { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] IM 登录超时,显示错误提示'); + } + errorMessage.value = 'IM 未登录,请稍后重试'; + isLoading.value = false; + } } /// 加载会话列表 Future loadConversations() async { if (isLoading.value) return; + // 检查 IM 登录状态 + if (!IMManager.instance.isLoggedIn) { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] IM 未登录,无法加载会话列表'); + } + errorMessage.value = 'IM 未登录,无法加载会话列表'; + isLoading.value = false; + return; + } + try { isLoading.value = true; errorMessage.value = ''; @@ -31,6 +119,9 @@ class ConversationController extends GetxController { // 更新会话列表 conversations.value = convList; + // 从所有会话的历史消息中提取用户信息并缓存(应用重启后恢复用户信息) + _extractUserInfoFromConversations(convList); + // 使用GetX日志系统 if (Get.isLogEnable) { Get.log('Loaded ${convList.length} conversations'); @@ -46,11 +137,116 @@ class ConversationController extends GetxController { } } + /// 从所有会话的历史消息中提取用户信息并缓存 + /// 这样应用重启后也能恢复用户信息 + /// 注意:只提取对方(接收到的消息)的用户信息,不提取自己的信息 + Future _extractUserInfoFromConversations(List convList) async { + try { + for (var conversation in convList) { + try { + // conversation.id 是对方用户ID + final targetUserId = conversation.id; + + // 如果缓存中已有该用户信息,跳过 + if (_userInfoCache.containsKey(targetUserId)) continue; + + // 获取会话的最新消息(最多获取最近20条) + final messages = await conversation.loadMessages( + loadCount: 20, + ); + + // 从消息中提取用户信息 + // 接收消息:提取发送者信息(sender_ 前缀) + // 发送消息:提取接收者信息(receiver_ 前缀) + for (var message in messages) { + Map? attributes; + try { + attributes = message.attributes; + } catch (e) { + continue; + } + + if (attributes == null || attributes.isEmpty) { + continue; + } + + if (message.direction == MessageDirection.RECEIVE) { + // 接收到的消息:从扩展字段中提取发送者信息(sender_ 前缀) + final fromUserId = message.from; + if (fromUserId != null && fromUserId == targetUserId) { + // 优先使用 sender_ 前缀的字段(发送者信息) + final nickName = attributes['sender_nickName'] as String? ?? attributes['nickName'] as String?; + final avatarUrl = attributes['sender_avatarUrl'] as String? ?? attributes['avatarUrl'] as String?; + + if (nickName != null || avatarUrl != null) { + final extendedInfo = ExtendedUserInfo( + userId: targetUserId, + nickName: nickName, + avatarUrl: avatarUrl, + ); + _userInfoCache[targetUserId] = extendedInfo; + + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 从接收消息恢复对方用户信息: userId=$targetUserId, nickName=$nickName'); + } + // 找到一个就足够了,跳出循环 + break; + } + } + } else if (message.direction == MessageDirection.SEND) { + // 发送的消息:从扩展字段中提取接收者信息(receiver_ 前缀) + final toUserId = message.to; + if (toUserId != null && toUserId == targetUserId) { + // 使用 receiver_ 前缀的字段(接收者信息) + final nickName = attributes['receiver_nickName'] as String?; + final avatarUrl = attributes['receiver_avatarUrl'] as String?; + + if (nickName != null || avatarUrl != null) { + final extendedInfo = ExtendedUserInfo( + userId: targetUserId, + nickName: nickName, + avatarUrl: avatarUrl, + ); + _userInfoCache[targetUserId] = extendedInfo; + + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 从发送消息恢复对方用户信息: userId=$targetUserId, nickName=$nickName'); + } + // 找到一个就足够了,跳出循环 + break; + } + } + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 从会话提取用户信息失败: ${conversation.id}, 错误: $e'); + } + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 批量提取用户信息失败: $e'); + } + } + } + /// 刷新会话列表 Future refreshConversations() async { await loadConversations(); } + /// 清除会话列表和用户信息缓存(用于退出登录时) + void clearConversations() { + conversations.clear(); + _userInfoCache.clear(); + errorMessage.value = ''; + isLoading.value = false; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 已清除会话列表和用户信息缓存'); + } + } + /// 获取会话的最新消息 String getLastMessageContent(EMMessage? message) { if(message?.body.type == MessageType.TXT){ @@ -108,24 +304,168 @@ class ConversationController extends GetxController { return '${messageTime.month.toString().padLeft(2, '0')}-${messageTime.day.toString().padLeft(2, '0')}'; } - Future loadContact(String userId) async{ + Future loadContact(String userId) async{ try { - var data = await IMManager.instance.getContacts(userId); - final userInfo = data[userId]; + // 1. 先从缓存中查找(优先级最高) + if (_userInfoCache.containsKey(userId)) { + final cachedUser = _userInfoCache[userId]!; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 从缓存获取到用户信息: $userId, nickName=${cachedUser.nickName}, avatarUrl=${cachedUser.avatarUrl}'); + } + return cachedUser; + } + + // 2. 尝试从会话的历史消息中提取用户信息(从消息扩展字段) + // 这是最重要的数据源,因为用户信息存储在消息扩展字段中 + // 注意:conversation.id 是对方用户ID,应该从接收到的消息中提取对方的信息 + try { + final conversation = await EMClient.getInstance.chatManager.getConversation( + userId, + type: EMConversationType.Chat, + createIfNeed: false, + ); + if (conversation != null) { + // 获取最近的消息(最多20条),查找包含用户信息的消息 + final messages = await conversation.loadMessages( + loadCount: 20, + ); + + // 从消息中查找用户信息(只查找接收到的消息,因为那是对方的用户信息) + // conversation.id 是对方用户ID,所以应该从接收到的消息中提取对方的信息 + if (Get.isLogEnable) { + Get.log('🔍 [ConversationController] 开始从会话历史消息查找用户信息: userId=$userId, 消息数量=${messages.length}'); + } + + // 先打印所有消息的详细信息,用于调试 + if (Get.isLogEnable) { + for (var msg in messages) { + final fromId = msg.from; + final toId = msg.to; + final direction = msg.direction; + Map? attrs; + try { + attrs = msg.attributes; + } catch (e) { + attrs = null; + } + Get.log(' 📨 消息: msgId=${msg.msgId}, 方向=$direction, from=$fromId, to=$toId, attributes=$attrs'); + } + } + + for (var message in messages) { + Map? attributes; + try { + attributes = message.attributes; + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 无法访问 message.attributes: $e'); + } + continue; + } + + if (attributes == null || attributes.isEmpty) { + continue; + } + + if (message.direction == MessageDirection.RECEIVE) { + // 接收到的消息:从扩展字段中提取发送者信息(sender_ 前缀) + // conversation.id 是对方用户ID,应该从接收到的消息中提取发送者(对方)的信息 + final fromUserId = message.from; + if (fromUserId != null && fromUserId == userId) { + // 优先使用 sender_ 前缀的字段(发送者信息) + final nickName = attributes['sender_nickName'] as String? ?? attributes['nickName'] as String?; + final avatarUrl = attributes['sender_avatarUrl'] as String? ?? attributes['avatarUrl'] as String?; + + if (Get.isLogEnable) { + Get.log('🔍 [ConversationController] 从接收消息提取发送者信息: msgId=${message.msgId}, fromUserId=$fromUserId, nickName=$nickName, avatarUrl=$avatarUrl'); + } + + if (nickName != null || avatarUrl != null) { + final extendedInfo = ExtendedUserInfo( + userId: userId, + nickName: nickName, + avatarUrl: avatarUrl, + ); + _userInfoCache[userId] = extendedInfo; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 从接收消息扩展字段获取到对方用户信息: userId=$userId, nickName=$nickName'); + } + return extendedInfo; + } + } + } else if (message.direction == MessageDirection.SEND) { + // 发送的消息:从扩展字段中提取接收者信息(receiver_ 前缀) + // conversation.id 是对方用户ID,应该从发送的消息中提取接收者(对方)的信息 + final toUserId = message.to; + if (toUserId != null && toUserId == userId) { + // 使用 receiver_ 前缀的字段(接收者信息) + final nickName = attributes['receiver_nickName'] as String?; + final avatarUrl = attributes['receiver_avatarUrl'] as String?; + + if (Get.isLogEnable) { + Get.log('🔍 [ConversationController] 从发送消息提取接收者信息: msgId=${message.msgId}, toUserId=$toUserId, nickName=$nickName, avatarUrl=$avatarUrl'); + } + + if (nickName != null || avatarUrl != null) { + final extendedInfo = ExtendedUserInfo( + userId: userId, + nickName: nickName, + avatarUrl: avatarUrl, + ); + _userInfoCache[userId] = extendedInfo; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 从发送消息扩展字段获取到对方用户信息: userId=$userId, nickName=$nickName'); + } + return extendedInfo; + } + } + } + } + + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 在会话历史消息中未找到用户信息: userId=$userId'); + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 从会话消息提取用户信息失败: $e'); + } + } - // 如果获取不到用户信息,返回 null - if (userInfo == null) { + // 3. 如果从消息扩展字段获取不到,尝试从环信获取用户信息(作为备选) + try { + var data = await IMManager.instance.getContacts(userId); + var emUserInfo = data[userId]; + + if (emUserInfo != null && (emUserInfo.nickName?.isNotEmpty ?? false)) { + final extendedInfo = ExtendedUserInfo.fromEMUserInfo(emUserInfo); + _userInfoCache[userId] = extendedInfo; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 从环信获取到用户信息: $userId, nickName=${extendedInfo.nickName}'); + } + return extendedInfo; + } + } catch (e) { if (Get.isLogEnable) { - Get.log('⚠️ [ConversationController] 未获取到用户信息: $userId'); + Get.log('⚠️ [ConversationController] 从环信获取用户信息失败: $e'); } } - return userInfo; + // 4. 如果都获取不到,返回一个基本的 ExtendedUserInfo(至少显示用户ID) + final fallbackInfo = ExtendedUserInfo(userId: userId); + _userInfoCache[userId] = fallbackInfo; + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 未从任何来源获取到用户信息,使用默认值: $userId'); + } + return fallbackInfo; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [ConversationController] 获取用户信息失败: $userId, 错误: $e'); } - return null; + // 即使出错也返回一个基本的 ExtendedUserInfo + final fallbackInfo = ExtendedUserInfo(userId: userId); + _userInfoCache[userId] = fallbackInfo; + return fallbackInfo; } } diff --git a/lib/controller/mine/edit_info_controller.dart b/lib/controller/mine/edit_info_controller.dart index bd6da61..b872dc5 100644 --- a/lib/controller/mine/edit_info_controller.dart +++ b/lib/controller/mine/edit_info_controller.dart @@ -289,7 +289,7 @@ class EditInfoController extends GetxController { } goPreview() { - Get.to(() => UserInformationPage(miId: userData.value?.id ?? "", userId: GlobalData().userId ?? "")); + Get.to(() => UserInformationPage(miId: userData.value?.id ?? "", userId: GlobalData().userId ?? "")); } int calculateAge(String birthdayStr) { diff --git a/lib/controller/setting/setting_controller.dart b/lib/controller/setting/setting_controller.dart index 6e2dd9b..aedd024 100644 --- a/lib/controller/setting/setting_controller.dart +++ b/lib/controller/setting/setting_controller.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'package:dating_touchme_app/controller/global.dart'; +import 'package:dating_touchme_app/controller/message/conversation_controller.dart'; +import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -44,8 +46,17 @@ class SettingController extends GetxController { } } - void logout(){ + void logout() async { + // 先退出 IM 登录 + await IMManager.instance.logout(); + // 清除会话列表和用户信息缓存 + if (Get.isRegistered()) { + final conversationController = Get.find(); + conversationController.clearConversations(); + } + // 清除本地存储 storage.erase(); + // 清除全局数据 GlobalData().logout(); } diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index d465192..5cb0101 100644 --- a/lib/im/im_manager.dart +++ b/lib/im/im_manager.dart @@ -6,6 +6,7 @@ import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:path_provider/path_provider.dart'; import '../controller/message/conversation_controller.dart'; import '../controller/message/chat_controller.dart'; +import '../controller/global.dart'; // 完整的IM管理器实现,使用实际的SDK类型和方法 class IMManager { @@ -18,6 +19,7 @@ class IMManager { bool _isInitialized = false; bool _listenersRegistered = false; + bool _isLoggedIn = false; // 监听器标识符 static const String _connectionHandlerKey = 'im_manager_connection_handler'; @@ -76,6 +78,8 @@ class IMManager { if (Get.isLogEnable) { Get.log('✅ [IMManager] 已连接到IM服务器'); } + // 连接成功后,通知 ConversationController 加载会话列表 + _refreshConversationList(); }, onDisconnected: () { if (Get.isLogEnable) { @@ -106,6 +110,12 @@ class IMManager { if (Get.isLogEnable) { Get.log('📨 [IMManager] 收到 ${messages.length} 条新消息'); } + // 从消息扩展字段中解析用户信息并缓存 + for (var message in messages) { + if (message.direction == MessageDirection.RECEIVE) { + _parseUserInfoFromMessageExt(message); + } + } // 收到新消息时,更新会话列表 _refreshConversationList(); // 通知对应的 ChatController 更新消息列表 @@ -142,14 +152,31 @@ class IMManager { return false; } var userId = storage.read('userId'); + if (Get.isLogEnable) { + Get.log('🔐 [IMManager] 准备登录IM,userId: $userId'); + } else { + print('🔐 [IMManager] 准备登录IM,userId: $userId'); + } await EMClient.getInstance.logout(); await EMClient.getInstance.loginWithToken(userId, token); // 注册监听器 _registerListeners(); - print('IM login successful'); + // 设置登录状态 + _isLoggedIn = true; + // 登录成功后,通知 ConversationController 刷新会话列表 + _refreshConversationList(); + if (Get.isLogEnable) { + Get.log('✅ [IMManager] IM登录成功,userId: $userId'); + } else { + print('✅ [IMManager] IM登录成功,userId: $userId'); + } return true; } catch (e) { - print('IM login failed: $e'); + if (Get.isLogEnable) { + Get.log('❌ [IMManager] IM登录失败: $e'); + } else { + print('❌ [IMManager] IM登录失败: $e'); + } return false; } } @@ -158,6 +185,8 @@ class IMManager { Future logout() async { try { await EMClient.getInstance.logout(); + // 清除登录状态 + _isLoggedIn = false; print('IM logout successful'); return true; } catch (e) { @@ -166,18 +195,81 @@ class IMManager { } } + /// 检查是否已登录 + bool get isLoggedIn => _isLoggedIn; + + /// 检查用户是否存在于IM系统中 + Future checkUserExists(String userId) async { + try { + final userInfo = await getUserInfo(userId); + if (userInfo != null) { + if (Get.isLogEnable) { + Get.log('✅ [IMManager] 用户存在: $userId'); + } else { + print('✅ [IMManager] 用户存在: $userId'); + } + return true; + } else { + if (Get.isLogEnable) { + Get.log('❌ [IMManager] 用户不存在: $userId'); + } else { + print('❌ [IMManager] 用户不存在: $userId'); + } + return false; + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 检查用户是否存在失败: $e'); + } else { + print('⚠️ [IMManager] 检查用户是否存在失败: $e'); + } + return false; + } + } + /// 发送文本消息 Future sendTextMessage( String content, String toChatUsername, ) async { try { + // 检查是否已登录 + // 注意:如果发送失败,可能是未登录或目标用户ID不正确 + if (Get.isLogEnable) { + Get.log('📤 [IMManager] 准备发送消息: to=$toChatUsername, content=$content'); + } else { + print('📤 [IMManager] 准备发送消息: to=$toChatUsername, content=$content'); + } + + // 检查当前登录的用户ID + final currentUserId = storage.read('userId'); + if (Get.isLogEnable) { + Get.log('📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername'); + } else { + print('📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername'); + } + + // 检查目标用户是否存在于IM系统中 + final userExists = await checkUserExists(toChatUsername); + if (!userExists) { + if (Get.isLogEnable) { + Get.log('❌ [IMManager] 目标用户不存在于IM系统中: $toChatUsername'); + } else { + print('❌ [IMManager] 目标用户不存在于IM系统中: $toChatUsername'); + } + // 即使用户不存在,也尝试发送,因为某些IM系统可能允许发送给未注册用户 + // 但会记录警告信息 + } + // 创建文本消息 final message = EMMessage.createTxtSendMessage( targetId: toChatUsername, content: content, ); + // 在消息扩展字段中添加当前用户信息(用于接收方显示头像和昵称) + _addUserInfoToMessageExt(message); + // 发送消息(如果未登录会抛出异常) final sentMessage = await EMClient.getInstance.chatManager.sendMessage(message); @@ -213,6 +305,9 @@ class IMManager { duration: duration, ); + // 在消息扩展字段中添加当前用户信息 + _addUserInfoToMessageExt(message); + // 发送消息 await EMClient.getInstance.chatManager.sendMessage(message); if (Get.isLogEnable) { @@ -240,6 +335,9 @@ class IMManager { sendOriginalImage: false, ); + // 在消息扩展字段中添加当前用户信息 + _addUserInfoToMessageExt(message); + // 发送消息 await EMClient.getInstance.chatManager.sendMessage(message); print('Image message sent successfully'); @@ -302,6 +400,9 @@ class IMManager { thumbnailLocalPath: thumbnailPath, // 🎯 指定缩略图路径 ); + // 在消息扩展字段中添加当前用户信息 + _addUserInfoToMessageExt(message); + print('消息创建成功,消息类型: ${message.body.type}'); print('消息体是否为视频: ${message.body is EMVideoMessageBody}'); @@ -367,6 +468,7 @@ class IMManager { String conversationId, { int pageSize = 20, String? startMsgId, + bool createIfNeed = false, }) async { try { EMConversationType convType = EMConversationType.Chat; @@ -375,7 +477,7 @@ class IMManager { final conversation = await EMClient.getInstance.chatManager.getConversation( conversationId, type: convType, - createIfNeed: false, + createIfNeed: createIfNeed, ); if (conversation == null) { @@ -408,6 +510,44 @@ class IMManager { } } + /// 从 SDK 重新获取消息的最新状态(用于检查消息发送状态) + Future getMessageById(String conversationId, String messageId) async { + try { + EMConversationType convType = EMConversationType.Chat; + + // 获取会话对象 + final conversation = await EMClient.getInstance.chatManager.getConversation( + conversationId, + type: convType, + createIfNeed: false, + ); + + if (conversation == null) { + if (Get.isLogEnable) { + Get.log('❌ [IMManager] 会话不存在: $conversationId'); + } + return null; + } + + // 加载最新消息(包含刚发送的消息) + final messages = await conversation.loadMessages(loadCount: 50); + + // 查找指定 messageId 的消息 + for (var msg in messages) { + if (msg.msgId == messageId) { + return msg; + } + } + + return null; + } catch (e) { + if (Get.isLogEnable) { + Get.log('❌ [IMManager] 获取消息状态失败: $e'); + } + return null; + } + } + /// 获取用户信息 Future getUserInfo(String userId) async { var data = await EMClient.getInstance.userInfoManager.fetchUserInfoById([ @@ -416,6 +556,185 @@ class IMManager { return data[userId]; } + /// 在消息扩展字段中添加当前用户信息 + /// 这样接收方可以从消息中获取发送方的头像和昵称 + void _addUserInfoToMessageExt(EMMessage message) { + try { + // 获取当前登录用户ID + final currentUserId = storage.read('userId'); + if (currentUserId == null) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 无法获取当前用户ID,跳过添加用户信息到消息扩展字段'); + } + return; + } + + // 获取接收者ID(消息的 to 字段) + final receiverId = message.to; + + // ========== 获取发送者信息 ========== + String? senderNickName; + String? senderAvatarUrl; + + // 尝试从 ChatController 获取发送者信息 + final senderChatController = _activeChatControllers[currentUserId]; + if (senderChatController != null) { + senderNickName = senderChatController.userNickName; + senderAvatarUrl = senderChatController.userAvatarUrl; + } + + // 如果 ChatController 中没有,尝试从全局数据获取 + if (senderNickName == null || senderAvatarUrl == null) { + try { + final globalData = GlobalData.instance; + final userData = globalData.userData; + if (userData != null) { + senderNickName = senderNickName ?? userData.nickName; + senderAvatarUrl = senderAvatarUrl ?? userData.profilePhoto; + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 从 GlobalData 获取发送者信息失败: $e'); + } + } + } + + // ========== 获取接收者信息 ========== + String? receiverNickName; + String? receiverAvatarUrl; + + // 尝试从 ChatController 获取接收者信息(通过接收者ID查找) + if (receiverId != null && receiverId.isNotEmpty) { + final receiverChatController = _activeChatControllers[receiverId]; + if (receiverChatController != null && receiverChatController.userData != null) { + // 从接收者的 ChatController 获取接收者的信息 + // 注意:这里获取的是接收者自己的信息(用于发送方在聊天列表显示) + receiverNickName = receiverChatController.userData!.nickName; + receiverAvatarUrl = receiverChatController.userData!.profilePhoto; + } + } + + // ========== 设置消息扩展字段 ========== + // 同时存储发送者和接收者的信息 + // 发送者信息:用于接收方在聊天列表显示发送者的头像和昵称 + // 接收者信息:用于发送方在聊天列表显示接收者的头像和昵称 + if (message.attributes == null) { + message.attributes = {}; + } + + // 发送者信息(sender_ 前缀) + message.attributes!['sender_userId'] = currentUserId; + if (senderNickName != null && senderNickName.isNotEmpty) { + message.attributes!['sender_nickName'] = senderNickName; + } + if (senderAvatarUrl != null && senderAvatarUrl.isNotEmpty) { + final cleanSenderAvatarUrl = senderAvatarUrl.trim().replaceAll('`', ''); + message.attributes!['sender_avatarUrl'] = cleanSenderAvatarUrl; + } + + // 接收者信息(receiver_ 前缀) + if (receiverId != null && receiverId.isNotEmpty) { + message.attributes!['receiver_userId'] = receiverId; + if (receiverNickName != null && receiverNickName.isNotEmpty) { + message.attributes!['receiver_nickName'] = receiverNickName; + } + if (receiverAvatarUrl != null && receiverAvatarUrl.isNotEmpty) { + final cleanReceiverAvatarUrl = receiverAvatarUrl.trim().replaceAll('`', ''); + message.attributes!['receiver_avatarUrl'] = cleanReceiverAvatarUrl; + } + } + + // 为了兼容旧代码,也保留原来的字段(发送者信息) + message.attributes!['userId'] = currentUserId; + if (senderNickName != null && senderNickName.isNotEmpty) { + message.attributes!['nickName'] = senderNickName; + } + if (senderAvatarUrl != null && senderAvatarUrl.isNotEmpty) { + final cleanSenderAvatarUrl = senderAvatarUrl.trim().replaceAll('`', ''); + message.attributes!['avatarUrl'] = cleanSenderAvatarUrl; + } + + if (Get.isLogEnable) { + Get.log('✅ [IMManager] 已添加用户信息到消息扩展字段:'); + Get.log(' 发送者: userId=$currentUserId, nickName=$senderNickName, avatarUrl=${senderAvatarUrl?.trim().replaceAll('`', '')}'); + Get.log(' 接收者: userId=$receiverId, nickName=$receiverNickName, avatarUrl=${receiverAvatarUrl?.trim().replaceAll('`', '')}'); + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 添加用户信息到消息扩展字段失败: $e'); + } + } + } + + /// 从消息扩展字段中解析用户信息并缓存 + void _parseUserInfoFromMessageExt(EMMessage message) { + try { + // 获取发送方用户ID + final fromUserId = message.from; + if (fromUserId == null || fromUserId.isEmpty) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 消息发送者ID为空,跳过解析用户信息'); + } + return; + } + + // 从消息扩展字段中获取用户信息 + Map? attributes; + try { + attributes = message.attributes; + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e'); + } + return; + } + + if (attributes == null || attributes.isEmpty) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 消息扩展字段为空: msgId=${message.msgId}, fromUserId=$fromUserId'); + } + return; + } + + if (Get.isLogEnable) { + Get.log('🔍 [IMManager] 消息扩展字段内容: $attributes'); + } + + final nickName = attributes['nickName'] as String?; + final avatarUrl = attributes['avatarUrl'] as String?; + + if (nickName == null && avatarUrl == null) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 消息扩展字段中没有用户信息: msgId=${message.msgId}'); + } + return; + } + + // 缓存用户信息到 ConversationController + if (Get.isRegistered()) { + final conversationController = Get.find(); + final extendedInfo = ExtendedUserInfo( + userId: fromUserId, + nickName: nickName, + avatarUrl: avatarUrl, + ); + conversationController.cacheUserInfo(fromUserId, extendedInfo); + + if (Get.isLogEnable) { + Get.log('✅ [IMManager] 从消息扩展字段解析并缓存用户信息: userId=$fromUserId, nickName=$nickName, avatarUrl=$avatarUrl'); + } + } else { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] ConversationController 未注册,无法缓存用户信息'); + } + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 从消息扩展字段解析用户信息失败: $e'); + } + } + } + /// 注册 ChatController void registerChatController(ChatController controller) { _activeChatControllers[controller.userId] = controller; diff --git a/lib/network/home_api.g.dart b/lib/network/home_api.g.dart index 84908ed..e25687c 100644 --- a/lib/network/home_api.g.dart +++ b/lib/network/home_api.g.dart @@ -58,7 +58,7 @@ class _HomeApi implements HomeApi { ), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 5aa117a..ceac9f1 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -45,7 +45,7 @@ class _RtcApi implements RtcApi { (json) => RtcChannelData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -76,7 +76,7 @@ class _RtcApi implements RtcApi { (json) => RtcChannelData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -107,7 +107,7 @@ class _RtcApi implements RtcApi { (json) => RtcChannelData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -143,7 +143,7 @@ class _RtcApi implements RtcApi { (json) => RtcChannelDetail.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -177,7 +177,7 @@ class _RtcApi implements RtcApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -212,7 +212,7 @@ class _RtcApi implements RtcApi { (json) => RtcSeatUserInfo.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -246,7 +246,7 @@ class _RtcApi implements RtcApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -280,7 +280,7 @@ class _RtcApi implements RtcApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -311,7 +311,7 @@ class _RtcApi implements RtcApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -351,7 +351,7 @@ class _RtcApi implements RtcApi { ), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -392,7 +392,7 @@ class _RtcApi implements RtcApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -426,7 +426,7 @@ class _RtcApi implements RtcApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); diff --git a/lib/network/user_api.g.dart b/lib/network/user_api.g.dart index bde954b..758c6e7 100644 --- a/lib/network/user_api.g.dart +++ b/lib/network/user_api.g.dart @@ -46,7 +46,7 @@ class _UserApi implements UserApi { (json) => LoginData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -79,7 +79,7 @@ class _UserApi implements UserApi { (json) => UserBaseData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -111,7 +111,7 @@ class _UserApi implements UserApi { (json) => UserData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -145,7 +145,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -179,7 +179,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -213,7 +213,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -247,7 +247,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -289,7 +289,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -320,7 +320,7 @@ class _UserApi implements UserApi { (json) => json as String, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -351,7 +351,7 @@ class _UserApi implements UserApi { (json) => OssData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -391,7 +391,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -425,7 +425,7 @@ class _UserApi implements UserApi { (json) => UserRoseData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -468,7 +468,7 @@ class _UserApi implements UserApi { (json) => RoseHistoryData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -505,7 +505,7 @@ class _UserApi implements UserApi { (json) => SubmitOrderData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -548,7 +548,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -591,7 +591,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -634,7 +634,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -677,7 +677,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -720,7 +720,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -754,7 +754,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -796,7 +796,7 @@ class _UserApi implements UserApi { : List.empty(), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -830,7 +830,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -867,7 +867,7 @@ class _UserApi implements UserApi { (json) => BankCardOcrData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -901,7 +901,7 @@ class _UserApi implements UserApi { (json) => WithdrawData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -935,7 +935,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -975,7 +975,7 @@ class _UserApi implements UserApi { (json) => WithdrawAuditData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -1007,7 +1007,7 @@ class _UserApi implements UserApi { (json) => UserInfoData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -1044,7 +1044,7 @@ class _UserApi implements UserApi { (json) => WalletAccountData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -1090,7 +1090,7 @@ class _UserApi implements UserApi { WalletAccountRecordData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -1122,7 +1122,7 @@ class _UserApi implements UserApi { (json) => UserInfoData.fromJson(json as Map), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -1160,7 +1160,7 @@ class _UserApi implements UserApi { ), ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); @@ -1191,7 +1191,7 @@ class _UserApi implements UserApi { (json) => json as dynamic, ); } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); + errorLogger?.logError(e, s, _options, _result); rethrow; } final httpResponse = HttpResponse(_value, _result); diff --git a/lib/pages/home/content_card.dart b/lib/pages/home/content_card.dart index a1659b7..e842417 100644 --- a/lib/pages/home/content_card.dart +++ b/lib/pages/home/content_card.dart @@ -270,6 +270,7 @@ class ContentCard extends StatelessWidget { return GestureDetector( onTap: () { // 点击卡片跳转到用户信息页面,传递用户数据 + // 注意:userId 参数应该传入 miId,因为详情接口使用 miId 来获取用户信息 Get.to(() => UserInformationPage(miId: item.miId, userId: item.userId,)); }, child: Container( diff --git a/lib/pages/home/user_information_page.dart b/lib/pages/home/user_information_page.dart index b5cf999..096555e 100644 --- a/lib/pages/home/user_information_page.dart +++ b/lib/pages/home/user_information_page.dart @@ -7,6 +7,7 @@ import 'package:dating_touchme_app/pages/home/report_page.dart'; import 'package:dating_touchme_app/pages/message/chat_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:get/get_core/src/get_main.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; @@ -382,15 +383,43 @@ class UserInformationPage extends StatelessWidget { textColor: Colors.white, backgroundColor: Color(0xC3333333), ), - onTap: (){ - final userInfo = controller.userData.value; - if (userInfo.userId != null && userInfo.userId!.isNotEmpty) { + onTap: () async { + try { + final userInfo = controller.userData.value; + print('📨 [UserInformationPage] 点击发消息'); + print(' - 页面传入的miId: $miId'); + print(' - 接口返回的userInfo.miId: ${userInfo.miId}'); + print(' - 接口返回的userInfo.id: ${userInfo.id}'); + print(' - 接口返回的userInfo.userId: ${userInfo.userId}'); + print(' - 接口返回的userInfo.accountId: ${userInfo.accountId}'); + + // IM系统需要使用userId或accountId,而不是miId + // 优先使用userId(这是IM系统的用户ID),如果没有则使用accountId,最后才使用miId + String? targetUserId = (userInfo.userId?.isNotEmpty == true ? userInfo.userId : + (userInfo.accountId?.isNotEmpty == true ? userInfo.accountId : + (userInfo.miId?.isNotEmpty == true ? userInfo.miId : + (userInfo.id?.isNotEmpty == true ? userInfo.id : + (miId.isNotEmpty ? miId : null))))); + + if (targetUserId == null || targetUserId.isEmpty) { + print('❌ [UserInformationPage] 无法获取用户ID,无法跳转'); + SmartDialog.showToast('用户ID不存在,无法发送消息'); + return; + } + + print('✅ [UserInformationPage] 使用userId: $targetUserId (用于IM聊天)'); + // 使用工厂方法将 UserInfoData 转换为 MarriageData final marriageData = MarriageData.fromUserInfoData(userInfo); - Get.to(() => ChatPage( - userId: userInfo.userId ?? "", + print('✅ [UserInformationPage] 准备跳转到聊天页面,userId: $targetUserId'); + + await Get.to(() => ChatPage( + userId: targetUserId, userData: marriageData, )); + } catch (e) { + print('❌ [UserInformationPage] 跳转聊天页面失败: $e'); + SmartDialog.showToast('跳转失败,请重试'); } }, ), diff --git a/lib/pages/message/chat_page.dart b/lib/pages/message/chat_page.dart index 71c7276..d84e441 100644 --- a/lib/pages/message/chat_page.dart +++ b/lib/pages/message/chat_page.dart @@ -11,6 +11,8 @@ import '../../generated/assets.dart'; import '../../model/home/marriage_data.dart'; import '../../../widget/message/chat_input_bar.dart'; import '../../../widget/message/message_item.dart'; +import '../../../widget/message/chat_gift_popup.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'chat_settings_page.dart'; class ChatPage extends StatefulWidget { @@ -31,6 +33,10 @@ class _ChatPageState extends State { final ScrollController _scrollController = ScrollController(); bool _isLoadingMore = false; late ChatController _controller; + + // 礼物弹窗相关 + final activeGift = ValueNotifier(null); + final giftNum = ValueNotifier(1); @override void initState() { @@ -79,9 +85,43 @@ class _ChatPageState extends State { @override void dispose() { _scrollController.dispose(); + activeGift.dispose(); + giftNum.dispose(); super.dispose(); } + // 显示礼物弹窗 + void _showGiftPopup() { + final giftProducts = _controller.giftProducts.toList(); + if (giftProducts.isEmpty) { + SmartDialog.showToast('礼物列表加载中,请稍候...'); + return; + } + + SmartDialog.show( + builder: (context) { + return ChatGiftPopup( + activeGift: activeGift, + giftNum: giftNum, + giftList: giftProducts, + changeActive: (index) { + activeGift.value = index; + }, + onSendGift: (gift, quantity) async { + await _controller.sendGift(gift: gift, quantity: quantity); + }, + ); + }, + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.centerFade_otherSlide, + maskColor: Colors.black.withOpacity(0.5), + maskWidget: GestureDetector( + onTap: () => SmartDialog.dismiss(), + child: Container(color: Colors.transparent), + ), + ); + } + @override Widget build(BuildContext context) { return GetBuilder( @@ -194,6 +234,10 @@ class _ChatPageState extends State { // 处理视频录制/选择完成,回传文件路径和时长 await controller.sendVideoMessage(filePath, duration); }, + onGiftTap: () { + // 显示礼物弹窗 + _showGiftPopup(); + }, // 语音通话回调(暂时注释) // onVoiceCall: () async { // // 发起语音通话 diff --git a/lib/pages/message/conversation_tab.dart b/lib/pages/message/conversation_tab.dart index 7e2d965..e7c5c70 100644 --- a/lib/pages/message/conversation_tab.dart +++ b/lib/pages/message/conversation_tab.dart @@ -60,10 +60,10 @@ class _ConversationTabState extends State // 构建会话项 Widget _buildConversationItem(EMConversation conversation) { - return FutureBuilder( + return FutureBuilder( future: controller.loadContact(conversation.id), builder: (context, userSnapshot) { - final EMUserInfo? userInfo = userSnapshot.data; + final ExtendedUserInfo? userInfo = userSnapshot.data; return FutureBuilder( future: controller.lastMessage(conversation), builder: (context, messageSnapshot) { @@ -103,7 +103,7 @@ class _ConversationTabState extends State decoration: BoxDecoration( borderRadius: BorderRadius.circular(28), image: DecorationImage( - image: (userInfo?.avatarUrl ?? '').isNotEmpty + image: (userInfo?.avatarUrl?.isNotEmpty ?? false) ? NetworkImage(userInfo!.avatarUrl!) : const AssetImage( Assets.imagesAvatarsExample, @@ -125,7 +125,7 @@ class _ConversationTabState extends State children: [ Expanded( child: Text( - (userInfo?.nickName ?? '').isNotEmpty + (userInfo?.nickName?.isNotEmpty ?? false) ? userInfo!.nickName! : conversation.id, // 如果没有昵称,显示用户ID style: const TextStyle( diff --git a/lib/widget/message/chat_gift_item.dart b/lib/widget/message/chat_gift_item.dart new file mode 100644 index 0000000..8cecebd --- /dev/null +++ b/lib/widget/message/chat_gift_item.dart @@ -0,0 +1,115 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dating_touchme_app/model/live/gift_product_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ChatGiftItem extends StatelessWidget { + final GiftProductModel item; + final int active; + final int index; + final void Function(int) changeActive; + + const ChatGiftItem({ + super.key, + required this.item, + required this.active, + required this.index, + required this.changeActive, + }); + + @override + Widget build(BuildContext context) { + final isActive = active == index; + + return InkWell( + onTap: () { + changeActive(index); + }, + child: Container( + width: 83.w, + height: 94.w, + padding: EdgeInsets.only(top: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(9.w)), + color: Color.fromRGBO( + 117, + 98, + 249, + isActive ? .2 : 0, + ), + border: Border.all( + width: 1, + color: Color.fromRGBO( + 117, + 98, + 249, + isActive ? 1 : 0, + ), + ), + ), + child: Column( + children: [ + _buildImage(), + SizedBox(height: 7.w), + Text( + item.productTitle, + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(51, 51, 51, 1), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 1.w), + Text( + "${item.unitSellingPrice.toInt()}支", + style: TextStyle( + fontSize: 7.w, + color: const Color.fromRGBO(144, 144, 144, 1), + ), + ), + ], + ), + ), + ); + } + + Widget _buildImage() { + if (item.mainPic.isNotEmpty) { + return CachedNetworkImage( + imageUrl: item.mainPic, + width: 41.w, + height: 41.w, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + width: 41.w, + height: 41.w, + color: Colors.grey[300], + child: Center( + child: SizedBox( + width: 20.w, + height: 20.w, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.grey[600], + ), + ), + ), + ), + errorWidget: (context, url, error) => Container( + width: 41.w, + height: 41.w, + color: Colors.grey[300], + child: Icon(Icons.error_outline, size: 20.w, color: Colors.grey), + ), + ); + } else { + return Container( + width: 41.w, + height: 41.w, + color: Colors.grey[300], + ); + } + } +} + diff --git a/lib/widget/message/chat_gift_popup.dart b/lib/widget/message/chat_gift_popup.dart new file mode 100644 index 0000000..cf96214 --- /dev/null +++ b/lib/widget/message/chat_gift_popup.dart @@ -0,0 +1,184 @@ +import 'package:dating_touchme_app/model/live/gift_product_model.dart'; +import 'package:dating_touchme_app/widget/message/chat_gift_item.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +class ChatGiftPopup extends StatefulWidget { + const ChatGiftPopup({ + super.key, + required this.activeGift, + required this.giftNum, + required this.giftList, + required this.changeActive, + required this.onSendGift, + }); + + final ValueNotifier activeGift; + final ValueNotifier giftNum; + final List giftList; + final void Function(int) changeActive; + final Future Function(GiftProductModel, int) onSendGift; + + @override + State createState() => _ChatGiftPopupState(); +} + +class _ChatGiftPopupState extends State { + @override + void initState() { + super.initState(); + // 默认选择第一个礼物 + if (widget.giftList.isNotEmpty && widget.activeGift.value == null) { + widget.activeGift.value = 0; + } + } + + // 处理赠送礼物 + Future _handleSendGift() async { + // 检查是否选中了礼物 + final activeIndex = widget.activeGift.value; + if (activeIndex == null || + activeIndex < 0 || + activeIndex >= widget.giftList.length) { + SmartDialog.showToast('请先选择礼物'); + return; + } + + // 获取选中的礼物 + final gift = widget.giftList[activeIndex]; + final quantity = widget.giftNum.value; + + // 发送礼物 + await widget.onSendGift(gift, quantity); + SmartDialog.dismiss(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: Radius.circular(9.w), + ), + color: Colors.white, + ), + height: 363.w, + child: Column( + children: [ + _buildTab(), + _buildGiftSwiper(), + _buildBottomBar(), + ], + ), + ), + ); + } + + Widget _buildTab() { + return Container( + height: 47.w, + padding: EdgeInsets.only(left: 29.w), + child: Row( + children: [ + Text( + "礼物", + style: TextStyle( + fontSize: 13.w, + color: const Color.fromRGBO(117, 98, 249, 1), + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } + + Widget _buildGiftSwiper() { + if (widget.giftList.isEmpty) { + return Expanded( + child: Center( + child: Text( + '暂无礼物', + style: TextStyle(fontSize: 14.w, color: Colors.grey), + ), + ), + ); + } + + return Expanded( + child: ValueListenableBuilder( + valueListenable: widget.activeGift, + builder: (context, active, _) { + return GridView.builder( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行4个 + crossAxisSpacing: 7.w, + mainAxisSpacing: 7.w, + childAspectRatio: 0.85, // 调整宽高比 + ), + itemCount: widget.giftList.length, + itemBuilder: (context, index) { + return ChatGiftItem( + item: widget.giftList[index], + active: active ?? 0, + index: index, + changeActive: widget.changeActive, + ); + }, + ); + }, + ), + ); + } + + Widget _buildBottomBar() { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 数量选择(暂时不实现,固定为1) + SizedBox(width: 1.w), + ValueListenableBuilder( + valueListenable: widget.giftNum, + builder: (context, num, _) { + return Row( + children: [ + GestureDetector( + onTap: () => _handleSendGift(), + child: Container( + width: 63.w, + height: 30.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(30.w)), + gradient: const LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(61, 138, 224, 1), + Color.fromRGBO(131, 89, 255, 1), + ], + ), + ), + child: Center( + child: Text( + "赠送", + style: TextStyle(fontSize: 13.w, color: Colors.white), + ), + ), + ), + ), + ], + ); + }, + ), + ], + ), + ); + } +} + diff --git a/lib/widget/message/chat_input_bar.dart b/lib/widget/message/chat_input_bar.dart index dffb0c3..00c73ae 100644 --- a/lib/widget/message/chat_input_bar.dart +++ b/lib/widget/message/chat_input_bar.dart @@ -16,6 +16,7 @@ class ChatInputBar extends StatefulWidget { final Function(String filePath, int duration)? onVideoRecorded; final VoidCallback? onVoiceCall; // 语音通话回调 final VoidCallback? onVideoCall; // 视频通话回调 + final VoidCallback? onGiftTap; // 礼物按钮回调 const ChatInputBar({ required this.onSendMessage, @@ -24,6 +25,7 @@ class ChatInputBar extends StatefulWidget { this.onVideoRecorded, this.onVoiceCall, this.onVideoCall, + this.onGiftTap, super.key, }); @@ -334,7 +336,9 @@ class _ChatInputBarState extends State { // widget.onVideoCall?.call(); // }), // 礼物按钮 - Image.asset(Assets.imagesGift, width: 24.w, height: 24.w), + Image.asset(Assets.imagesGift, width: 24.w, height: 24.w).onTap(() { + widget.onGiftTap?.call(); + }), // 表情按钮 Image.asset(Assets.imagesEmoji, width: 24.w, height: 24.w).onTap(_toggleEmojiPanel), // 更多按钮 diff --git a/lib/widget/message/gift_item.dart b/lib/widget/message/gift_item.dart new file mode 100644 index 0000000..5d32ad6 --- /dev/null +++ b/lib/widget/message/gift_item.dart @@ -0,0 +1,293 @@ +import 'dart:convert'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:im_flutter_sdk/im_flutter_sdk.dart'; + +import '../../generated/assets.dart'; + +class GiftItem extends StatelessWidget { + final EMMessage message; + final bool isSentByMe; + final bool showTime; + final String formattedTime; + final VoidCallback? onResend; + + const GiftItem({ + required this.message, + required this.isSentByMe, + required this.showTime, + required this.formattedTime, + this.onResend, + super.key, + }); + + /// 从消息内容中解析礼物信息(使用特殊的JSON格式) + Map? _parseGiftInfo() { + try { + if (message.body.type == MessageType.TXT) { + final textBody = message.body as EMTextMessageBody; + final content = textBody.content; + + // 检查是否是礼物消息(以 [GIFT:] 开头) + if (content.startsWith('[GIFT:]')) { + final jsonStr = content.substring(7); // 移除 '[GIFT:]' 前缀 + return jsonDecode(jsonStr) as Map; + } + } + } catch (e) { + print('解析礼物信息失败: $e'); + } + return null; + } + + /// 获取礼物标题 + String _getGiftTitle() { + final giftInfo = _parseGiftInfo(); + if (giftInfo != null) { + return giftInfo['giftProductTitle']?.toString() ?? '礼物'; + } + return '礼物'; + } + + /// 获取礼物图片 + String _getGiftImage() { + final giftInfo = _parseGiftInfo(); + if (giftInfo != null) { + return giftInfo['giftMainPic']?.toString() ?? ''; + } + return ''; + } + + /// 获取礼物数量 + int _getGiftQuantity() { + final giftInfo = _parseGiftInfo(); + if (giftInfo != null) { + return giftInfo['quantity'] as int? ?? 1; + } + return 1; + } + + @override + Widget build(BuildContext context) { + final giftInfo = _parseGiftInfo(); + if (giftInfo == null) { + // 如果解析失败,不显示 + return SizedBox.shrink(); + } + + final giftTitle = _getGiftTitle(); + final giftImage = _getGiftImage(); + final quantity = _getGiftQuantity(); + + return Column( + children: [ + // 显示时间 + if (showTime) _buildTimeLabel(), + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), + child: Row( + mainAxisAlignment: + isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (!isSentByMe) _buildAvatar(), + if (!isSentByMe) SizedBox(width: 8.w), + // 发送消息时,状态在左侧 + if (isSentByMe) + Align( + alignment: Alignment.center, + child: Container( + margin: EdgeInsets.only(top: 10.h), + child: _buildMessageStatus(), + ), + ), + if (isSentByMe) SizedBox(width: 10.w), + // 礼物消息容器 + Container( + constraints: BoxConstraints(maxWidth: 200.w), + margin: EdgeInsets.only(top: 10.h), + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: isSentByMe ? Color(0xff8E7BF6) : Colors.white, + borderRadius: BorderRadius.only( + topLeft: + isSentByMe ? Radius.circular(12.w) : Radius.circular(0), + topRight: + isSentByMe ? Radius.circular(0) : Radius.circular(12.w), + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 礼物图片 + if (giftImage.isNotEmpty) + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + color: Colors.grey[200], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.w), + child: CachedNetworkImage( + imageUrl: giftImage, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + color: Colors.grey[200], + child: Center( + child: SizedBox( + width: 20.w, + height: 20.w, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.grey[600], + ), + ), + ), + ), + errorWidget: (context, url, error) => Container( + color: Colors.grey[200], + child: Icon( + Icons.card_giftcard, + size: 20.w, + color: Colors.grey[400], + ), + ), + ), + ), + ) + else + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + color: Colors.grey[200], + ), + child: Icon( + Icons.card_giftcard, + size: 20.w, + color: Colors.grey[400], + ), + ), + SizedBox(width: 8.w), + // 礼物信息 + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + giftTitle, + style: TextStyle( + fontSize: 14.sp, + color: isSentByMe ? Colors.white : Colors.black87, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (quantity > 1) ...[ + SizedBox(height: 2.h), + Text( + 'x$quantity', + style: TextStyle( + fontSize: 12.sp, + color: isSentByMe + ? Colors.white70 + : Colors.grey[600], + ), + ), + ], + ], + ), + ), + ], + ), + ), + if (isSentByMe) SizedBox(width: 8.w), + if (isSentByMe) _buildAvatar(), + ], + ), + ), + ], + ); + } + + // 构建时间标签 + Widget _buildTimeLabel() { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: Text( + formattedTime, + style: TextStyle(fontSize: 12.sp, color: Colors.grey), + ), + ), + ); + } + + // 构建头像 + Widget _buildAvatar() { + return Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.w), + image: DecorationImage( + image: AssetImage(Assets.imagesAvatarsExample), + fit: BoxFit.cover, + ), + ), + ); + } + + // 构建消息状态(发送中、已发送、失败重发) + Widget _buildMessageStatus() { + if (!isSentByMe) { + return SizedBox.shrink(); + } + + final status = message.status; + + if (status == MessageStatus.FAIL) { + // 发送失败,显示重发按钮 + return GestureDetector( + onTap: onResend, + child: Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.refresh, + size: 14.w, + color: Colors.red, + ), + ), + ); + } else if (status == MessageStatus.PROGRESS) { + // 发送中,显示加载动画 + return Container( + width: 16.w, + height: 16.w, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.grey), + ), + ); + } else { + // 发送成功,不显示任何状态 + return SizedBox.shrink(); + } + } +} + diff --git a/lib/widget/message/message_item.dart b/lib/widget/message/message_item.dart index e46c074..3e65e0d 100644 --- a/lib/widget/message/message_item.dart +++ b/lib/widget/message/message_item.dart @@ -8,6 +8,7 @@ import 'image_item.dart'; import 'voice_item.dart'; import 'video_item.dart'; import 'call_item.dart'; +import 'gift_item.dart'; import '../../controller/message/chat_controller.dart'; class MessageItem extends StatelessWidget { @@ -31,9 +32,7 @@ class MessageItem extends StatelessWidget { final textBody = message.body as EMTextMessageBody; final content = textBody.content; // 检查是否是通话消息(以 [CALL:] 开头) - if (content != null && content.startsWith('[CALL:]')) { - return true; - } + return content.startsWith('[CALL:]'); } } catch (e) { // 解析失败,不是通话消息 @@ -41,6 +40,21 @@ class MessageItem extends StatelessWidget { return false; } + // 检查是否是礼物消息(通过消息内容识别) + bool _isGiftMessage() { + try { + if (message.body.type == MessageType.TXT) { + final textBody = message.body as EMTextMessageBody; + final content = textBody.content; + // 检查是否是礼物消息(以 [GIFT:] 开头) + return content.startsWith('[GIFT:]'); + } + } catch (e) { + // 解析失败,不是礼物消息 + } + return false; + } + @override Widget build(BuildContext context) { print('📨 [MessageItem] 渲染消息,类型: ${message.body.type}'); @@ -64,6 +78,25 @@ class MessageItem extends StatelessWidget { ); } + // 处理礼物消息(通过文本消息的扩展属性识别) + if (message.body.type == MessageType.TXT && _isGiftMessage()) { + return GiftItem( + message: message, + isSentByMe: isSentByMe, + showTime: shouldShowTime(), + formattedTime: formatMessageTime(message.serverTime), + onResend: () { + // 通过传入的 controller 或 Get 找到 ChatController 并调用重发方法 + try { + final controller = chatController ?? Get.find(); + controller.resendMessage(message); + } catch (e) { + print('重发消息失败: $e'); + } + }, + ); + } + // 处理文本消息 if (message.body.type == MessageType.TXT) { final textBody = message.body as EMTextMessageBody; diff --git a/location_plugin/example/pubspec.lock b/location_plugin/example/pubspec.lock index 36a3835..678892a 100644 --- a/location_plugin/example/pubspec.lock +++ b/location_plugin/example/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.13.0" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" cupertino_icons: @@ -46,7 +46,7 @@ packages: description: name: cupertino_icons sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.8" fake_async: @@ -54,7 +54,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" file: @@ -62,7 +62,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" flutter: @@ -80,7 +80,7 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_test: @@ -98,7 +98,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" http_parser: @@ -106,7 +106,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" integration_test: @@ -119,7 +119,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -127,7 +127,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -135,7 +135,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -143,7 +143,7 @@ packages: description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" location_plugin: @@ -158,7 +158,7 @@ packages: description: name: matcher sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.12.17" material_color_utilities: @@ -166,7 +166,7 @@ packages: description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.11.1" meta: @@ -174,7 +174,7 @@ packages: description: name: meta sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.16.0" path: @@ -182,7 +182,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" platform: @@ -190,7 +190,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.6" plugin_platform_interface: @@ -198,7 +198,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" process: @@ -206,7 +206,7 @@ packages: description: name: process sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.5" sky_engine: @@ -219,7 +219,7 @@ packages: description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.1" stack_trace: @@ -227,7 +227,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: @@ -235,7 +235,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: @@ -243,7 +243,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" sync_http: @@ -251,7 +251,7 @@ packages: description: name: sync_http sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.1" term_glyph: @@ -259,7 +259,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: @@ -267,7 +267,7 @@ packages: description: name: test_api sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.6" typed_data: @@ -275,7 +275,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" vector_math: @@ -283,7 +283,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" vm_service: @@ -291,7 +291,7 @@ packages: description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "15.0.2" web: @@ -299,7 +299,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" webdriver: @@ -307,7 +307,7 @@ packages: description: name: webdriver sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.0" sdks: diff --git a/pubspec.lock b/pubspec.lock index 97cd0f3..f8dcbe5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,16 +5,16 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a - url: "https://pub.flutter-io.cn" + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" source: hosted - version: "88.0.0" + version: "91.0.0" agora_rtc_engine: dependency: "direct main" description: name: agora_rtc_engine sha256: "6559294d18ce4445420e19dbdba10fb58cac955cd8f22dbceae26716e194d70e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.5.3" agora_rtm: @@ -22,7 +22,7 @@ packages: description: name: agora_rtm sha256: "3cd8e25ecfccbb2e8ca5a70b173bff080dab782e233f9b40f483adbd31dd38fb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.5" agora_token_generator: @@ -30,23 +30,23 @@ packages: description: name: agora_token_generator sha256: eeb53d753430b6d6227b05ace89655cd7990b3137a236937825699d528377904 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" - url: "https://pub.flutter-io.cn" + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.4.1" ansicolor: dependency: transitive description: name: ansicolor sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.3" app_settings: @@ -54,7 +54,7 @@ packages: description: name: app_settings sha256: "3e46c561441e5820d3a25339bf8b51b9e45a5f686873851a20c257a530917795" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.1" archive: @@ -62,7 +62,7 @@ packages: description: name: archive sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.7" args: @@ -70,7 +70,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" async: @@ -78,7 +78,7 @@ packages: description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.13.0" audioplayers: @@ -86,7 +86,7 @@ packages: description: name: audioplayers sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.5.1" audioplayers_android: @@ -94,7 +94,7 @@ packages: description: name: audioplayers_android sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.2.1" audioplayers_darwin: @@ -102,7 +102,7 @@ packages: description: name: audioplayers_darwin sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.0" audioplayers_linux: @@ -110,7 +110,7 @@ packages: description: name: audioplayers_linux sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.2.1" audioplayers_platform_interface: @@ -118,7 +118,7 @@ packages: description: name: audioplayers_platform_interface sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.1.1" audioplayers_web: @@ -126,7 +126,7 @@ packages: description: name: audioplayers_web sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" audioplayers_windows: @@ -134,7 +134,7 @@ packages: description: name: audioplayers_windows sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.2.1" boolean_selector: @@ -142,23 +142,23 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" build: dependency: transitive description: name: build - sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9 - url: "https://pub.flutter-io.cn" + sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413 + url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" build_config: dependency: transitive description: name: build_config sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.0" build_daemon: @@ -166,39 +166,39 @@ packages: description: name: build_daemon sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "7b5b569f3df370590a85029148d6fc66c7d0201fc6f1847c07dd85d365ae9fcd" - url: "https://pub.flutter-io.cn" + sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057" + url: "https://pub.dev" source: hosted - version: "2.10.3" + version: "2.10.4" built_collection: dependency: transitive description: name: built_collection sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d - url: "https://pub.flutter-io.cn" + sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" + url: "https://pub.dev" source: hosted - version: "8.12.0" + version: "8.12.1" cached_network_image: dependency: "direct main" description: name: cached_network_image sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.1" cached_network_image_platform_interface: @@ -206,7 +206,7 @@ packages: description: name: cached_network_image_platform_interface sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.1" cached_network_image_web: @@ -214,7 +214,7 @@ packages: description: name: cached_network_image_web sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.1" camera: @@ -222,23 +222,23 @@ packages: description: name: camera sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.10.6" camera_android: dependency: transitive description: name: camera_android - sha256: "292c96d986feda7ae5882abe633423c6e70ab4579c038ab78711010f5d7ecf5c" - url: "https://pub.flutter-io.cn" + sha256: "50c0d1c4b122163e3d7cdfcd6d4cd8078aac27d0f1cd1e7b3fa69e6b3f06f4b7" + url: "https://pub.dev" source: hosted - version: "0.10.10+13" + version: "0.10.10+14" camera_avfoundation: dependency: transitive description: name: camera_avfoundation sha256: "035b90c1e33c2efad7548f402572078f6e514d4f82be0a315cd6c6af7e855aa8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.22+6" camera_platform_interface: @@ -246,23 +246,23 @@ packages: description: name: camera_platform_interface sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.12.0" camera_web: dependency: transitive description: name: camera_web - sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" - url: "https://pub.flutter-io.cn" + sha256: "77e53acb64d9de8917424eeb32b5c7c73572d1e00954bbf54a1e609d79a751a2" + url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.5+1" characters: dependency: transitive description: name: characters sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" checked_yaml: @@ -270,7 +270,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.4" chewie: @@ -278,7 +278,7 @@ packages: description: name: chewie sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.13.0" clock: @@ -286,7 +286,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" code_builder: @@ -294,7 +294,7 @@ packages: description: name: code_builder sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.11.0" collection: @@ -302,7 +302,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" common_utils: @@ -310,7 +310,7 @@ packages: description: name: common_utils sha256: c26884339b13ff99b0739e56f4b02090c84054ed9dd3a045435cd24e7b99c2c1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.0" convert: @@ -318,23 +318,23 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.2" cross_file: dependency: transitive description: name: cross_file - sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" - url: "https://pub.flutter-io.cn" + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.5+1" crypto: dependency: transitive description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.7" csslib: @@ -342,7 +342,7 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" cupertino_icons: @@ -350,23 +350,23 @@ packages: description: name: cupertino_icons sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.8" dart_style: dependency: transitive description: name: dart_style - sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 - url: "https://pub.flutter-io.cn" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" dbus: dependency: transitive description: name: dbus sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.11" decimal: @@ -374,7 +374,7 @@ packages: description: name: decimal sha256: fc706a5618b81e5b367b01dd62621def37abc096f2b46a9bd9068b64c1fa36d0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.4" dio: @@ -382,7 +382,7 @@ packages: description: name: dio sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.9.0" dio_web_adapter: @@ -390,7 +390,7 @@ packages: description: name: dio_web_adapter sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" easy_localization: @@ -398,7 +398,7 @@ packages: description: name: easy_localization sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.8" easy_logger: @@ -406,7 +406,7 @@ packages: description: name: easy_logger sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.2" easy_refresh: @@ -414,7 +414,7 @@ packages: description: name: easy_refresh sha256: "486e30abfcaae66c0f2c2798a10de2298eb9dc5e0bb7e1dba9328308968cae0c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.0" event_bus: @@ -422,7 +422,7 @@ packages: description: name: event_bus sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" extended_image: @@ -430,7 +430,7 @@ packages: description: name: extended_image sha256: "85199f9233e03abc2ce2e68cbb2991648666af4a527ae4e6250935be8edfddae" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.1.0" extended_image_library: @@ -438,7 +438,7 @@ packages: description: name: extended_image_library sha256: e61dafd94400fff6ef7ed1523d445ff3af137f198f3228e4a3107bc5b4bec5d1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.6" fake_async: @@ -446,7 +446,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" ffi: @@ -454,7 +454,7 @@ packages: description: name: ffi sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" file: @@ -462,31 +462,31 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "80a877f5ec570c4fb3b40720a70b6f31e8bb1315a464b4d3e92fe82754d4b21a" - url: "https://pub.flutter-io.cn" + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "44f24d102e368370951b98ffe86c7325b38349e634578312976607d28cc6d747" - url: "https://pub.flutter-io.cn" + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" source: hosted - version: "0.9.4+6" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" file_selector_windows: @@ -494,7 +494,7 @@ packages: description: name: file_selector_windows sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.3+5" fixnum: @@ -502,7 +502,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" flustars: @@ -510,7 +510,7 @@ packages: description: name: flustars sha256: "7019ab8d68c0d4759ee122644d91a165d450b0492717f9e7e9d0ce277dcf664b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" flutter: @@ -523,7 +523,7 @@ packages: description: name: flutter_cache_manager sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.1" flutter_lints: @@ -531,7 +531,7 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_localizations: @@ -544,7 +544,7 @@ packages: description: name: flutter_native_splash sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.7" flutter_oss_aliyun: @@ -552,23 +552,23 @@ packages: description: name: flutter_oss_aliyun sha256: "8280c1e8dfb792dec6449d1e6052e1d3492b3b3a1dfee456cc41d3f31b4cbc26" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.4.2" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687" - url: "https://pub.flutter-io.cn" + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" source: hosted - version: "2.0.32" + version: "2.0.33" flutter_screenutil: dependency: "direct main" description: name: flutter_screenutil sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.9.3" flutter_slidable: @@ -576,7 +576,7 @@ packages: description: name: flutter_slidable sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.2" flutter_smart_dialog: @@ -584,7 +584,7 @@ packages: description: name: flutter_smart_dialog sha256: "0852df132cb03fd8fc5144eb404c31eb7eb50c22aecb1cc2504f2f598090d756" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.9.8+9" flutter_svga: @@ -592,7 +592,7 @@ packages: description: name: flutter_svga sha256: "8b96237fd33c80f3e9850245515d41c70520a4bcb9f3415e96b53fb798e6ab0a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.8" flutter_swiper_null_safety: @@ -600,7 +600,7 @@ packages: description: name: flutter_swiper_null_safety sha256: "5a855e0080d035c08e82f8b7fd2f106344943a30c9ab483b2584860a2f22eaaf" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" flutter_test: @@ -618,23 +618,23 @@ packages: description: name: fluwx sha256: "7e92d2000ee49c5262a88c51ea2d22b91a753d5b29df27cc264bb0a115d65373" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.7.5" get: dependency: "direct main" description: name: get - sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 - url: "https://pub.flutter-io.cn" + sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" + url: "https://pub.dev" source: hosted - version: "4.7.2" + version: "4.7.3" get_storage: dependency: "direct main" description: name: get_storage sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" glob: @@ -642,7 +642,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.3" graphs: @@ -650,7 +650,7 @@ packages: description: name: graphs sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.2" html: @@ -658,7 +658,7 @@ packages: description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.15.6" http: @@ -666,7 +666,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" http_client_helper: @@ -674,7 +674,7 @@ packages: description: name: http_client_helper sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" http_multi_server: @@ -682,7 +682,7 @@ packages: description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" http_parser: @@ -690,7 +690,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" im_flutter_sdk: @@ -698,7 +698,7 @@ packages: description: name: im_flutter_sdk sha256: "5f81988c5edf14a4e3868b9c47f01de2ee355e2203ae5bd95d7cacfe30e15d97" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.15.2" im_flutter_sdk_android: @@ -706,7 +706,7 @@ packages: description: name: im_flutter_sdk_android sha256: ce4e01f1374a6cf20bb46f6774e1effbde5c014fd1cd3003b6604813e4fe9e71 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.15.2" im_flutter_sdk_interface: @@ -714,7 +714,7 @@ packages: description: name: im_flutter_sdk_interface sha256: "08ad3ce9fc935e24272bce34aebfd9b3c3c30aab2b9e7d4b2e759acf4a4874f5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.15.2" im_flutter_sdk_ios: @@ -722,7 +722,7 @@ packages: description: name: im_flutter_sdk_ios sha256: "11300c086f5821730224932c28860198ef5d879f7941f9158dddee499f7bb60e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.15.2" image: @@ -730,7 +730,7 @@ packages: description: name: image sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.5.4" image_picker: @@ -738,7 +738,7 @@ packages: description: name: image_picker sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" image_picker_android: @@ -746,23 +746,23 @@ packages: description: name: image_picker_android sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.8.12+23" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" - url: "https://pub.flutter-io.cn" + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" image_picker_ios: dependency: transitive description: name: image_picker_ios sha256: "997d100ce1dda5b1ba4085194c5e36c9f8a1fb7987f6a36ab677a344cd2dc986" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.8.13+2" image_picker_linux: @@ -770,7 +770,7 @@ packages: description: name: image_picker_linux sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2" image_picker_macos: @@ -778,7 +778,7 @@ packages: description: name: image_picker_macos sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2+1" image_picker_platform_interface: @@ -786,7 +786,7 @@ packages: description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.11.1" image_picker_windows: @@ -794,7 +794,7 @@ packages: description: name: image_picker_windows sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2" intl: @@ -802,7 +802,7 @@ packages: description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.20.2" io: @@ -810,7 +810,7 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.5" iris_method_channel: @@ -818,7 +818,7 @@ packages: description: name: iris_method_channel sha256: bfb5cfc6c6eae42da8cd1b35977a72d8b8881848a5dfc3d672e4760a907d11a0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.4" js: @@ -826,7 +826,7 @@ packages: description: name: js sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.2" json_annotation: @@ -834,23 +834,23 @@ packages: description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe" - url: "https://pub.flutter-io.cn" + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 + url: "https://pub.dev" source: hosted - version: "6.11.1" + version: "6.11.2" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -858,7 +858,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -866,7 +866,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -874,7 +874,7 @@ packages: description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" location_plugin: @@ -889,7 +889,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.0" matcher: @@ -897,7 +897,7 @@ packages: description: name: matcher sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.12.17" material_color_utilities: @@ -905,7 +905,7 @@ packages: description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.11.1" meta: @@ -913,7 +913,7 @@ packages: description: name: meta sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.16.0" mime: @@ -921,7 +921,7 @@ packages: description: name: mime sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.6" nested: @@ -929,7 +929,7 @@ packages: description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" octo_image: @@ -937,7 +937,7 @@ packages: description: name: octo_image sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.0" package_config: @@ -945,7 +945,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" package_info_plus: @@ -953,7 +953,7 @@ packages: description: name: package_info_plus sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.0.0" package_info_plus_platform_interface: @@ -961,7 +961,7 @@ packages: description: name: package_info_plus_platform_interface sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.1" path: @@ -969,7 +969,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" path_drawing: @@ -977,7 +977,7 @@ packages: description: name: path_drawing sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" path_parsing: @@ -985,7 +985,7 @@ packages: description: name: path_parsing sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" path_provider: @@ -993,31 +993,31 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "95c68a74d3cab950fd0ed8073d9fab15c1c06eb1f3eec68676e87aabc9ecee5a" - url: "https://pub.flutter-io.cn" + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" source: hosted - version: "2.2.21" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "97390a0719146c7c3e71b6866c34f1cde92685933165c1c671984390d2aca776" - url: "https://pub.flutter-io.cn" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.5.1" path_provider_linux: dependency: transitive description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -1025,7 +1025,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" path_provider_windows: @@ -1033,7 +1033,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" permission_handler: @@ -1041,7 +1041,7 @@ packages: description: name: permission_handler sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "12.0.1" permission_handler_android: @@ -1049,7 +1049,7 @@ packages: description: name: permission_handler_android sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "13.0.1" permission_handler_apple: @@ -1057,7 +1057,7 @@ packages: description: name: permission_handler_apple sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.4.7" permission_handler_html: @@ -1065,7 +1065,7 @@ packages: description: name: permission_handler_html sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.3+5" permission_handler_platform_interface: @@ -1073,7 +1073,7 @@ packages: description: name: permission_handler_platform_interface sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.3.0" permission_handler_windows: @@ -1081,7 +1081,7 @@ packages: description: name: permission_handler_windows sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.1" petitparser: @@ -1089,23 +1089,23 @@ packages: description: name: petitparser sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" photo_manager: dependency: transitive description: name: photo_manager - sha256: a0d9a7a9bc35eda02d33766412bde6d883a8b0acb86bbe37dac5f691a0894e8a - url: "https://pub.flutter-io.cn" + sha256: "12c8873ec2e7488dd6a0cebcb3c4f1d6c51db34ba696c973faf121b53ffd210e" + url: "https://pub.dev" source: hosted - version: "3.7.1" + version: "3.8.2" photo_manager_image_provider: dependency: transitive description: name: photo_manager_image_provider sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" platform: @@ -1113,7 +1113,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.6" plugin_platform_interface: @@ -1121,7 +1121,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" pointycastle: @@ -1129,7 +1129,7 @@ packages: description: name: pointycastle sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.0" pool: @@ -1137,7 +1137,7 @@ packages: description: name: pool sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.2" posix: @@ -1145,7 +1145,7 @@ packages: description: name: posix sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.0.3" protobuf: @@ -1153,7 +1153,7 @@ packages: description: name: protobuf sha256: de9c9eb2c33f8e933a42932fe1dc504800ca45ebc3d673e6ed7f39754ee4053e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.2.0" provider: @@ -1161,7 +1161,7 @@ packages: description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.5+1" pub_semver: @@ -1169,7 +1169,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" pubspec_parse: @@ -1177,7 +1177,7 @@ packages: description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.0" rational: @@ -1185,7 +1185,7 @@ packages: description: name: rational sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.3" record: @@ -1193,31 +1193,31 @@ packages: description: name: record sha256: "6bad72fb3ea6708d724cf8b6c97c4e236cf9f43a52259b654efeb6fd9b737f1f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.2" record_android: dependency: transitive description: name: record_android - sha256: fb54ee4e28f6829b8c580252a9ef49d9c549cfd263b0660ad7eeac0908658e9f - url: "https://pub.flutter-io.cn" + sha256: "9aaf3f151e61399b09bd7c31eb5f78253d2962b3f57af019ac5a2d1a3afdcf71" + url: "https://pub.dev" source: hosted - version: "1.4.4" + version: "1.4.5" record_ios: dependency: transitive description: name: record_ios - sha256: "765b42ac1be019b1674ddd809b811fc721fe5a93f7bb1da7803f0d16772fd6d7" - url: "https://pub.flutter-io.cn" + sha256: "69fcd37c6185834e90254573599a9165db18a2cbfa266b6d1e46ffffeb06a28c" + url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" record_linux: dependency: transitive description: name: record_linux sha256: "235b1f1fb84e810f8149cc0c2c731d7d697f8d1c333b32cb820c449bf7bb72d8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" record_macos: @@ -1225,7 +1225,7 @@ packages: description: name: record_macos sha256: "842ea4b7e95f4dd237aacffc686d1b0ff4277e3e5357865f8d28cd28bc18ed95" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" record_platform_interface: @@ -1233,39 +1233,39 @@ packages: description: name: record_platform_interface sha256: b0065fdf1ec28f5a634d676724d388a77e43ce7646fb049949f58c69f3fcb4ed - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" record_web: dependency: transitive description: name: record_web - sha256: "20ac10d56514cb9f8cecc8f3579383084fdfb43b0d04e05a95244d0d76091d90" - url: "https://pub.flutter-io.cn" + sha256: "3feeffbc0913af3021da9810bb8702a068db6bc9da52dde1d19b6ee7cb9edb51" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" record_windows: dependency: transitive description: name: record_windows sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.7" retrofit: dependency: "direct main" description: name: retrofit - sha256: "7d78824afa6eeeaf6ac58220910ee7a97597b39e93360d4bda230b7c6df45089" - url: "https://pub.flutter-io.cn" + sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe" + url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.9.1" retrofit_generator: dependency: "direct dev" description: name: retrofit_generator sha256: "5827551e2496f2c9586e4f23eedd6ce0b519286d2f405f91bf70f342a96b48ce" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.0.6" rxdart: @@ -1273,7 +1273,7 @@ packages: description: name: rxdart sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.28.0" sensors_plus: @@ -1281,7 +1281,7 @@ packages: description: name: sensors_plus sha256: "89e2bfc3d883743539ce5774a2b93df61effde40ff958ecad78cd66b1a8b8d52" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.2" sensors_plus_platform_interface: @@ -1289,7 +1289,7 @@ packages: description: name: sensors_plus_platform_interface sha256: "58815d2f5e46c0c41c40fb39375d3f127306f7742efe3b891c0b1c87e2b5cd5d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" shared_preferences: @@ -1297,23 +1297,23 @@ packages: description: name: shared_preferences sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "07d552dbe8e71ed720e5205e760438ff4ecfb76ec3b32ea664350e2ca4b0c43b" - url: "https://pub.flutter-io.cn" + sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b" + url: "https://pub.dev" source: hosted - version: "2.4.16" + version: "2.4.17" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.6" shared_preferences_linux: @@ -1321,7 +1321,7 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_platform_interface: @@ -1329,7 +1329,7 @@ packages: description: name: shared_preferences_platform_interface sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_web: @@ -1337,7 +1337,7 @@ packages: description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.3" shared_preferences_windows: @@ -1345,7 +1345,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shelf: @@ -1353,7 +1353,7 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.2" shelf_web_socket: @@ -1361,7 +1361,7 @@ packages: description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" sky_engine: @@ -1373,16 +1373,16 @@ packages: dependency: transitive description: name: source_gen - sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243" - url: "https://pub.flutter-io.cn" + sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75" + url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.1" source_helper: dependency: transitive description: name: source_helper sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.8" source_span: @@ -1390,7 +1390,7 @@ packages: description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.1" sp_util: @@ -1398,7 +1398,7 @@ packages: description: name: sp_util sha256: "9da43dce5de79c17a787d0626bf01538d63090ca32521200d22a232171c495dc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.3" sqflite: @@ -1406,7 +1406,7 @@ packages: description: name: sqflite sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.2" sqflite_android: @@ -1414,7 +1414,7 @@ packages: description: name: sqflite_android sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.2+2" sqflite_common: @@ -1422,7 +1422,7 @@ packages: description: name: sqflite_common sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.6" sqflite_darwin: @@ -1430,7 +1430,7 @@ packages: description: name: sqflite_darwin sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.2" sqflite_platform_interface: @@ -1438,7 +1438,7 @@ packages: description: name: sqflite_platform_interface sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.0" stack_trace: @@ -1446,7 +1446,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: @@ -1454,7 +1454,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" stream_transform: @@ -1462,7 +1462,7 @@ packages: description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" string_scanner: @@ -1470,7 +1470,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" synchronized: @@ -1478,7 +1478,7 @@ packages: description: name: synchronized sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.0" tdesign_flutter: @@ -1486,7 +1486,7 @@ packages: description: name: tdesign_flutter sha256: cf166a5fdbbfd9129305d2c2906633c8d0151bbb2d0a6fbcf901bdd76726a555 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.6" tdesign_flutter_adaptation: @@ -1494,7 +1494,7 @@ packages: description: name: tdesign_flutter_adaptation sha256: "707bbc52ec8b5872ea808500200f402007acf24c03a1cc823a3be2113c1d813a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.32.0" term_glyph: @@ -1502,7 +1502,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: @@ -1510,7 +1510,7 @@ packages: description: name: test_api sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.6" typed_data: @@ -1518,7 +1518,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" universal_io: @@ -1526,7 +1526,7 @@ packages: description: name: universal_io sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.1" url_launcher: @@ -1534,7 +1534,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.2" url_launcher_android: @@ -1542,7 +1542,7 @@ packages: description: name: url_launcher_android sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.28" url_launcher_ios: @@ -1550,7 +1550,7 @@ packages: description: name: url_launcher_ios sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.6" url_launcher_linux: @@ -1558,7 +1558,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" url_launcher_macos: @@ -1566,7 +1566,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -1574,7 +1574,7 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.2" url_launcher_web: @@ -1582,7 +1582,7 @@ packages: description: name: url_launcher_web sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" url_launcher_windows: @@ -1590,7 +1590,7 @@ packages: description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.5" uuid: @@ -1598,7 +1598,7 @@ packages: description: name: uuid sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.5.2" vector_math: @@ -1606,7 +1606,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" video_player: @@ -1614,31 +1614,31 @@ packages: description: name: video_player sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.10.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: "36913f94430b474c4a9033d59b7552b800e736a8521e7166e84895ddcedd0b03" - url: "https://pub.flutter-io.cn" + sha256: "3f7ef3fb7b29f510e58f4d56b6ffbc3463b1071f2cf56e10f8d25f5b991ed85b" + url: "https://pub.dev" source: hosted - version: "2.8.19" + version: "2.8.21" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "6bced1739cf1f96f03058118adb8ac0dd6f96aa1a1a6e526424ab92fd2a6a77d" - url: "https://pub.flutter-io.cn" + sha256: e4d33b79a064498c6eb3a6a492b6a5012573d4943c28d566caf1a6c0840fe78d + url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.8.8" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.6.0" video_player_web: @@ -1646,7 +1646,7 @@ packages: description: name: video_player_web sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.0" video_thumbnail: @@ -1654,7 +1654,7 @@ packages: description: name: video_thumbnail sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.6" visibility_detector: @@ -1662,7 +1662,7 @@ packages: description: name: visibility_detector sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.0+2" vm_service: @@ -1670,7 +1670,7 @@ packages: description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "15.0.2" wakelock_plus: @@ -1678,7 +1678,7 @@ packages: description: name: wakelock_plus sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" wakelock_plus_platform_interface: @@ -1686,7 +1686,7 @@ packages: description: name: wakelock_plus_platform_interface sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.0" watcher: @@ -1694,7 +1694,7 @@ packages: description: name: watcher sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.4" web: @@ -1702,7 +1702,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" web_socket: @@ -1710,7 +1710,7 @@ packages: description: name: web_socket sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" web_socket_channel: @@ -1718,7 +1718,7 @@ packages: description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.3" wechat_assets_picker: @@ -1726,7 +1726,7 @@ packages: description: name: wechat_assets_picker sha256: c307e50394c1e6dfcd5c4701e84efb549fce71444fedcf2e671c50d809b3e2a1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.8.0" wechat_camera_picker: @@ -1734,7 +1734,7 @@ packages: description: name: wechat_camera_picker sha256: "776ce32feda72d84b63425533a27d3b822bfb93cc063d2aef3cc6d788769f36b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.4.0" wechat_picker_library: @@ -1742,7 +1742,7 @@ packages: description: name: wechat_picker_library sha256: "5cb61b9aa935b60da5b043f8446fbb9c5077419f20ccc4856bf444aec4f44bc1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.7" win32: @@ -1750,7 +1750,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.15.0" xdg_directories: @@ -1758,7 +1758,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" xml: @@ -1766,7 +1766,7 @@ packages: description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.6.1" yaml: @@ -1774,7 +1774,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.3" sdks: