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.
394 lines
12 KiB
394 lines
12 KiB
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 'package:cached_network_image/cached_network_image.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, // 已拒绝
|
|
terminated
|
|
}
|
|
|
|
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,
|
|
});
|
|
|
|
/// 从自定义消息中解析通话信息
|
|
Map<String, dynamic>? _parseCallInfo() {
|
|
try {
|
|
if (message.body.type == MessageType.CUSTOM) {
|
|
final customBody = message.body as EMCustomMessageBody;
|
|
if (customBody.event == 'call' && customBody.params != null) {
|
|
// 将 Map<String, String> 转换为 Map<String, dynamic>
|
|
final params = customBody.params!;
|
|
Get.log('通话信息: $params');
|
|
return {
|
|
'callType': params['callType'] ?? 'voice',
|
|
'callStatus': params['callStatus'] ?? 'missed',
|
|
'callDuration': params['callDuration'] != null
|
|
? int.tryParse(params['callDuration']!)
|
|
: null,
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('解析通话信息失败: $e');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// 从消息中解析通话类型
|
|
CallType? _getCallType() {
|
|
final callInfo = _parseCallInfo();
|
|
Get.log('通话信息: $callInfo');
|
|
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;
|
|
} else if (statusStr == 'terminated') {
|
|
return CallStatus.terminated;
|
|
}
|
|
}
|
|
return CallStatus.missed; // 默认未接听
|
|
}
|
|
|
|
/// 获取通话时长(秒)
|
|
int? _getCallDuration() {
|
|
final callInfo = _parseCallInfo();
|
|
if (callInfo != null) {
|
|
Get.log('通话信息: ${callInfo['callDuration']}');
|
|
return int.tryParse(callInfo['callDuration'].toString());
|
|
}
|
|
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 {
|
|
// 根据通话类型显示不同的图标:视频通话显示视频图标,语音通话显示语音图标
|
|
if (callType == CallType.video) {
|
|
return Assets.imagesSendVideoCall;
|
|
} else {
|
|
return Assets.imagesSendCall;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 获取状态文本
|
|
String _getStatusText() {
|
|
final callType = _getCallType();
|
|
final callStatus = _getCallStatus();
|
|
final duration = _getCallDuration();
|
|
|
|
if (callStatus == CallStatus.calling && duration != null) {
|
|
// 通话中,显示时长
|
|
return '通话中';
|
|
} else if (callStatus == CallStatus.cancelled) {
|
|
return '已取消';
|
|
} else if (callStatus == CallStatus.rejected) {
|
|
return '已拒绝';
|
|
} else if(callStatus == CallStatus.terminated && duration != null){
|
|
return _formatDuration(duration);
|
|
}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,
|
|
color: isSentByMe ? Colors.white : Colors.orange,
|
|
),
|
|
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
|
|
? CachedNetworkImage(
|
|
imageUrl: avatarUrl,
|
|
width: 40.w,
|
|
height: 40.w,
|
|
fit: BoxFit.cover,
|
|
placeholder: (context, url) => Image.asset(
|
|
Assets.imagesUserAvatar,
|
|
width: 40.w,
|
|
height: 40.w,
|
|
fit: BoxFit.cover,
|
|
),
|
|
errorWidget: (context, url, error) => 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) {
|
|
// 发送中,不显示loading
|
|
return SizedBox.shrink();
|
|
} else {
|
|
// 发送成功,不显示任何状态
|
|
return SizedBox.shrink();
|
|
}
|
|
}
|
|
}
|
|
|