Browse Source

1. 完善聊天,添加礼物

ios
Jolie 4 months ago
parent
commit
3a0f593b17
20 changed files with 2705 additions and 411 deletions
  1. 65
      .vscode/launch.json
  2. 924
      lib/controller/message/chat_controller.dart
  3. 360
      lib/controller/message/conversation_controller.dart
  4. 2
      lib/controller/mine/edit_info_controller.dart
  5. 13
      lib/controller/setting/setting_controller.dart
  6. 325
      lib/im/im_manager.dart
  7. 2
      lib/network/home_api.g.dart
  8. 24
      lib/network/rtc_api.g.dart
  9. 64
      lib/network/user_api.g.dart
  10. 1
      lib/pages/home/content_card.dart
  11. 39
      lib/pages/home/user_information_page.dart
  12. 44
      lib/pages/message/chat_page.dart
  13. 8
      lib/pages/message/conversation_tab.dart
  14. 115
      lib/widget/message/chat_gift_item.dart
  15. 184
      lib/widget/message/chat_gift_popup.dart
  16. 6
      lib/widget/message/chat_input_bar.dart
  17. 293
      lib/widget/message/gift_item.dart
  18. 39
      lib/widget/message/message_item.dart
  19. 68
      location_plugin/example/pubspec.lock
  20. 540
      pubspec.lock

65
.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"
}
]
}

924
lib/controller/message/chat_controller.dart
File diff suppressed because it is too large
View File

360
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<String, ExtendedUserInfo> _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<void> _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<void> 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<void> _extractUserInfoFromConversations(List<EMConversation> 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<String, dynamic>? 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<void> 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<EMUserInfo?> loadContact(String userId) async{
Future<ExtendedUserInfo?> 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<String, dynamic>? 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<String, dynamic>? 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. ExtendedUserInfoID
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;
}
}

2
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) {

13
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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
conversationController.clearConversations();
}
//
storage.erase();
//
GlobalData().logout();
}

325
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<bool> 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<bool> 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<EMMessage?> 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<EMMessage?> 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<EMUserInfo?> 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<String, dynamic>? 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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
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;

2
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);

24
lib/network/rtc_api.g.dart

@ -45,7 +45,7 @@ class _RtcApi implements RtcApi {
(json) => RtcChannelData.fromJson(json as Map<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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);

64
lib/network/user_api.g.dart

@ -46,7 +46,7 @@ class _UserApi implements UserApi {
(json) => LoginData.fromJson(json as Map<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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<String, dynamic>),
);
} 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);

1
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(

39
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或accountIdmiId
// 使userIdIM系统的用户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('跳转失败,请重试');
}
},
),

44
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<ChatPage> {
final ScrollController _scrollController = ScrollController();
bool _isLoadingMore = false;
late ChatController _controller;
//
final activeGift = ValueNotifier<int?>(null);
final giftNum = ValueNotifier<int>(1);
@override
void initState() {
@ -79,9 +85,43 @@ class _ChatPageState extends State<ChatPage> {
@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<ChatController>(
@ -194,6 +234,10 @@ class _ChatPageState extends State<ChatPage> {
// /
await controller.sendVideoMessage(filePath, duration);
},
onGiftTap: () {
//
_showGiftPopup();
},
//
// onVoiceCall: () async {
// //

8
lib/pages/message/conversation_tab.dart

@ -60,10 +60,10 @@ class _ConversationTabState extends State<ConversationTab>
//
Widget _buildConversationItem(EMConversation conversation) {
return FutureBuilder<EMUserInfo?>(
return FutureBuilder<ExtendedUserInfo?>(
future: controller.loadContact(conversation.id),
builder: (context, userSnapshot) {
final EMUserInfo? userInfo = userSnapshot.data;
final ExtendedUserInfo? userInfo = userSnapshot.data;
return FutureBuilder<EMMessage?>(
future: controller.lastMessage(conversation),
builder: (context, messageSnapshot) {
@ -103,7 +103,7 @@ class _ConversationTabState extends State<ConversationTab>
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<ConversationTab>
children: [
Expanded(
child: Text(
(userInfo?.nickName ?? '').isNotEmpty
(userInfo?.nickName?.isNotEmpty ?? false)
? userInfo!.nickName!
: conversation.id, // ID
style: const TextStyle(

115
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],
);
}
}
}

184
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<int?> activeGift;
final ValueNotifier<int> giftNum;
final List<GiftProductModel> giftList;
final void Function(int) changeActive;
final Future<void> Function(GiftProductModel, int) onSendGift;
@override
State<ChatGiftPopup> createState() => _ChatGiftPopupState();
}
class _ChatGiftPopupState extends State<ChatGiftPopup> {
@override
void initState() {
super.initState();
//
if (widget.giftList.isNotEmpty && widget.activeGift.value == null) {
widget.activeGift.value = 0;
}
}
//
Future<void> _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<int?>(
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<int>(
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),
),
),
),
),
],
);
},
),
],
),
);
}
}

6
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<ChatInputBar> {
// 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),
//

293
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<String, dynamic>? _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<String, dynamic>;
}
}
} 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<Color>(Colors.grey),
),
);
} else {
//
return SizedBox.shrink();
}
}
}

39
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<ChatController>();
controller.resendMessage(message);
} catch (e) {
print('重发消息失败: $e');
}
},
);
}
//
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;

68
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:

540
pubspec.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save