2026-07-01 18:25:58 +08:00

1838 lines
62 KiB
Dart

import 'dart:convert';
import 'package:extended_image/extended_image.dart'
show ExtendedNetworkImageProvider;
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_ninepatch_image/flutter_ninepatch_image.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../app_localizations.dart';
import '../../../chatvibe_core/constants/at_app_colors.dart';
import '../../../chatvibe_core/constants/at_global_config.dart';
import '../../../chatvibe_core/constants/at_room_msg_type.dart';
import '../../../chatvibe_core/constants/at_screen.dart';
import '../../../chatvibe_core/routes/at_fluro_navigator.dart';
import '../../../chatvibe_core/utilities/at_room_utils.dart';
import '../../../chatvibe_data/models/enum/at_activity_reward_props_type.dart';
import '../../../chatvibe_data/models/enum/at_props_type.dart';
import '../../../chatvibe_data/models/enum/at_room_roles_type.dart';
import '../../../chatvibe_data/sources/repositories/user_repository_impl.dart';
import '../../../chatvibe_domain/models/res/gift_res.dart';
import '../../../chatvibe_domain/models/res/login_res.dart';
import '../../../chatvibe_domain/models/res/mic_res.dart';
import '../../../chatvibe_domain/usecases/at_case.dart';
import '../../../chatvibe_features/index/main_route.dart';
import '../../components/at_compontent.dart';
import '../../components/at_tts.dart';
import '../../components/text/at_text.dart';
///消息item
class MsgItem extends StatefulWidget {
static final Map<String, Future<ChatVibeUserProfile>> roomUserFutureCache =
<String, Future<ChatVibeUserProfile>>{};
static final Set<String> roomUserRefreshInFlight = <String>{};
static final Set<String> roomUserFullInfoLoaded = <String>{};
static final Map<String, ExtendedNetworkImageProvider>
badgeImageProviderCache = <String, ExtendedNetworkImageProvider>{};
static final Map<String, ExtendedNetworkImageProvider>
chatBoxImageProviderCache = <String, ExtendedNetworkImageProvider>{};
static void clearRoomUserCaches({bool clearBadgeCache = false}) {
roomUserFutureCache.clear();
roomUserRefreshInFlight.clear();
roomUserFullInfoLoaded.clear();
chatBoxImageProviderCache.clear();
if (clearBadgeCache) {
badgeImageProviderCache.clear();
}
}
final Function(ChatVibeUserProfile? user) onClick;
final Msg msg;
MsgItem({Key? key, required this.msg, required this.onClick})
: super(key: key);
@override
_MsgItemState createState() => _MsgItemState();
}
class _MsgItemState extends State<MsgItem> {
static const int _maxRoomUserFutureCacheSize = 256;
static const int _maxBadgeImageProviderCacheSize = 256;
static const int _maxChatBoxImageProviderCacheSize = 64;
static const Duration _badgeImageCacheMaxAge = Duration(days: 7);
static const Duration _chatBoxImageCacheMaxAge = Duration(days: 7);
@override
Widget build(BuildContext context) {
//gift
// if (msg.type == Roomtype.gift.code) {
// return _buildGiftMsg(context);
// }
///系统提示
if (widget.msg.type == ATRoomMsgType.systemTips) {
return _systemTipsRoomItem1(context);
}
///进入房间
if (widget.msg.type == ATRoomMsgType.joinRoom) {
return _enterRoomItem1(context);
}
///礼物
if (widget.msg.type == ATRoomMsgType.gift) {
return _buildGiftMsg1(context);
}
///火箭用户中奖
if (widget.msg.type == ATRoomMsgType.rocketRewardUser) {
return _buildRocketRewardMsg1(context);
}
///幸运礼物
if (widget.msg.type == ATRoomMsgType.gameLuckyGift) {
return _buildGameLuckyGiftMsg1(context);
}
///幸运礼物
if (widget.msg.type == ATRoomMsgType.gameLuckyGift_5) {
return _buildGameLuckyGiftMsg_5(context);
}
///房间身份变动
if (widget.msg.type == ATRoomMsgType.roomRoleChange) {
return _buildRoleChangeMsg1(context);
}
///踢出房间
if (widget.msg.type == ATRoomMsgType.qcfj) {
widget.msg.msg = ATAppLocalizations.of(context)!.kickRoomTips;
}
///图片消息
if (widget.msg.type == ATRoomMsgType.image) {
return _buildImageMsg1(context);
}
///掷骰子
if (widget.msg.type == ATRoomMsgType.roomDice) {
return _buildDiceMsg1(context);
}
///石头剪刀布
if (widget.msg.type == ATRoomMsgType.roomRPS) {
return _buildRPSMsg1(context);
}
///幸运数字
if (widget.msg.type == ATRoomMsgType.roomLuckNumber) {
return _buildLuckNumberMsg1(context);
}
if (widget.msg.user != null) {
PropsResources? chatBox = widget.msg.user?.getChatBox();
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:
chatBox != null
? Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.7, // 最大宽度约束
),
padding: EdgeInsets.symmetric(horizontal: 10.w),
margin: EdgeInsetsDirectional.only(
start: 34.w,
).copyWith(bottom: 1.w),
child: NinePatchImage(
scale: 3,
alignment: Alignment.center,
imageProvider: _getChatBoxImageProvider(
chatBox.expand,
),
sliceCachedKey: _buildNinePatchCacheKey(
chatBox.expand,
),
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) {},
),
),
),
)
: Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.7, // 最大宽度约束
),
decoration: BoxDecoration(
color: Color(0xffB6A1DE).withOpacity(0.2),
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: ExtendedText(
widget.msg?.msg ?? '',
style: TextStyle(
fontSize: sp(13),
color: Color(
_msgColor(widget.msg.type ?? ""),
),
fontWeight: FontWeight.w500,
),
softWrap: true, // 自动换行
specialTextSpanBuilder: AtTextSpanBuilder(
onTapCall: (userId) {},
),
),
),
onLongPress: () {
if (!(widget.msg?.msg ?? "").startsWith(AtText.flag)) {
Clipboard.setData(
ClipboardData(text: widget.msg?.msg ?? ''),
);
ATTts.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: Color(0xffB6A1DE).withOpacity(0.2),
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,
),
),
);
}
}
///系统提示
_systemTipsRoomItem1(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: Color(0xffB6A1DE).withOpacity(0.2),
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,
),
),
);
}
///火箭用户中奖
_buildRocketRewardMsg1(BuildContext context) {
List<InlineSpan> spans = [];
spans.add(
TextSpan(
text: "${widget.msg.user?.userNickname} ",
style: TextStyle(
fontSize: sp(12),
color: themeColor,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
spans.add(
TextSpan(
text: ATAppLocalizations.of(context)!.inRocket,
style: TextStyle(
fontSize: sp(12),
color: Colors.white,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
spans.add(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Image.asset(
"images/room/at_icon_room_rocket_lv${widget.msg.number}.png",
width: 18.w,
),
),
);
spans.add(
TextSpan(
text: ATAppLocalizations.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 == ATActivityRewardATPropsType.GOLD.name
? Image.asset("atu_images/general/at_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: Color(0xffB6A1DE).withOpacity(0.2),
borderRadius: BorderRadius.all(Radius.circular(15.w)),
),
child: text,
),
);
}
///幸运礼物
_buildGameLuckyGiftMsg1(BuildContext context) {
List<InlineSpan> spans = [];
spans.add(
TextSpan(
text: "${widget.msg.user?.userNickname} ",
style: TextStyle(
fontSize: sp(12),
color: themeColor,
fontWeight: FontWeight.w500,
),
recognizer:
TapGestureRecognizer()
..onTap = () {
widget.onClick(widget.msg.user);
},
),
);
spans.add(
TextSpan(
text: ATAppLocalizations.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: themeColor,
fontWeight: FontWeight.w500,
),
recognizer:
TapGestureRecognizer()
..onTap = () {
widget.onClick(widget.msg.toUser);
},
),
);
ATGlobalConfig.lang == "ar"
? spans.add(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Image.asset(
"atu_images/general/at_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: " ${ATAppLocalizations.of(context)!.obtain}",
style: TextStyle(
fontSize: sp(12),
color: themeColor,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
spans.add(
TextSpan(
text: " ${widget.msg.awardAmount}",
style: TextStyle(
fontSize: sp(12),
color: themeColor,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
ATGlobalConfig.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(
"atu_images/general/at_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: Color(0xffB6A1DE).withOpacity(0.2),
borderRadius: BorderRadius.all(Radius.circular(15.w)),
),
child: text,
),
);
}
///幸运礼物中奖5倍以及以上
_buildGameLuckyGiftMsg_5(BuildContext context) {
List<InlineSpan> 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: ATAppLocalizations.of(context)!.get,
style: TextStyle(
fontSize: sp(12),
color: Colors.white,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
spans.add(
TextSpan(
text: " ${widget.msg.awardAmount} ",
style: TextStyle(
fontSize: sp(12),
color: Color(0xffFEF129),
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
spans.add(
TextSpan(
text: ATAppLocalizations.of(context)!.coins3,
style: TextStyle(
fontSize: sp(12),
color: Color(0xffFEF129),
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
spans.add(
TextSpan(
text: " ${ATAppLocalizations.of(context)!.receivedFromALuckyGift}",
style: TextStyle(
fontSize: sp(12),
color: Colors.white,
fontWeight: FontWeight.w500,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
);
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("atu_images/room/at_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(
"atu_images/room/at_icon_luck_gift_msg_n_ball.png",
),
fit: BoxFit.fill,
),
),
alignment: AlignmentDirectional.center,
width: 55.w,
height: 55.w,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10.w),
buildNumForGame(widget.msg.msg ?? "0", size: 18.w),
SizedBox(height: 3.w),
ATGlobalConfig.lang == "ar"
? Image.asset(
"atu_images/room/at_icon_times_text_ar.png",
height: 12.w,
)
: Image.asset(
"atu_images/room/at_icon_times_text_en.png",
height: 10.w,
),
],
),
),
SizedBox(width: 15.w),
],
),
),
);
}
Widget _buildGiftMsg1(BuildContext context) {
PropsResources? chatBox = widget.msg.user?.getChatBox();
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:
chatBox != null
? Container(
constraints: BoxConstraints(
maxWidth: ScreenUtil().screenWidth * 0.7, // 最大宽度约束
),
padding: EdgeInsets.symmetric(horizontal: 10.w),
margin: EdgeInsetsDirectional.only(
start: 34.w,
).copyWith(bottom: 1.w),
child: NinePatchImage(
scale: 3,
alignment: Alignment.center,
imageProvider: _getChatBoxImageProvider(
chatBox.expand,
),
sliceCachedKey: _buildNinePatchCacheKey(
chatBox.expand,
),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text:
ATAppLocalizations.of(context)!.sendTo,
style: TextStyle(
fontSize: 13.sp,
color: Colors.white,
),
),
TextSpan(text: " "),
TextSpan(
text: widget.msg.toUser?.userNickname ?? "",
style: TextStyle(
fontSize: 13.sp,
color: themeColor,
),
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,
),
),
)
: Container(
constraints: BoxConstraints(
maxWidth: ScreenUtil().screenWidth * 0.7, // 最大宽度约束
),
decoration: BoxDecoration(
color: Color(0xffB6A1DE).withOpacity(0.2),
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: ATAppLocalizations.of(context)!.sendTo,
style: TextStyle(
fontSize: 13.sp,
color: Colors.white,
),
),
TextSpan(text: " "),
TextSpan(
text: widget.msg.toUser?.userNickname ?? "",
style: TextStyle(
fontSize: 13.sp,
color: themeColor,
),
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,
),
),
),
],
),
],
),
);
}
///进入房间
_enterRoomItem1(BuildContext context) {
List<InlineSpan> 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: ATAppLocalizations.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 _buildMedals(List<WearBadge> wearBadge) {
return wearBadge.isNotEmpty
? Expanded(
child: SingleChildScrollView(
child: SizedBox(
height: 25.w,
child: ListView.separated(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: wearBadge.length,
itemBuilder: (context, index) {
return netImage(
width: 25.w,
height: 25.w,
url: wearBadge[index].selectUrl ?? "",
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox(width: 5.w);
},
),
),
),
)
: Container();
}
String _buildNinePatchCacheKey(String? url) {
final String normalizedUrl = (url ?? "").trim();
if (normalizedUrl.isEmpty) {
return "";
}
// Reuse the parsed 9-patch slices for the same chat box background so
// rapid room message updates don't repeatedly rebuild bubble geometry.
return "$normalizedUrl|scale:3|v3";
}
int _msgColor(String type) {
switch (type) {
case ATRoomMsgType.joinRoom:
case ATRoomMsgType.systemTips:
case ATRoomMsgType.welcome:
return 0xFFFFFFFF;
case ATRoomMsgType.gift:
return 0xFFFFFFFF;
case ATRoomMsgType.shangMai:
case ATRoomMsgType.xiaMai:
case ATRoomMsgType.qcfj:
case ATRoomMsgType.killXiaMai:
return 0xFFFFFFFF;
break;
// case type.text:
// return _mapTextColor();
// break;
default:
return 0xFFFFFFFF;
}
}
_roleNameColor(String role) {
if (ATRoomRolesType.HOMEOWNER.name == role) {
return Color(0xff2AC0F2);
} else if (ATRoomRolesType.ADMIN.name == role) {
return Color(0xffF9A024);
} else if (ATRoomRolesType.MEMBER.name == role) {
return Color(0xff28ED77);
} else {
return Colors.white;
}
}
Widget _buildRoleChangeMsg1(BuildContext context) {
var text = "";
if (ATRoomRolesType.ADMIN.name == widget.msg.msg) {
text = ATAppLocalizations.of(context)!.adminByHomeowner;
} else if (ATRoomRolesType.MEMBER.name == widget.msg.msg) {
text = ATAppLocalizations.of(context)!.memberByHomeowner;
} else {
text = ATAppLocalizations.of(context)!.touristByHomeowner;
}
List<InlineSpan> 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: Color(0xffB6A1DE).withOpacity(0.2),
borderRadius: BorderRadius.all(Radius.circular(15.w)),
),
child: textView,
),
);
}
Widget _buildImageMsg1(BuildContext context) {
PropsResources? chatBox = widget.msg.user?.getChatBox();
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:
chatBox != null
? Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.5, // 最大宽度约束
),
padding: EdgeInsets.symmetric(horizontal: 10.w),
margin: EdgeInsetsDirectional.only(
start: 34.w,
).copyWith(bottom: 1.w),
child: NinePatchImage(
scale: 3,
alignment: Alignment.center,
imageProvider: _getChatBoxImageProvider(
chatBox.expand,
),
sliceCachedKey: _buildNinePatchCacheKey(
chatBox.expand,
),
child: netImage(url: widget.msg.msg ?? ""),
),
)
: Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.5, // 最大宽度约束
),
decoration: BoxDecoration(
color: Color(0xffB6A1DE).withOpacity(0.2),
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]),
);
ATNavigatorUtils.push(
context,
"${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0",
);
},
),
),
],
),
],
),
),
);
}
Widget _buildDiceMsg1(BuildContext context) {
PropsResources? chatBox = widget.msg.user?.getChatBox();
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:
chatBox != null
? Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.5, // 最大宽度约束
),
padding: EdgeInsets.symmetric(horizontal: 10.w),
margin: EdgeInsetsDirectional.only(
start: 34.w,
).copyWith(bottom: 1.w),
child: NinePatchImage(
scale: 3,
alignment: Alignment.center,
imageProvider: _getChatBoxImageProvider(
chatBox.expand,
),
sliceCachedKey: _buildNinePatchCacheKey(
chatBox.expand,
),
child:
(widget.msg.hasPlayedAnimation ?? false)
? Image.asset(
"atu_images/room/at_icon_dice_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
),
// 传入Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// 根据snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation =
true;
return Image.asset(
"atu_images/room/at_icon_dice_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"atu_images/room/at_icon_dice_animl.webp",
height: 35.w,
);
}
},
),
),
)
: Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.5, // 最大宽度约束
),
decoration: BoxDecoration(
color: Color(0xffB6A1DE).withOpacity(0.2),
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(
"atu_images/room/at_icon_dice_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
),
// 传入Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// 根据snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation =
true;
return Image.asset(
"atu_images/room/at_icon_dice_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"atu_images/room/at_icon_dice_animl.webp",
height: 35.w,
);
}
},
),
),
onTap: () {},
),
),
],
),
],
),
),
);
}
Widget _buildRPSMsg1(BuildContext context) {
PropsResources? chatBox = widget.msg.user?.getChatBox();
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:
chatBox != null
? Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.5, // 最大宽度约束
),
padding: EdgeInsets.symmetric(horizontal: 10.w),
margin: EdgeInsetsDirectional.only(
start: 34.w,
).copyWith(bottom: 1.w),
child: NinePatchImage(
scale: 3,
alignment: Alignment.center,
imageProvider: _getChatBoxImageProvider(
chatBox.expand,
),
sliceCachedKey: _buildNinePatchCacheKey(
chatBox.expand,
),
child:
(widget.msg.hasPlayedAnimation ?? false)
? Image.asset(
"atu_images/room/at_icon_rps_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
),
// 传入Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// 根据snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation =
true;
return Image.asset(
"atu_images/room/at_icon_rps_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"atu_images/room/at_icon_rps_animal.webp",
height: 35.w,
);
}
},
),
),
)
: Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.5, // 最大宽度约束
),
decoration: BoxDecoration(
color: Color(0xffB6A1DE).withOpacity(0.2),
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(
"atu_images/room/at_icon_rps_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
),
// 传入Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// 根据snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation =
true;
return Image.asset(
"atu_images/room/at_icon_rps_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"atu_images/room/at_icon_rps_animal.webp",
height: 35.w,
);
}
},
),
),
onTap: () {},
),
),
],
),
],
),
),
);
}
Widget _buildLuckNumberMsg1(BuildContext context) {
PropsResources? chatBox = widget.msg.user?.getChatBox();
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:
chatBox != null
? Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.7, // 最大宽度约束
),
padding: EdgeInsets.symmetric(horizontal: 10.w),
margin: EdgeInsetsDirectional.only(
start: 34.w,
).copyWith(bottom: 1.w),
child: NinePatchImage(
scale: 3,
alignment: Alignment.center,
imageProvider: _getChatBoxImageProvider(
chatBox.expand,
),
sliceCachedKey: _buildNinePatchCacheKey(
chatBox.expand,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ATGlobalConfig.lang == "ar"
? Image.asset(
"atu_images/room/at_icon_luck_num_text_ar.png",
height: 20.w,
)
: Image.asset(
"atu_images/room/at_icon_luck_num_text_en.png",
height: 20.w,
),
buildNumForRoomLuckNum(
widget.msg.msg ?? "",
size: 20.w,
),
],
),
),
)
: Container(
constraints: BoxConstraints(
maxWidth:
ScreenUtil().screenWidth * 0.7, // 最大宽度约束
),
decoration: BoxDecoration(
color: Color(0xffB6A1DE).withOpacity(0.2),
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: [
ATGlobalConfig.lang == "ar"
? Image.asset(
"atu_images/room/at_icon_luck_num_text_ar.png",
height: 20.w,
)
: Image.asset(
"atu_images/room/at_icon_luck_num_text_en.png",
height: 20.w,
),
buildNumForRoomLuckNum(
widget.msg.msg ?? "",
size: 20.w,
),
],
),
),
onTap: () {},
),
),
],
),
],
),
),
);
}
_buildUserInfoLine(BuildContext context) {
final String userId = widget.msg.user?.id ?? "";
if (userId.isEmpty) {
return const SizedBox.shrink();
}
final bool shouldRefreshUserInfo = widget.msg.needUpDataUserInfo ?? false;
if (shouldRefreshUserInfo) {
ATRoomUtils.roomUsersMap.remove(userId);
MsgItem.roomUserFutureCache.remove(userId);
MsgItem.roomUserFullInfoLoaded.remove(userId);
MsgItem.roomUserRefreshInFlight.add(userId);
widget.msg.needUpDataUserInfo = false;
}
final ChatVibeUserProfile? msgUser = widget.msg.user;
ChatVibeUserProfile? cachedUser = ATRoomUtils.roomUsersMap[userId];
if (cachedUser != null && MsgItem.roomUserFullInfoLoaded.contains(userId)) {
return _buildNewUserInfoLine(context, cachedUser);
}
final ChatVibeUserProfile? fallbackUser = cachedUser ?? msgUser;
return FutureBuilder(
future: _getOrLoadRoomUserInfo(userId),
builder: (ct, AsyncSnapshot<ChatVibeUserProfile> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
ChatVibeUserProfile user = snapshot.data!;
ATRoomUtils.roomUsersMap[user.id ?? ""] = user;
MsgItem.roomUserFullInfoLoaded.add(userId);
MsgItem.roomUserRefreshInFlight.remove(userId);
return _buildNewUserInfoLine(context, user);
}
if (snapshot.hasError) {
MsgItem.roomUserFutureCache.remove(userId);
MsgItem.roomUserRefreshInFlight.remove(userId);
}
}
if (fallbackUser != null) {
return _buildNewUserInfoLine(context, fallbackUser);
}
final PropsResources? activeVip = _getActiveVip(widget.msg.user);
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: 3.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
getWealthLevel(
widget.msg.user?.wealthLevel ?? 0,
width: 48.w,
height: 23.w,
fontSize: 10.sp,
),
SizedBox(width: 3.w),
getUserLevel(
widget.msg.user?.charmLevel ?? 0,
width: 48.w,
height: 23.w,
fontSize: 10.sp,
),
SizedBox(width: 3.w),
chatvibeNickNameText(
maxWidth: 120.w,
fontWeight: FontWeight.w500,
widget.msg.user?.userNickname ?? "",
fontSize: 13.sp,
type: activeVip?.name ?? "",
needScroll:
(widget.msg.user?.userNickname?.characters.length ??
0) >
10,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
activeVip != null
? _buildStableBadgeImage(
url: activeVip.cover ?? "",
width: 25.w,
height: 25.w,
)
: Container(),
_buildMedals(
(widget.msg.user?.wearBadge?.where((item) {
return item.use ?? false;
}).toList() ??
[]),
),
],
),
],
),
),
],
);
},
);
}
PropsResources? _getActiveVip(ChatVibeUserProfile? user) {
final List<UseProps>? useProps = user?.useProps;
if (useProps == null || useProps.isEmpty) {
return null;
}
final int now = DateTime.now().millisecondsSinceEpoch;
PropsResources? activeVip;
PropsResources? fallbackVipWithoutExpire;
int latestExpireTime = 0;
for (final UseProps useProp in useProps) {
if (useProp.propsResources?.type != ATPropsType.NOBLE_VIP.name) {
continue;
}
final int? parsedExpire = int.tryParse(useProp.expireTime ?? "");
if (parsedExpire == null || parsedExpire <= 0) {
fallbackVipWithoutExpire ??= useProp.propsResources;
continue;
}
final int expireTime = parsedExpire;
if (expireTime > now && expireTime >= latestExpireTime) {
latestExpireTime = expireTime;
activeVip = useProp.propsResources;
}
}
return activeVip ?? fallbackVipWithoutExpire;
}
Widget _buildStableBadgeImage({
required String? url,
required double width,
required double height,
}) {
final String normalizedUrl = (url ?? "").trim();
if (normalizedUrl.isEmpty) {
return const SizedBox.shrink();
}
return RepaintBoundary(
child: Image(
image: _getBadgeImageProvider(normalizedUrl),
width: width,
height: height,
fit: BoxFit.contain,
gaplessPlayback: true,
filterQuality: FilterQuality.low,
errorBuilder: (context, error, stackTrace) {
return const SizedBox.shrink();
},
),
);
}
ExtendedNetworkImageProvider _getChatBoxImageProvider(String? url) {
final String normalizedUrl = (url ?? "").trim();
final ExtendedNetworkImageProvider? cached =
MsgItem.chatBoxImageProviderCache[normalizedUrl];
if (cached != null) {
return cached;
}
if (MsgItem.chatBoxImageProviderCache.length >=
_maxChatBoxImageProviderCacheSize) {
MsgItem.chatBoxImageProviderCache.remove(
MsgItem.chatBoxImageProviderCache.keys.first,
);
}
final ExtendedNetworkImageProvider provider = ExtendedNetworkImageProvider(
normalizedUrl,
cache: true,
cacheMaxAge: _chatBoxImageCacheMaxAge,
);
MsgItem.chatBoxImageProviderCache[normalizedUrl] = provider;
return provider;
}
ExtendedNetworkImageProvider _getBadgeImageProvider(String? url) {
final String normalizedUrl = (url ?? "").trim();
final ExtendedNetworkImageProvider? cached =
MsgItem.badgeImageProviderCache[normalizedUrl];
if (cached != null) {
return cached;
}
if (MsgItem.badgeImageProviderCache.length >=
_maxBadgeImageProviderCacheSize) {
MsgItem.badgeImageProviderCache.remove(
MsgItem.badgeImageProviderCache.keys.first,
);
}
final ExtendedNetworkImageProvider provider = ExtendedNetworkImageProvider(
normalizedUrl,
cache: true,
cacheMaxAge: _badgeImageCacheMaxAge,
);
MsgItem.badgeImageProviderCache[normalizedUrl] = provider;
return provider;
}
Future<ChatVibeUserProfile> _getOrLoadRoomUserInfo(String userId) {
final Future<ChatVibeUserProfile>? cached =
MsgItem.roomUserFutureCache[userId];
if (cached != null) {
return cached;
}
if (MsgItem.roomUserFutureCache.length >= _maxRoomUserFutureCacheSize) {
MsgItem.roomUserFutureCache.remove(
MsgItem.roomUserFutureCache.keys.first,
);
}
final Future<ChatVibeUserProfile> future = AccountRepository().loadUserInfo(
userId,
);
MsgItem.roomUserFutureCache[userId] = future;
return future;
}
_buildNewUserInfoLine(BuildContext context, ChatVibeUserProfile user) {
final PropsResources? activeVip = _getActiveVip(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: 3.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
getWealthLevel(
user.wealthLevel ?? 0,
width: 48.w,
height: 23.w,
fontSize: 10.sp,
),
SizedBox(width: 3.w),
getUserLevel(
user.charmLevel ?? 0,
width: 48.w,
height: 23.w,
fontSize: 10.sp,
),
SizedBox(width: 3.w),
chatvibeNickNameText(
maxWidth: 120.w,
fontWeight: FontWeight.w500,
user.userNickname ?? "",
fontSize: 13.sp,
type: activeVip?.name ?? "",
needScroll:
(user.userNickname?.characters.length ?? 0) > 10,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
activeVip != null
? _buildStableBadgeImage(
url: activeVip.cover ?? "",
width: 25.w,
height: 25.w,
)
: Container(),
_buildMedals(
user.wearBadge?.where((item) {
return item.use ?? false;
}).toList() ??
[],
),
],
),
],
),
),
],
);
}
}
///消息
class Msg {
String? groupId;
String? msg;
MicRes? userWheat;
String? type;
ChatVibeUserProfile? user;
String? role = "";
ChatVibeGiftRes? gift;
//礼物个数
num? number;
num? awardAmount;
// 送礼给谁
ChatVibeUserProfile? toUser;
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.gift,
this.number,
this.awardAmount,
this.hasPlayedAnimation,
this.needUpDataUserInfo,
}) {
time = DateTime.now().millisecondsSinceEpoch;
}
Msg.fromJson(dynamic json) {
groupId = json['groupId'];
msg = json['msg'];
type = json['type'];
role = json['role'];
time = json['time'];
number = json['number'];
awardAmount = json['awardAmount'];
hasPlayedAnimation = json['hasPlayedAnimation'];
needUpDataUserInfo = json['needUpDataUserInfo'];
userWheat =
json['userWheat'] != null ? MicRes.fromJson(json['userWheat']) : null;
user =
json['user'] != null
? ChatVibeUserProfile.fromJson(json['user'])
: null;
toUser =
json['toUser'] != null
? ChatVibeUserProfile.fromJson(json['toUser'])
: null;
gift = json['gift'] != null ? ChatVibeGiftRes.fromJson(json['gift']) : null;
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['groupId'] = groupId;
map['msg'] = msg;
map['type'] = type;
map['role'] = role;
map['time'] = time;
map['awardAmount'] = awardAmount;
map['number'] = number;
map['hasPlayedAnimation'] = hasPlayedAnimation;
map['needUpDataUserInfo'] = needUpDataUserInfo;
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;
}
}