import 'dart:convert'; import 'dart:math' as math; import 'package:extended_text/extended_text.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/shared/tools/sc_room_utils.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/business_logic/models/res/gift_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/mic_res.dart'; import 'package:yumi/shared/business_logic/usecases/sc_case.dart'; import 'package:yumi/modules/index/main_route.dart'; import 'package:yumi/ui_kit/widgets/sc_nine_patch_image.dart'; import 'package:yumi/ui_kit/widgets/badge/sc_user_badge_strip.dart'; import 'package:yumi/ui_kit/widgets/room/room_emoji_asset_image.dart'; import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_chat_message.dart'; import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_models.dart'; import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart'; import '../../../shared/data_sources/models/enum/sc_activity_reward_props_type.dart'; import '../../../shared/data_sources/models/enum/sc_room_roles_type.dart'; import '../../components/sc_compontent.dart'; import '../../components/sc_tts.dart'; import '../../components/text/sc_text.dart'; const EdgeInsets _vipBubbleCenterSliceRatio = EdgeInsets.fromLTRB( 0.34, 0.34, 0.34, 0.34, ); const EdgeInsets _vipBubbleSourceEdgeInset = EdgeInsets.all(2); ///消息item class MsgItem extends StatefulWidget { final Function(SocialChatUserProfile? user) onClick; final Msg msg; const MsgItem({super.key, required this.msg, required this.onClick}); @override State createState() => _MsgItemState(); } class _MsgItemState extends State { static const Duration _userInfoFailureRetryInterval = Duration(minutes: 5); static final Map> _userInfoFutures = {}; static final Map _userInfoFailedAt = {}; @override Widget build(BuildContext context) { //gift // if (msg.type == Roomtype.gift.code) { // return _buildGiftMsg(context); // } ///系统提示 if (widget.msg.type == SCRoomMsgType.systemTips) { return _systemTipsRoomItem(context); } ///进入房间 if (widget.msg.type == SCRoomMsgType.joinRoom) { return _enterRoomItem(context); } ///礼物 if (widget.msg.type == SCRoomMsgType.gift || widget.msg.type == SCRoomMsgType.luckGiftAnimOther) { return _buildGiftMsg(context); } ///火箭用户中奖 if (SCRoomMsgType.isRocketRewardUserType(widget.msg.type)) { return _buildRocketRewardMsg(context); } ///火箭燃料加满倒计时 if (widget.msg.type == SCRoomMsgType.rocketLaunchNotice) { return _buildRocketLaunchNoticeMsg(context); } ///幸运礼物 if (widget.msg.type == SCRoomMsgType.gameLuckyGift) { return _buildGameLuckyGiftMsg(context); } ///幸运礼物 if (widget.msg.type == SCRoomMsgType.gameLuckyGift_5) { return _buildGameLuckyGiftMsg_5(context); } ///房间身份变动 if (widget.msg.type == SCRoomMsgType.roomRoleChange) { return _buildRoleChangeMsg(context); } ///踢出房间 if (widget.msg.type == SCRoomMsgType.qcfj) { widget.msg.msg = SCAppLocalizations.of(context)!.kickRoomTips; } ///图片消息 if (widget.msg.type == SCRoomMsgType.image) { return _buildImageMsg(context); } ///表情包聊天消息 if (widget.msg.type == SCRoomMsgType.emoticons) { return _buildEmoticonMsg(context); } ///掷骰子 if (widget.msg.type == SCRoomMsgType.roomDice) { return _buildDiceMsg(context); } ///石头剪刀布 if (widget.msg.type == SCRoomMsgType.roomRPS) { return _buildRPSMsg(context); } ///幸运数字 if (widget.msg.type == SCRoomMsgType.roomLuckNumber) { return _buildLuckNumberMsg(context); } ///房间红包 if (widget.msg.type == SCRoomMsgType.roomRedPacket) { return _buildRoomRedPacketMsg(context); } if (widget.msg.type == SCRoomMsgType.roomRedPacketClaim) { return _buildRoomRedPacketClaimMsg(context); } if (widget.msg.user != null) { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( // 使用Flexible替代Expanded child: GestureDetector( child: _buildTextMessageBubble(), onLongPress: () { if (!(widget.msg.msg ?? "").startsWith(AtText.flag)) { Clipboard.setData( ClipboardData(text: widget.msg.msg ?? ''), ); SCTts.show("Copied"); } }, ), ), ], ), ], ), ), ); } else { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(8.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.all(Radius.circular(15.w)), ), child: Text( widget.msg.msg ?? '', style: TextStyle( fontSize: sp(13), color: Color(_msgColor(widget.msg.type ?? "")), fontWeight: FontWeight.w500, ), textAlign: TextAlign.left, ), ), ); } } Widget _buildTextMessageBubble() { final chatBubble = widget.msg.user?.getChatBox(); final hasChatBubble = _resolveRoomChatBubbleImageUrl(chatBubble).isNotEmpty; final bubbleStartMargin = 54.w; return LayoutBuilder( builder: (context, constraints) { final rowMaxWidth = constraints.maxWidth.isFinite ? constraints.maxWidth : ScreenUtil().screenWidth; final availableBubbleWidth = math.max( 1.0, rowMaxWidth - bubbleStartMargin, ); if (hasChatBubble) { return _buildVipTextMessageBubble( chatBubble, maxBubbleWidth: availableBubbleWidth, startMargin: bubbleStartMargin, ); } return Container( constraints: BoxConstraints( maxWidth: math.min( ScreenUtil().screenWidth * 0.7, availableBubbleWidth, ), ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), margin: EdgeInsetsDirectional.only( start: bubbleStartMargin, ).copyWith(bottom: 8.w), child: ExtendedText( widget.msg.msg ?? '', style: TextStyle( fontSize: sp(13), color: Color(_msgColor(widget.msg.type ?? "")), fontWeight: FontWeight.w500, ), softWrap: true, specialTextSpanBuilder: AtTextSpanBuilder(onTapCall: (userId) {}), ), ); }, ); } Widget _buildRoomRedPacketMsg(BuildContext context) { final data = RoomRedPacketUiData.fromMessagePayload( payload: widget.msg.msg, fallbackUser: widget.msg.user, ); return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.msg.user != null) ...[ _buildUserInfoLine(context), SizedBox(height: 6.w), ], RoomRedPacketChatMessage(data: data), ], ), ), ); } Widget _buildRoomRedPacketClaimMsg(BuildContext context) { final localizations = SCAppLocalizations.of(context)!; final packetSenderName = (widget.msg.msg ?? '').trim().isNotEmpty ? (widget.msg.msg ?? '').trim() : localizations.redEnvelope; final message = localizations.roomRedPacketReceivedMessage( _formatAwardAmount(widget.msg.number), packetSenderName, ); return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.msg.user != null) ...[ _buildUserInfoLine(context), SizedBox(height: 6.w), ], Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.7, ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: Text( message, style: TextStyle( fontSize: sp(13), color: Color(_msgColor(widget.msg.type ?? "")), fontWeight: FontWeight.w500, ), textAlign: TextAlign.start, ), ), ], ), ), ); } Widget _buildVipTextMessageBubble( PropsResources? resource, { required double maxBubbleWidth, required double startMargin, }) { final messageText = widget.msg.msg ?? ""; final textStyle = TextStyle( fontSize: sp(13), color: Color(_msgColor(widget.msg.type ?? "")), fontWeight: FontWeight.w500, ); final imageUrl = _resolveRoomChatBubbleImageUrl(resource); final multilineMaxBubbleWidth = math.min(maxBubbleWidth, 300.w); final multilineMaxTextWidth = math.max( 82.w, multilineMaxBubbleWidth - 64.w, ); final multilineTextPainter = TextPainter( text: TextSpan(text: messageText, style: textStyle), textDirection: Directionality.of(context), )..layout(maxWidth: multilineMaxTextWidth); if (multilineTextPainter.computeLineMetrics().length > 1) { return _buildMultilineVipTextMessageBubble( messageText: messageText, textStyle: textStyle, imageUrl: imageUrl, textSize: multilineTextPainter.size, maxBubbleWidth: multilineMaxBubbleWidth, startMargin: startMargin, ); } return _buildSingleLineVipTextMessageBubble( messageText: messageText, textStyle: textStyle, imageUrl: imageUrl, maxBubbleWidth: maxBubbleWidth, startMargin: startMargin, ); } Widget _buildMultilineVipTextMessageBubble({ required String messageText, required TextStyle textStyle, required String imageUrl, required Size textSize, required double maxBubbleWidth, required double startMargin, }) { final leftPadding = 22.w; final rightPadding = 42.w; final topPadding = 16.w; final bottomPadding = 24.w; final bubbleWidth = math .max(126.w, textSize.width + leftPadding + rightPadding) .clamp(126.w, maxBubbleWidth); final textMaxWidth = math.max( 64.w, bubbleWidth - leftPadding - rightPadding, ); final textPainter = TextPainter( text: TextSpan(text: messageText, style: textStyle), textDirection: Directionality.of(context), )..layout(maxWidth: textMaxWidth); final textBoxHeight = textPainter.size.height + 2.w; final bubbleHeight = math.max( 76.w, textBoxHeight + topPadding + bottomPadding, ); return Container( width: bubbleWidth, height: bubbleHeight, margin: EdgeInsetsDirectional.only( start: startMargin, ).copyWith(bottom: 8.w), child: Stack( fit: StackFit.expand, children: [ _buildVipBubbleBackground( url: imageUrl, width: bubbleWidth, height: bubbleHeight, fallback: SizedBox(width: bubbleWidth, height: bubbleHeight), ), PositionedDirectional( start: leftPadding, top: topPadding, width: textMaxWidth, child: ExtendedText( messageText, style: textStyle, softWrap: true, specialTextSpanBuilder: AtTextSpanBuilder(onTapCall: (userId) {}), ), ), ], ), ); } Widget _buildSingleLineVipTextMessageBubble({ required String messageText, required TextStyle textStyle, required String imageUrl, required double maxBubbleWidth, required double startMargin, }) { final leftPadding = 22.w; final rightPadding = 24.w; final topPadding = 12.w; final bottomPadding = 16.w; final minSkinWidth = 96.w; final minSkinHeight = 58.w; final textWidthSafety = 10.w; final maxTextWidth = math.max( 1.0, maxBubbleWidth - leftPadding - rightPadding, ); final textSize = _measureMessageTextSize( text: messageText, style: textStyle, maxWidth: maxTextWidth, ); final bubbleWidth = math.min( maxBubbleWidth, math.max( minSkinWidth, textSize.width + textWidthSafety + leftPadding + rightPadding, ), ); final textMaxWidth = math.max( 1.0, bubbleWidth - leftPadding - rightPadding, ); final bubbleHeight = math.max( minSkinHeight, textSize.height + topPadding + bottomPadding, ); return Container( width: bubbleWidth, height: bubbleHeight, margin: EdgeInsetsDirectional.only( start: startMargin, ).copyWith(bottom: 8.w), child: Stack( clipBehavior: Clip.none, fit: StackFit.expand, children: [ Positioned.fill( child: _buildVipBubbleSkin( url: imageUrl, width: math.max(bubbleWidth, minSkinWidth), height: math.max(bubbleHeight, minSkinHeight), alignment: AlignmentDirectional.centerStart, ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( leftPadding, topPadding, rightPadding, bottomPadding, ), child: Align( alignment: AlignmentDirectional.centerStart, child: SizedBox( width: textMaxWidth, child: ExtendedText( messageText, style: textStyle, maxLines: 1, softWrap: false, specialTextSpanBuilder: AtTextSpanBuilder( onTapCall: (userId) {}, ), ), ), ), ), ], ), ); } Widget _buildVipBubbleSkin({ required String url, required double width, required double height, required AlignmentGeometry alignment, }) { return OverflowBox( alignment: alignment, minWidth: width, maxWidth: width, minHeight: height, maxHeight: height, child: SCNetworkSvgaWidget.isSvga(url) ? SCNetworkSvgaWidget( resource: url, width: width, height: height, fit: BoxFit.fill, loop: true, centerSliceRatio: _vipBubbleCenterSliceRatio, sourceEdgeInset: _vipBubbleSourceEdgeInset, fallback: SizedBox(width: width, height: height), ) : _buildStaticVipBubbleImage( url: url, width: width, height: height, fallback: SizedBox(width: width, height: height), ), ); } Widget _buildVipBubbleBackground({ required String url, required double width, required double height, required Widget fallback, }) { if (SCNetworkSvgaWidget.isSvga(url)) { return SCNetworkSvgaWidget( resource: url, width: width, height: height, fit: BoxFit.fill, loop: true, centerSliceRatio: _vipBubbleCenterSliceRatio, sourceEdgeInset: _vipBubbleSourceEdgeInset, fallback: fallback, ); } return _buildStaticVipBubbleImage( url: url, width: width, height: height, fallback: fallback, ); } Widget _buildStaticVipBubbleImage({ required String url, required double width, required double height, Widget? fallback, }) { final imageUrl = url.trim(); if (imageUrl.isEmpty) { return fallback ?? SizedBox(width: width, height: height); } return SCNinePatchImage( url: imageUrl, width: width, height: height, centerSliceRatio: _vipBubbleCenterSliceRatio, sourceEdgeInset: _vipBubbleSourceEdgeInset, fallback: fallback ?? SizedBox(width: width, height: height), ); } Size _measureMessageTextSize({ required String text, required TextStyle style, required double maxWidth, }) { final textPainter = TextPainter( text: TextSpan(text: text, style: style), textDirection: Directionality.of(context), )..layout(maxWidth: maxWidth); return textPainter.size; } String _resolveRoomChatBubbleImageUrl(PropsResources? resource) { final candidates = [ resource?.roomSendCoverUrl, resource?.roomOpenedUrl, resource?.roomNotOpenedUrl, resource?.imSendCoverUrl, resource?.imOpenedUrl, resource?.imOpenedUrlTwo, resource?.imNotOpenedUrl, resource?.cover, resource?.expand, resource?.sourceUrl, ]; return _firstNonBlank([ ...candidates.where((value) => SCNetworkSvgaWidget.isSvga(value)), ...candidates, ]); } String _firstNonBlank(Iterable values) { for (final value in values) { final text = value?.trim(); if (text != null && text.isNotEmpty) { return text; } } return ""; } ///系统提示 _systemTipsRoomItem(BuildContext context) { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(8.w), vertical: width(8), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 8.w), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.all(Radius.circular(15.w)), ), child: Text( widget.msg.msg ?? '', style: TextStyle( fontSize: sp(13), height: 1.5.w, color: Color(_msgColor(widget.msg.type ?? "")), fontWeight: FontWeight.w500, ), textAlign: TextAlign.left, ), ), ); } ///火箭用户中奖 _buildRocketRewardMsg(BuildContext context) { List spans = []; final nickname = Msg._firstNonBlankString([ widget.msg.user?.userNickname, widget.msg.user?.account, widget.msg.user?.id, SCAppLocalizations.of(context)?.member, 'Member', ]); spans.add( TextSpan( text: "$nickname ", style: TextStyle( fontSize: sp(12), color: SocialChatTheme.primaryColor, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( TextSpan( text: SCAppLocalizations.of(context)!.inRocket, style: TextStyle( fontSize: sp(12), color: Colors.white, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( WidgetSpan(alignment: PlaceholderAlignment.middle, child: _rocketIcon()), ); spans.add( TextSpan( text: SCAppLocalizations.of(context)!.obtain, style: TextStyle( fontSize: sp(12), color: Colors.white, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: widget.msg.role == SCActivityRewardATPropsType.GOLD.name ? Image.asset("sc_images/general/sc_icon_jb.png", width: 18.w) : netImage(url: widget.msg.msg ?? "", width: 18.w), ), ); Widget text = Text.rich( TextSpan(children: spans), textAlign: TextAlign.left, strutStyle: StrutStyle( height: 1.8, // 行高倍数 fontWeight: FontWeight.w500, forceStrutHeight: true, // 强制应用行高 ), ); return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), margin: EdgeInsets.only(left: 34.w).copyWith(bottom: 8.w), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.all(Radius.circular(15.w)), ), child: text, ), ); } Widget _rocketIcon() { final iconUrl = widget.msg.rocketIconUrl?.trim() ?? ''; if (iconUrl.isEmpty) { return SizedBox(width: 4.w); } return netImage(url: iconUrl, width: 18.w); } Widget _buildRocketLaunchNoticeMsg(BuildContext context) { final seconds = (widget.msg.number ?? num.tryParse(widget.msg.msg ?? '') ?? 10).toInt(); final template = SCAppLocalizations.of( context, )?.roomRocketFuelFullNotice(seconds.toString()) ?? 'Rocket fuel is full! Launching in ${seconds}s!'; final iconUrl = widget.msg.rocketIconUrl?.trim() ?? ''; return Container( alignment: AlignmentDirectional.topStart, child: Container( constraints: BoxConstraints(minHeight: 42.w, maxWidth: 304.w), padding: EdgeInsetsDirectional.only( start: 12.w, end: 10.w, top: 8.w, bottom: 8.w, ), margin: EdgeInsetsDirectional.only(start: 10.w, end: 36.w, bottom: 8.w), decoration: BoxDecoration( color: const Color(0xFFF1F2FF).withValues(alpha: 0.92), borderRadius: BorderRadius.circular(9.w), border: Border.all(color: const Color(0xFF6672FF), width: 1.w), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( child: Text( template, textScaler: TextScaler.noScaling, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( color: const Color(0xFF6672FF), fontSize: sp(13), height: 1.2, fontWeight: FontWeight.w600, ), ), ), SizedBox(width: 8.w), SizedBox( width: 42.w, height: 28.w, child: iconUrl.isEmpty ? Icon( Icons.rocket_launch_rounded, color: const Color(0xFFB8BEFA), size: 28.w, ) : netImage( url: iconUrl, width: 42.w, height: 28.w, fit: BoxFit.contain, noDefaultImg: true, ), ), ], ), ), ); } ///幸运礼物 _buildGameLuckyGiftMsg(BuildContext context) { List spans = []; spans.add( TextSpan( text: "${widget.msg.user?.userNickname} ", style: TextStyle( fontSize: sp(12), color: SocialChatTheme.primaryColor, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer() ..onTap = () { widget.onClick(widget.msg.user); }, ), ); spans.add( TextSpan( text: SCAppLocalizations.of(context)!.sendTo, style: TextStyle( fontSize: sp(12), color: Colors.white, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( TextSpan( text: " ${widget.msg.toUser?.userNickname} ", style: TextStyle( fontSize: sp(12), color: SocialChatTheme.primaryColor, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer() ..onTap = () { widget.onClick(widget.msg.toUser); }, ), ); SCGlobalConfig.lang == "ar" ? spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: Image.asset("sc_images/general/sc_icon_jb.png", width: 18.w), ), ) : spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: netImage(url: widget.msg.gift?.giftPhoto ?? "", width: 18.w), ), ); spans.add( TextSpan( text: " ${SCAppLocalizations.of(context)!.obtain}", style: TextStyle( fontSize: sp(12), color: SocialChatTheme.primaryColor, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( TextSpan( text: " ${_formatAwardAmount(widget.msg.awardAmount)}", style: TextStyle( fontSize: sp(12), color: SocialChatTheme.primaryColor, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); SCGlobalConfig.lang == "ar" ? spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: netImage(url: widget.msg.gift?.giftPhoto ?? "", width: 18.w), ), ) : spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: Image.asset("sc_images/general/sc_icon_jb.png", width: 18.w), ), ); Widget text = Text.rich( TextSpan(children: spans), textAlign: TextAlign.left, strutStyle: StrutStyle( height: 1.3, // 行高倍数 fontWeight: FontWeight.w500, forceStrutHeight: true, // 强制应用行高 ), ); return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), margin: EdgeInsets.only(left: 34.w).copyWith(bottom: 8.w), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.all(Radius.circular(15.w)), ), child: text, ), ); } ///幸运礼物中奖5倍以及以上 _buildGameLuckyGiftMsg_5(BuildContext context) { List spans = []; spans.add( TextSpan( text: "${widget.msg.user?.userNickname} ", style: TextStyle( fontSize: sp(12), color: Color(0xffFEF129), fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer() ..onTap = () { widget.onClick(widget.msg.user); }, ), ); spans.add( TextSpan( text: SCAppLocalizations.of(context)!.get, style: TextStyle( fontSize: sp(12), color: Colors.white, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( TextSpan( text: " ${_formatAwardAmount(widget.msg.awardAmount)} ", style: TextStyle( fontSize: sp(12), color: Color(0xffFEF129), fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: Image.asset( "sc_images/general/sc_icon_jb.png", width: 18.w, height: 18.w, ), ), ); spans.add( TextSpan( text: " from ", style: TextStyle( fontSize: sp(12), color: Colors.white, fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: netImage( url: widget.msg.gift?.giftPhoto ?? "", width: 18.w, height: 18.w, ), ), ); Widget text = Text.rich( TextSpan(children: spans), textAlign: TextAlign.left, strutStyle: StrutStyle( height: 1.2, // 行高倍数 fontWeight: FontWeight.w500, forceStrutHeight: true, // 强制应用行高 ), ); return Container( alignment: AlignmentDirectional.topStart, child: Container( constraints: BoxConstraints(minHeight: 68.w), padding: EdgeInsets.symmetric(vertical: 5.w), margin: EdgeInsets.only(left: 34.w).copyWith(bottom: 8.w), decoration: BoxDecoration( image: DecorationImage( image: AssetImage("sc_images/room/sc_icon_luck_gift_msg_n_bg.png"), fit: BoxFit.fill, ), ), child: Row( children: [ SizedBox(width: 10.w), Expanded(child: text), Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage( "sc_images/room/sc_icon_luck_gift_msg_n_ball.png", ), fit: BoxFit.fill, ), ), alignment: AlignmentDirectional.center, width: 55.w, height: 60.w, child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 10.w), buildNumForGame(widget.msg.msg ?? "0", size: 18.w), SizedBox(height: 3.w), SCGlobalConfig.lang == "ar" ? Image.asset( "sc_images/room/sc_icon_times_text_ar.png", height: 12.w, ) : Image.asset( "sc_images/room/sc_icon_times_text_en.png", height: 10.w, ), ], ), ), SizedBox(width: 15.w), ], ), ), ); } String _formatAwardAmount(num? awardAmount) { if (awardAmount == null) { return "0"; } if (awardAmount % 1 == 0) { return awardAmount.toInt().toString(); } return awardAmount.toString(); } Widget _buildGiftMsg(BuildContext context) { return Container( alignment: AlignmentDirectional.topStart, padding: EdgeInsets.symmetric(horizontal: width(5.w), vertical: width(4)), margin: EdgeInsets.symmetric(horizontal: width(10)).copyWith(bottom: 8.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( // 使用Flexible替代Expanded child: Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.7, // 最大宽度约束 ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 4.w, ), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: Text.rich( TextSpan( children: [ TextSpan( text: SCAppLocalizations.of(context)!.sendTo, style: TextStyle( fontSize: 13.sp, color: Colors.white, ), ), TextSpan(text: " "), TextSpan( text: _giftTargetName(context), style: TextStyle( fontSize: 13.sp, color: SocialChatTheme.primaryColor, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), TextSpan(text: " "), WidgetSpan( child: netImage( url: widget.msg.gift?.giftPhoto ?? "", width: 22.w, height: 22.w, ), ), TextSpan(text: " "), TextSpan( text: "x${widget.msg.number}", style: TextStyle( fontSize: 13.sp, color: Colors.white, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ], ), textAlign: TextAlign.left, ), ), ), ], ), ], ), ); } String _giftTargetName(BuildContext context) { final targetUserIds = widget.msg.targetUserIds ?.map((id) => id.trim()) .where((id) => id.isNotEmpty) .toSet(); if (targetUserIds != null && targetUserIds.length > 1) { return SCAppLocalizations.of(context)!.multiple; } return widget.msg.toUser?.userNickname ?? ""; } ///进入房间 _enterRoomItem(BuildContext context) { List spans = []; if (widget.msg.role != null && widget.msg.role != "0") { spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, child: Padding( padding: EdgeInsets.only(right: 3.w), child: msgRoleTag(widget.msg.role ?? "", width: 18.w), ), ), ); } spans.add( TextSpan( text: "${widget.msg.user?.userNickname ?? ''} ", style: TextStyle( fontSize: sp(13), color: _roleNameColor(widget.msg.role ?? ""), fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); spans.add( TextSpan( text: SCAppLocalizations.of(context)!.joinRoomTips, style: TextStyle( fontSize: sp(13), color: Color(_msgColor(widget.msg.type ?? "")), fontWeight: FontWeight.w500, ), ), ); Widget text = Text.rich( TextSpan(children: spans), textAlign: TextAlign.left, ); return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(8.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 8.w), child: text, ), ); } Widget _buildUserNameAndLevels(SocialChatUserProfile? user) { return SizedBox( height: 23.w, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Flexible( fit: FlexFit.loose, child: socialchatNickNameText( user?.userNickname ?? "", maxWidth: 112.w, fontWeight: FontWeight.w500, fontSize: 13.sp, type: user?.getVIP()?.name ?? "", needScroll: (user?.userNickname?.characters.length ?? 0) > 10, ), ), SizedBox(width: 4.w), getWealthLevel( user?.wealthLevel ?? 0, width: 42.w, height: 20.w, fontSize: 9.sp, ), SizedBox(width: 2.w), getUserLevel( user?.charmLevel ?? 0, width: 42.w, height: 20.w, fontSize: 9.sp, ), ], ), ); } List _activeWearBadges(SocialChatUserProfile? user) { return user?.wearBadge?.where((item) => item.use ?? false).toList() ?? const []; } Widget _buildMedals({ required String? userId, List fallbackBadges = const [], }) { final hasUserId = (userId ?? "").trim().isNotEmpty; if (!hasUserId && fallbackBadges.isEmpty) { return SizedBox(height: 16.w); } return SizedBox( width: double.infinity, child: SCUserBadgeStrip( userId: userId, fallbackBadges: fallbackBadges, height: 23.w, badgeHeight: 21.w, longBadgeWidth: 51.w, spacing: 4.5.w, maxBadges: 8, reserveSpace: true, ), ); } int _msgColor(String type) { switch (type) { case SCRoomMsgType.joinRoom: case SCRoomMsgType.systemTips: case SCRoomMsgType.welcome: return 0xFFFFFFFF; case SCRoomMsgType.gift: return 0xFFFFFFFF; case SCRoomMsgType.shangMai: case SCRoomMsgType.xiaMai: case SCRoomMsgType.qcfj: case SCRoomMsgType.killXiaMai: return 0xFFFFFFFF; // case type.text: // return _mapTextColor(); // break; default: return 0xFFFFFFFF; } } _roleNameColor(String role) { if (SCRoomRolesType.HOMEOWNER.name == role) { return Color(0xff2AC0F2); } else if (SCRoomRolesType.ADMIN.name == role) { return Color(0xffF9A024); } else if (SCRoomRolesType.MEMBER.name == role) { return Color(0xff28ED77); } else { return Colors.white; } } Widget _buildRoleChangeMsg(BuildContext context) { var text = ""; if (SCRoomRolesType.ADMIN.name == widget.msg.msg) { text = SCAppLocalizations.of(context)!.adminByHomeowner; } else if (SCRoomRolesType.MEMBER.name == widget.msg.msg) { text = SCAppLocalizations.of(context)!.memberByHomeowner; } else { text = SCAppLocalizations.of(context)!.touristByHomeowner; } List spans = []; if (widget.msg.toUser != null) { spans.add( TextSpan( text: "${widget.msg.toUser?.userNickname ?? ''} ", style: TextStyle( fontSize: sp(13), color: Color(0xff2AC0F2), fontWeight: FontWeight.w500, ), recognizer: TapGestureRecognizer()..onTap = () {}, ), ); } spans.add( TextSpan( text: text, style: TextStyle( fontSize: sp(13), color: Colors.white, fontWeight: FontWeight.w500, ), ), ); Widget textView = Text.rich( TextSpan(children: spans), textAlign: TextAlign.left, ); return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(8.w), vertical: width(8), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 8.w), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.all(Radius.circular(15.w)), ), child: textView, ), ); } Widget _buildImageMsg(BuildContext context) { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( // 使用Flexible替代Expanded child: GestureDetector( child: Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.5, // 最大宽度约束 ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 4.w, ), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: netImage(url: widget.msg.msg ?? ""), ), onTap: () { String encodedUrls = Uri.encodeComponent( jsonEncode([widget.msg.msg]), ); SCNavigatorUtils.push( context, "${SCMainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", ); }, ), ), ], ), ], ), ), ); } Widget _buildEmoticonMsg(BuildContext context) { final emojiPath = widget.msg.msg ?? ""; return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( child: Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.45, ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 8.w, ), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: _buildEmojiImage( emojiPath, width: 72.w, height: 72.w, ), ), ), ], ), ], ), ), ); } Widget _buildEmojiImage( String path, { required double width, required double height, }) { if (path.isEmpty) { return SizedBox(width: width, height: height); } if (_isLocalAsset(path)) { return RoomEmojiAssetImage( key: ValueKey(path), asset: path, width: width, height: height, ); } return netImage(url: path, width: width, height: height); } bool _isLocalAsset(String path) { return path.startsWith("assets/") || path.startsWith("sc_images/"); } Widget _buildDiceMsg(BuildContext context) { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( // 使用Flexible替代Expanded child: GestureDetector( child: Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.5, // 最大宽度约束 ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 4.w, ), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: (widget.msg.hasPlayedAnimation ?? false) ? Image.asset( "sc_images/room/sc_icon_dice_${widget.msg.msg}.png", height: 35.w, ) : FutureBuilder( future: Future.delayed( Duration(milliseconds: 2000), ), // 传入Future builder: ( BuildContext context, AsyncSnapshot snapshot, ) { // 根据snapshot的状态来构建UI if (snapshot.connectionState == ConnectionState.done) { widget.msg.hasPlayedAnimation = true; return Image.asset( "sc_images/room/sc_icon_dice_${widget.msg.msg}.png", height: 35.w, ); } else { return Image.asset( "sc_images/room/sc_icon_dice_animl.webp", height: 35.w, ); } }, ), ), onTap: () {}, ), ), ], ), ], ), ), ); } Widget _buildRPSMsg(BuildContext context) { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( // 使用Flexible替代Expanded child: GestureDetector( child: Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.5, // 最大宽度约束 ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 4.w, ), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: (widget.msg.hasPlayedAnimation ?? false) ? Image.asset( "sc_images/room/sc_icon_rps_${widget.msg.msg}.png", height: 35.w, ) : FutureBuilder( future: Future.delayed( Duration(milliseconds: 2000), ), // 传入Future builder: ( BuildContext context, AsyncSnapshot snapshot, ) { // 根据snapshot的状态来构建UI if (snapshot.connectionState == ConnectionState.done) { widget.msg.hasPlayedAnimation = true; return Image.asset( "sc_images/room/sc_icon_rps_${widget.msg.msg}.png", height: 35.w, ); } else { return Image.asset( "sc_images/room/sc_icon_rps_animal.webp", height: 35.w, ); } }, ), ), onTap: () {}, ), ), ], ), ], ), ), ); } Widget _buildLuckNumberMsg(BuildContext context) { return Container( alignment: AlignmentDirectional.topStart, child: Container( padding: EdgeInsets.symmetric( horizontal: width(5.w), vertical: width(4), ), margin: EdgeInsets.symmetric( horizontal: width(10), ).copyWith(bottom: 6.w), child: Column( children: [ _buildUserInfoLine(context), SizedBox(height: 6.w), Row( children: [ Flexible( // 使用Flexible替代Expanded child: GestureDetector( child: Container( constraints: BoxConstraints( maxWidth: ScreenUtil().screenWidth * 0.7, // 最大宽度约束 ), decoration: BoxDecoration( color: Colors.black38, borderRadius: BorderRadius.only( topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w), ), ), padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 4.w, ), margin: EdgeInsetsDirectional.only( start: 54.w, ).copyWith(bottom: 8.w), child: Row( mainAxisSize: MainAxisSize.min, children: [ SCGlobalConfig.lang == "ar" ? Image.asset( "sc_images/room/sc_icon_luck_num_text_ar.png", height: 20.w, ) : Image.asset( "sc_images/room/sc_icon_luck_num_text_en.png", height: 20.w, ), buildNumForRoomLuckNum( widget.msg.msg ?? "", size: 20.w, ), ], ), ), onTap: () {}, ), ), ], ), ], ), ), ); } _buildUserInfoLine(BuildContext context) { final userId = widget.msg.user?.id; if ((widget.msg.needUpDataUserInfo) ?? false) { SCRoomUtils.roomUsersMap.remove(userId ?? ""); if ((userId ?? "").isNotEmpty) { _userInfoFutures.remove(userId); _userInfoFailedAt.remove(userId); } widget.msg.needUpDataUserInfo = false; } if (userId == null || userId.isEmpty) { return _buildPendingUserInfoLine(context); } return SCRoomUtils.roomUsersMap[userId] == null ? FutureBuilder( future: _loadRoomUserInfoSilently(userId), builder: (ct, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { SocialChatUserProfile user = snapshot.data!; SCRoomUtils.roomUsersMap[user.id ?? ""] = user; return _buildNewUserInfoLine(context, user); } } return _buildPendingUserInfoLine(context); }, ) : _buildNewUserInfoLine(context, SCRoomUtils.roomUsersMap[userId]!); } Future _loadRoomUserInfoSilently(String userId) { final failedAt = _userInfoFailedAt[userId]; if (failedAt != null && DateTime.now().difference(failedAt) < _userInfoFailureRetryInterval) { return Future.value(null); } return _userInfoFutures.putIfAbsent(userId, () async { try { final user = await SCAccountRepository().loadUserInfo( userId, silentErrorToast: true, ); SCRoomUtils.roomUsersMap[user.id ?? userId] = user; _userInfoFailedAt.remove(userId); return user; } catch (error) { _userInfoFailedAt[userId] = DateTime.now(); _userInfoFutures.remove(userId); return null; } }); } Widget _buildPendingUserInfoLine(BuildContext context) { return Row( children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { widget.onClick(widget.msg.user); }, child: Stack( alignment: AlignmentDirectional.bottomEnd, children: [ head( url: widget.msg.user?.userAvatar ?? "", width: 48.w, // headdress: msg.user?.getHeaddress()?.sourceUrl, ), Positioned( child: msgRoleTag(widget.msg.role ?? "", width: 14.w), bottom: 2.w, right: 2.w, ), ], ), ), SizedBox(width: 8.w), Expanded( child: SizedBox( height: 50.w, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildUserNameAndLevels(widget.msg.user), SizedBox(height: 2.w), _buildMedals( userId: widget.msg.user?.id, fallbackBadges: _activeWearBadges(widget.msg.user), ), ], ), ), ), ], ); } _buildNewUserInfoLine(BuildContext context, SocialChatUserProfile user) { return Row( children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { widget.onClick(user); }, child: Stack( alignment: AlignmentDirectional.bottomEnd, children: [ head( url: user.userAvatar ?? "", width: 48.w, // headdress: msg.user?.getHeaddress()?.sourceUrl, ), Positioned( child: msgRoleTag(widget.msg.role ?? "", width: 14.w), bottom: 2.w, right: 2.w, ), ], ), ), SizedBox(width: 8.w), Expanded( child: SizedBox( height: 50.w, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildUserNameAndLevels(user), SizedBox(height: 2.w), _buildMedals( userId: user.id, fallbackBadges: _activeWearBadges(user), ), ], ), ), ), ], ); } } ///消息 class Msg { String? groupId; String? msg; MicRes? userWheat; String? type; SocialChatUserProfile? user; String? role = ""; String? rocketIconUrl = ""; SocialChatGiftRes? gift; //礼物个数 num? number; // 自定义动画需要播放的次数 int? customAnimationCount; num? awardAmount; // 送礼给谁 SocialChatUserProfile? toUser; List? targetUserIds; String? giftBatchId; int? time = 0; //动画是否已经播放(骰子、石头剪刀布、随机数字) bool? hasPlayedAnimation = false; bool? needUpDataUserInfo = false; Msg({ required this.groupId, required this.msg, required this.type, this.user, this.toUser, this.userWheat, this.role = "", this.rocketIconUrl, this.gift, this.number, this.customAnimationCount, this.awardAmount, this.targetUserIds, this.giftBatchId, this.hasPlayedAnimation, this.needUpDataUserInfo, }) { time = DateTime.now().millisecondsSinceEpoch; } Msg.fromJson(dynamic json) { groupId = json['groupId']; msg = json['msg']; type = json['type']; role = json['role']; rocketIconUrl = json['rocketIconUrl']?.toString(); time = json['time']; number = json['number']; customAnimationCount = json['customAnimationCount']; awardAmount = json['awardAmount']; giftBatchId = json['giftBatchId']?.toString(); final dynamic rawTargetUserIds = json['targetUserIds']; if (rawTargetUserIds is List) { targetUserIds = rawTargetUserIds .map((item) => item?.toString().trim() ?? "") .where((id) => id.isNotEmpty) .toList(); } hasPlayedAnimation = json['hasPlayedAnimation']; needUpDataUserInfo = json['needUpDataUserInfo']; userWheat = json['userWheat'] != null ? MicRes.fromJson(json['userWheat']) : null; user = json['user'] != null ? SocialChatUserProfile.fromJson(json['user']) : null; toUser = json['toUser'] != null ? SocialChatUserProfile.fromJson(json['toUser']) : null; gift = json['gift'] != null ? SocialChatGiftRes.fromJson(json['gift']) : null; _hydrateRocketRewardPayload(json); } void _hydrateRocketRewardPayload(dynamic json) { if (!SCRoomMsgType.isRocketRewardUserType(type)) { return; } final root = _stringKeyMap(json); final payload = _stringKeyMap(root['data']); final source = payload.isEmpty ? root : payload; groupId = _firstNonBlankString([ groupId, source['groupId'], source['roomAccount'], source['room_account'], source['roomId'], source['room_id'], ]); role = _firstNonBlankString([ role, source['rewardType'], source['reward_type'], ]); rocketIconUrl = _firstNonBlankString([ rocketIconUrl, source['rocketIconUrl'], source['rocket_icon_url'], source['rocketIcon'], source['rewardCover'], source['reward_cover'], ]); msg = _firstNonBlankString([ msg, source['rewardCover'], source['reward_cover'], source['rewardIconUrl'], source['reward_icon_url'], source['cover'], rocketIconUrl, ]); user ??= _rocketRewardUserProfile(source); } static SocialChatUserProfile? _rocketRewardUserProfile( Map source, ) { final userId = _firstNonBlankString([ source['userId'], source['user_id'], source['rewardUserId'], source['reward_user_id'], source['receiverUserId'], source['receiver_user_id'], source['targetUserId'], source['target_user_id'], ]); final nickname = _firstNonBlankString([ source['userNickname'], source['user_nickname'], source['nickname'], source['receiverNickname'], source['receiver_nickname'], source['account'], userId, ]); final account = _firstNonBlankString([ source['account'], source['userAccount'], source['user_account'], source['actualAccount'], source['actual_account'], userId, ]); final avatar = _firstNonBlankString([ source['userAvatar'], source['user_avatar'], source['avatar'], source['receiverAvatar'], source['receiver_avatar'], ]); if (userId.isEmpty && nickname.isEmpty && account.isEmpty && avatar.isEmpty) { return null; } return SocialChatUserProfile( id: userId, account: account, userNickname: nickname, userAvatar: avatar, ); } static Map _stringKeyMap(dynamic value) { if (value is Map) { return value; } if (value is Map) { return value.map((key, item) => MapEntry(key.toString(), item)); } return const {}; } static String _firstNonBlankString(Iterable values) { for (final value in values) { final text = value?.toString().trim() ?? ''; if (text.isNotEmpty && text.toLowerCase() != 'null') { return text; } } return ''; } Map toJson() { final map = {}; map['groupId'] = groupId; map['msg'] = msg; map['type'] = type; map['role'] = role; if ((rocketIconUrl ?? "").trim().isNotEmpty) { map['rocketIconUrl'] = rocketIconUrl; } map['time'] = time; map['awardAmount'] = awardAmount; map['number'] = number; map['customAnimationCount'] = customAnimationCount; map['hasPlayedAnimation'] = hasPlayedAnimation; map['needUpDataUserInfo'] = needUpDataUserInfo; if ((giftBatchId ?? "").trim().isNotEmpty) { map['giftBatchId'] = giftBatchId; } if (targetUserIds != null) { map['targetUserIds'] = targetUserIds; } if (userWheat != null) { map['userWheat'] = userWheat?.toJson(); } if (user != null) { map['user'] = user?.toJson(); } if (toUser != null) { map['toUser'] = toUser?.toJson(); } if (gift != null) { map['gift'] = gift?.toJson(); } return map; } }