You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

381 lines
11 KiB

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
import 'package:get/get.dart';
import '../../generated/assets.dart';
import '../../controller/global.dart';
import '../../controller/message/chat_controller.dart';
/// 通话类型
enum CallType {
voice, // 语音通话
video, // 视频通话
}
/// 通话状态
enum CallStatus {
calling, // 通话中(显示时长)
missed, // 未接听
cancelled, // 已取消
rejected, // 已拒绝
}
class CallItem extends StatelessWidget {
final EMMessage message;
final bool isSentByMe;
final bool showTime;
final String formattedTime;
final VoidCallback? onResend;
const CallItem({
required this.message,
required this.isSentByMe,
required this.showTime,
required this.formattedTime,
this.onResend,
super.key,
});
/// 从消息内容中解析通话信息(使用特殊的JSON格式)
Map<String, dynamic>? _parseCallInfo() {
try {
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
// 检查是否是通话消息(以 [CALL:] 开头)
if (content.startsWith('[CALL:]')) {
final jsonStr = content.substring(7); // 移除 '[CALL:]' 前缀
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
}
} catch (e) {
print('解析通话信息失败: $e');
}
return null;
}
/// 从消息中解析通话类型
CallType? _getCallType() {
final callInfo = _parseCallInfo();
if (callInfo != null) {
final callTypeStr = callInfo['callType'] as String?;
if (callTypeStr == 'voice') {
return CallType.voice;
} else if (callTypeStr == 'video') {
return CallType.video;
}
}
return null;
}
/// 从消息中解析通话状态
CallStatus? _getCallStatus() {
final callInfo = _parseCallInfo();
if (callInfo != null) {
final statusStr = callInfo['callStatus'] as String?;
if (statusStr == 'calling') {
return CallStatus.calling;
} else if (statusStr == 'missed') {
return CallStatus.missed;
} else if (statusStr == 'cancelled') {
return CallStatus.cancelled;
} else if (statusStr == 'rejected') {
return CallStatus.rejected;
}
}
return CallStatus.missed; // 默认未接听
}
/// 获取通话时长(秒)
int? _getCallDuration() {
final callInfo = _parseCallInfo();
if (callInfo != null) {
return callInfo['callDuration'] as int?;
}
return null;
}
/// 格式化通话时长
String _formatDuration(int seconds) {
final minutes = seconds ~/ 60;
final secs = seconds % 60;
if (minutes > 0) {
return '${minutes}:${secs.toString().padLeft(2, '0')}';
} else {
return '${secs}';
}
}
/// 获取图标资源
String _getIconAsset() {
final callType = _getCallType();
final callStatus = _getCallStatus();
if (isSentByMe) {
// 自己发起的通话
if (callType == CallType.video) {
return Assets.imagesSendVideoCall;
} else {
return Assets.imagesSendCall;
}
} else {
// 别人发起的通话
if (callStatus == CallStatus.rejected) {
return Assets.imagesRejectCall;
} else {
return Assets.imagesAcceptCall;
}
}
}
/// 获取状态文本
String _getStatusText() {
final callType = _getCallType();
final callStatus = _getCallStatus();
final duration = _getCallDuration();
if (callStatus == CallStatus.calling && duration != null) {
// 通话中,显示时长
return _formatDuration(duration);
} else if (callStatus == CallStatus.missed) {
return callType == CallType.video ? '未接听视频通话' : '未接听语音通话';
} else if (callStatus == CallStatus.cancelled) {
return callType == CallType.video ? '已取消视频通话' : '已取消语音通话';
} else if (callStatus == CallStatus.rejected) {
return callType == CallType.video ? '已拒绝视频通话' : '已拒绝语音通话';
} else {
return callType == CallType.video ? '视频通话' : '语音通话';
}
}
@override
Widget build(BuildContext context) {
final callType = _getCallType();
if (callType == null) {
// 如果解析失败,显示默认文本消息
return SizedBox.shrink();
}
final iconAsset = _getIconAsset();
final statusText = _getStatusText();
final callStatus = _getCallStatus();
final isCalling = callStatus == CallStatus.calling;
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.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
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: [
// 图标
Image.asset(
iconAsset,
width: 24.w,
height: 24.w,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
// 状态文本
Flexible(
child: Text(
statusText,
style: TextStyle(
fontSize: 14.sp,
color: isSentByMe ? Colors.white : Colors.black87,
fontWeight: isCalling ? FontWeight.w500 : FontWeight.normal,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
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() {
String? avatarUrl;
if (isSentByMe) {
// 发送的消息:使用当前登录用户的头像
// 优先从消息 attributes 中获取
try {
final attributes = message.attributes;
if (attributes != null) {
avatarUrl = attributes['sender_avatarUrl'] as String? ??
attributes['avatarUrl'] as String?;
}
} catch (e) {
// 忽略错误
}
// 如果消息中没有,使用当前登录用户的头像
if (avatarUrl == null || avatarUrl.isEmpty) {
avatarUrl = GlobalData().userData?.profilePhoto;
}
} else {
// 接收的消息:使用发送者的头像
try {
final attributes = message.attributes;
if (attributes != null) {
avatarUrl = attributes['sender_avatarUrl'] as String? ??
attributes['avatarUrl'] as String?;
}
} catch (e) {
// 忽略错误
}
// 如果消息中没有,尝试从 ChatController 获取对方用户头像
if ((avatarUrl == null || avatarUrl.isEmpty)) {
try {
// 尝试从 Get 获取 ChatController
final chatController = Get.find<ChatController>();
avatarUrl = chatController.userAvatarUrl;
} catch (e) {
// ChatController 可能不存在,忽略错误
}
}
}
// 清理头像URL(移除反引号)
if (avatarUrl != null && avatarUrl.isNotEmpty) {
avatarUrl = avatarUrl.trim().replaceAll('`', '');
}
return ClipOval(
child: avatarUrl != null && avatarUrl.isNotEmpty
? Image.network(
avatarUrl,
width: 40.w,
height: 40.w,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
Assets.imagesUserAvatar,
width: 40.w,
height: 40.w,
fit: BoxFit.cover,
);
},
)
: Image.asset(
Assets.imagesUserAvatar,
width: 40.w,
height: 40.w,
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: 44.w,
height: 26.h,
decoration: BoxDecoration(
color: Color.fromRGBO(248, 85, 66, 1),
borderRadius: BorderRadius.circular(8.w),
),
child: Center(
child: Text(
'重发',
style: TextStyle(
fontSize: 12.sp,
color: Colors.white,
),
),
),
),
);
} 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();
}
}
}