From 68a1597bae226c0950af847d5eb9ad0ae79f5777 Mon Sep 17 00:00:00 2001 From: Jolie <> Date: Thu, 20 Nov 2025 02:02:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=81=8A=E5=A4=A9=E5=AE=A4?= =?UTF-8?q?=E5=85=AC=E5=B1=8F=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E5=B0=81=E8=A3=85=E4=BC=98=E5=8C=96=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/controller/discover/room_controller.dart | 85 +++++- lib/model/live/live_chat_message.dart | 37 +++ lib/pages/discover/live_room_page.dart | 37 ++- lib/pages/home/nearby_tab.dart | 11 +- lib/pages/home/recommend_tab.dart | 11 +- lib/rtc/rtm_manager.dart | 47 +++- lib/service/live_chat_message_service.dart | 250 ++++++++++++++++++ lib/widget/live/live_room_action_bar.dart | 32 ++- lib/widget/live/live_room_chat_item.dart | 40 ++- .../live/live_room_notice_chat_panel.dart | 79 ++++-- lib/widget/live/live_room_user_header.dart | 27 +- 11 files changed, 563 insertions(+), 93 deletions(-) create mode 100644 lib/model/live/live_chat_message.dart create mode 100644 lib/service/live_chat_message_service.dart diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index 3c2f771..9451f99 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -1,10 +1,10 @@ -import 'package:agora_token_generator/agora_token_generator.dart'; +import 'package:dating_touchme_app/model/live/live_chat_message.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; +import 'package:dating_touchme_app/service/live_chat_message_service.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:permission_handler/permission_handler.dart'; /// 直播房间相关控制器 @@ -20,6 +20,61 @@ class RoomController extends GetxController { /// 是否正在创建 final RxBool isLoading = false.obs; + /// 聊天消息列表 + final RxList chatMessages = [].obs; + + /// 消息服务实例 + final LiveChatMessageService _messageService = LiveChatMessageService.instance; + + @override + void onInit() { + super.onInit(); + // 注册消息监听 + _registerMessageListener(); + } + + @override + void onClose() { + super.onClose(); + // 移除消息监听 + _messageService.unregisterMessageListener(); + } + + /// 注册消息监听 + void _registerMessageListener() { + _messageService.registerMessageListener( + onMessageReceived: (message) { + _addMessage(message); + }, + onMessageError: (error) { + print('❌ 消息处理错误: $error'); + }, + ); + } + + /// 添加消息到列表(带去重和数量限制) + void _addMessage(LiveChatMessage message) { + // 去重:检查是否已存在相同的消息(基于 userId + content + timestamp) + final exists = chatMessages.any((m) => + m.userId == message.userId && + m.content == message.content && + (m.timestamp - message.timestamp).abs() < 1000); // 1秒内的相同消息视为重复 + + if (exists) { + print('⚠️ 消息已存在,跳过添加'); + return; + } + + chatMessages.add(message); + print('✅ 消息已添加到列表,当前消息数: ${chatMessages.length}'); + + // 限制消息数量,最多保留100条 + if (chatMessages.length > 100) { + chatMessages.removeAt(0); + print('📝 消息列表已满,移除最旧的消息'); + } + } + /// 调用接口创建 RTC 频道 Future createRtcChannel() async { if (isLoading.value) return; @@ -32,14 +87,6 @@ class RoomController extends GetxController { final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; - GetStorage storage = GetStorage(); - String userId = storage.read('userId') ?? ''; - String tokens = RtmTokenBuilder.buildToken( - appId: '4c2ea9dcb4c5440593a418df0fdd512d', - appCertificate: '16f34b45181a4fae8acdb1a28762fcfa', - userId: userId, - tokenExpireSeconds: 3600, - ); await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid); } else { final message = base.message.isNotEmpty ? base.message : '创建频道失败'; @@ -67,8 +114,24 @@ class RoomController extends GetxController { } } + /// 发送公屏消息 + Future sendChatMessage(String content) async { + final channelName = rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId; + + final result = await _messageService.sendMessage( + content: content, + channelName: channelName, + ); + + // 如果发送成功,立即添加到本地列表(优化体验,避免等待 RTM 回调) + if (result.success && result.message != null) { + _addMessage(result.message!); + } + } + + /// 发送消息(保留原有方法,用于兼容) Future sendMessage(String message) async { - await RTCManager.instance.sendMessage(message); + await sendChatMessage(message); } Future _ensureRtcPermissions() async { diff --git a/lib/model/live/live_chat_message.dart b/lib/model/live/live_chat_message.dart new file mode 100644 index 0000000..0d87a93 --- /dev/null +++ b/lib/model/live/live_chat_message.dart @@ -0,0 +1,37 @@ +/// 直播间聊天消息模型 +class LiveChatMessage { + final String userId; + final String userName; + final String? avatar; + final String content; + final int timestamp; + + LiveChatMessage({ + required this.userId, + required this.userName, + this.avatar, + required this.content, + required this.timestamp, + }); + + factory LiveChatMessage.fromJson(Map json) { + return LiveChatMessage( + userId: json['userId'] ?? json['uid'] ?? '', + userName: json['userName'] ?? json['nickName'] ?? '用户', + avatar: json['avatar'] ?? json['profilePhoto'], + content: json['content'] ?? json['message'] ?? '', + timestamp: json['timestamp'] ?? DateTime.now().millisecondsSinceEpoch, + ); + } + + Map toJson() { + return { + 'userId': userId, + 'userName': userName, + 'avatar': avatar, + 'content': content, + 'timestamp': timestamp, + }; + } +} + diff --git a/lib/pages/discover/live_room_page.dart b/lib/pages/discover/live_room_page.dart index ab5e03b..631c59e 100644 --- a/lib/pages/discover/live_room_page.dart +++ b/lib/pages/discover/live_room_page.dart @@ -1,4 +1,5 @@ import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -79,6 +80,21 @@ class _LiveRoomPageState extends State { super.dispose(); } + /// 发送消息 + Future _sendMessage() async { + final content = _messageController.text.trim(); + if (content.isEmpty) { + return; + } + + // 发送消息 + await _roomController.sendChatMessage(content); + + // 清空输入框 + _messageController.clear(); + message = ''; + } + void _showGiftPopup() { SmartDialog.show( alignment: Alignment.bottomCenter, @@ -143,9 +159,23 @@ class _LiveRoomPageState extends State { child: Column( children: [ SizedBox(height: 10.w), - const LiveRoomUserHeader( - userName: '开心的橘子', - popularityText: '1263', + Builder( + builder: (context) { + // 从 GlobalData 获取当前用户信息 + final userData = GlobalData().userData; + final userName = userData?.nickName ?? '用户'; + // 人气数可以从接口获取,这里先用默认值或从其他地方获取 + final popularityText = '0'; // TODO: 从接口获取真实人气数 + + return LiveRoomUserHeader( + userName: userName, + popularityText: popularityText, + avatarAsset: (userData?.profilePhoto != null && + userData!.profilePhoto!.isNotEmpty) + ? userData.profilePhoto! + : Assets.imagesUserAvatar, + ); + }, ), SizedBox(height: 7.w), LiveRoomAnchorShowcase(), @@ -165,6 +195,7 @@ class _LiveRoomPageState extends State { onMessageChanged: (value) { message = value; }, + onSendTap: _sendMessage, onGiftTap: _showGiftPopup, onChargeTap: _showRechargePopup, ), diff --git a/lib/pages/home/nearby_tab.dart b/lib/pages/home/nearby_tab.dart index 99a534d..e82b006 100644 --- a/lib/pages/home/nearby_tab.dart +++ b/lib/pages/home/nearby_tab.dart @@ -40,7 +40,6 @@ class _NearbyTabState extends State return Obx(() { final List dataSource = controller.nearbyFeed; - final bool isLoading = controller.nearbyIsLoading.value; final bool hasMore = controller.nearbyHasMore.value; return SmartRefresher( @@ -106,16 +105,8 @@ class _NearbyTabState extends State bottom: totalBottomPadding + 12, ), itemBuilder: (context, index) { - // 加载状态 - if (isLoading && dataSource.isEmpty && index == 0) { - // 使用足够的高度确保可以滚动 - return SizedBox( - height: MediaQuery.of(context).size.height * 1.2, - child: const Center(child: CircularProgressIndicator()), - ); - } // 空数据状态 - if (!isLoading && dataSource.isEmpty && index == 0) { + if (dataSource.isEmpty && index == 0) { // 使用足够的高度确保可以滚动 return SizedBox( height: MediaQuery.of(context).size.height * 1.2, diff --git a/lib/pages/home/recommend_tab.dart b/lib/pages/home/recommend_tab.dart index d906b31..7804380 100644 --- a/lib/pages/home/recommend_tab.dart +++ b/lib/pages/home/recommend_tab.dart @@ -40,7 +40,6 @@ class _RecommendTabState extends State return Obx(() { final List dataSource = controller.recommendFeed; - final bool isLoading = controller.recommendIsLoading.value; final bool hasMore = controller.recommendHasMore.value; return SmartRefresher( @@ -106,16 +105,8 @@ class _RecommendTabState extends State bottom: totalBottomPadding + 12, ), itemBuilder: (context, index) { - // 加载状态 - if (isLoading && dataSource.isEmpty && index == 0) { - // 使用足够的高度确保可以滚动 - return SizedBox( - height: MediaQuery.of(context).size.height * 1.2, - child: const Center(child: CircularProgressIndicator()), - ); - } // 空数据状态 - if (!isLoading && dataSource.isEmpty && index == 0) { + if (dataSource.isEmpty && index == 0) { // 使用足够的高度确保可以滚动 return SizedBox( height: MediaQuery.of(context).size.height * 1.2, diff --git a/lib/rtc/rtm_manager.dart b/lib/rtc/rtm_manager.dart index 99a463b..a8097d3 100644 --- a/lib/rtc/rtm_manager.dart +++ b/lib/rtc/rtm_manager.dart @@ -4,9 +4,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; -import '../model/rtc/rtc_channel_data.dart'; import '../network/network_service.dart'; -import '../network/rtc_api.dart'; /// 声网 RTM 管理器,聚合常用的客户端与 StreamChannel 操作 class RTMManager { @@ -148,15 +146,42 @@ class RTMManager { String? customType, bool storeInHistory = false, }) async { - _ensureInitialized(); - final (status, _) = await _client!.publish( - channelName, - message, - channelType: channelType, - customType: customType, - storeInHistory: storeInHistory, - ); - return _handleStatus(status); + try { + _ensureInitialized(); + if (!_isLoggedIn) { + print('❌ RTM 未登录,无法发布消息'); + return false; + } + print('📤 RTM 发布消息到频道: $channelName'); + print('📤 RTM 消息内容: $message'); + print('📤 RTM 开始调用 publish 方法...'); + + final (status, _) = await _client!.publish( + channelName, + message, + channelType: channelType, + customType: customType, + storeInHistory: storeInHistory, + ); + + print('📤 RTM publish 方法返回,状态码: ${status.errorCode}'); + print('📤 RTM publish 错误状态: ${status.error}'); + + final success = _handleStatus(status); + if (success) { + print('✅ RTM 消息发布成功'); + } else { + print('❌ RTM 消息发布失败: ${status.errorCode}'); + if (onOperationError != null) { + onOperationError!(status); + } + } + return success; + } catch (e, stackTrace) { + print('❌ RTM publishChannelMessage 异常: $e'); + print('❌ 堆栈信息: $stackTrace'); + return false; + } } /// 释放 RTM Client diff --git a/lib/service/live_chat_message_service.dart b/lib/service/live_chat_message_service.dart new file mode 100644 index 0000000..847d913 --- /dev/null +++ b/lib/service/live_chat_message_service.dart @@ -0,0 +1,250 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:agora_rtm/agora_rtm.dart'; +import 'package:dating_touchme_app/controller/global.dart'; +import 'package:dating_touchme_app/model/live/live_chat_message.dart'; +import 'package:dating_touchme_app/rtc/rtc_manager.dart'; +import 'package:dating_touchme_app/rtc/rtm_manager.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get_storage/get_storage.dart'; + +/// 直播间聊天消息服务 +/// 负责消息的发送、接收、解析和管理 +class LiveChatMessageService { + // 单例模式 + static final LiveChatMessageService _instance = LiveChatMessageService._internal(); + factory LiveChatMessageService() => _instance; + static LiveChatMessageService get instance => _instance; + + final GetStorage _storage = GetStorage(); + bool _isListenerRegistered = false; + + // 消息监听回调 + Function(LiveChatMessage message)? onMessageReceived; + Function(String error)? onMessageError; + + LiveChatMessageService._internal(); + + /// 注册消息监听器 + void registerMessageListener({ + Function(LiveChatMessage message)? onMessageReceived, + Function(String error)? onMessageError, + }) { + if (_isListenerRegistered) { + print('⚠️ 消息监听器已注册,跳过重复注册'); + return; + } + + this.onMessageReceived = onMessageReceived; + this.onMessageError = onMessageError; + + RTMManager.instance.onMessageEvent = (MessageEvent event) { + _handleIncomingMessage(event); + }; + + _isListenerRegistered = true; + print('✅ 消息监听器注册完成'); + } + + /// 移除消息监听器 + void unregisterMessageListener() { + RTMManager.instance.onMessageEvent = null; + _isListenerRegistered = false; + onMessageReceived = null; + onMessageError = null; + print('✅ 消息监听器已移除'); + } + + /// 处理接收到的消息 + void _handleIncomingMessage(MessageEvent event) { + try { + // 解析消息内容 + final messageText = _parseMessageContent(event.message); + final messageData = json.decode(messageText) as Map; + + // 只处理聊天消息类型 + if (messageData['type'] == 'chat_message') { + final chatMessage = LiveChatMessage.fromJson(messageData); + onMessageReceived?.call(chatMessage); + } + } catch (e, stackTrace) { + final error = '解析RTM消息失败: $e'; + print('❌ $error'); + print('❌ 堆栈信息: $stackTrace'); + onMessageError?.call(error); + } + } + + /// 解析消息内容(支持 String 和 Uint8List) + String _parseMessageContent(dynamic message) { + if (message is String) { + return message; + } else if (message is Uint8List) { + return utf8.decode(message); + } else { + return message.toString(); + } + } + + /// 构建消息数据 + Map _buildMessageData(String content) { + final userId = _storage.read('userId') ?? GlobalData().userId ?? ''; + final userData = GlobalData().userData; + final userName = userData?.nickName ?? '用户'; + final avatar = userData?.profilePhoto; + + return { + 'type': 'chat_message', + 'userId': userId, + 'userName': userName, + 'avatar': avatar, + 'content': content.trim(), + 'timestamp': DateTime.now().millisecondsSinceEpoch, + }; + } + + /// 验证消息内容 + String? _validateMessage(String content) { + final trimmed = content.trim(); + if (trimmed.isEmpty) { + return '消息内容不能为空'; + } + if (trimmed.length > 500) { + return '消息内容不能超过500个字符'; + } + return null; + } + + /// 检查发送前置条件 + String? _checkSendPreconditions(String channelName) { + if (!RTMManager.instance.isInitialized) { + return 'RTM 未初始化,无法发送消息'; + } + if (!RTMManager.instance.isLoggedIn) { + return 'RTM 未登录,无法发送消息'; + } + if (channelName.isEmpty) { + return '未加入频道,无法发送消息'; + } + return null; + } + + /// 发送聊天消息 + /// + /// [content] 消息内容 + /// [channelName] 频道名称,如果为空则从 RTCManager 获取 + /// [showToast] 是否显示 Toast 提示,默认为 true + /// + /// 返回发送结果,true 表示成功,false 表示失败 + Future sendMessage({ + required String content, + String? channelName, + bool showToast = true, + }) async { + // 验证消息内容 + final validationError = _validateMessage(content); + if (validationError != null) { + if (showToast) { + SmartDialog.showToast(validationError); + } + return MessageSendResult.failure(validationError); + } + + // 获取频道名称 + final targetChannelName = channelName ?? + RTCManager.instance.currentChannelId ?? ''; + + // 检查前置条件 + final preconditionError = _checkSendPreconditions(targetChannelName); + if (preconditionError != null) { + print('❌ $preconditionError'); + if (showToast) { + SmartDialog.showToast(preconditionError); + } + return MessageSendResult.failure(preconditionError); + } + + try { + // 构建消息数据 + final messageData = _buildMessageData(content); + final messageJson = json.encode(messageData); + + print('📤 发送消息到频道: $targetChannelName'); + print('📤 消息内容: $messageJson'); + + // 通过 RTM 发送消息 + final success = await RTMManager.instance.publishChannelMessage( + channelName: targetChannelName, + message: messageJson, + ); + + if (success) { + print('✅ 消息发送成功'); + // 创建消息对象(用于立即显示) + final chatMessage = LiveChatMessage( + userId: messageData['userId'] as String, + userName: messageData['userName'] as String, + avatar: messageData['avatar'] as String?, + content: messageData['content'] as String, + timestamp: messageData['timestamp'] as int, + ); + return MessageSendResult.success(chatMessage); + } else { + final error = '消息发送失败,请重试'; + print('❌ $error'); + if (showToast) { + SmartDialog.showToast(error); + } + return MessageSendResult.failure(error); + } + } catch (e, stackTrace) { + final error = '发送消息异常: $e'; + print('❌ $error'); + print('❌ 堆栈信息: $stackTrace'); + if (showToast) { + SmartDialog.showToast(error); + } + return MessageSendResult.failure(error); + } + } + + /// 创建消息对象(用于本地显示) + LiveChatMessage createLocalMessage(String content) { + final messageData = _buildMessageData(content); + return LiveChatMessage( + userId: messageData['userId'] as String, + userName: messageData['userName'] as String, + avatar: messageData['avatar'] as String?, + content: messageData['content'] as String, + timestamp: messageData['timestamp'] as int, + ); + } +} + +/// 消息发送结果 +class MessageSendResult { + final bool success; + final LiveChatMessage? message; + final String? error; + + MessageSendResult._({ + required this.success, + this.message, + this.error, + }); + + factory MessageSendResult.success(LiveChatMessage message) { + return MessageSendResult._( + success: true, + message: message, + ); + } + + factory MessageSendResult.failure(String error) { + return MessageSendResult._( + success: false, + error: error, + ); + } +} + diff --git a/lib/widget/live/live_room_action_bar.dart b/lib/widget/live/live_room_action_bar.dart index a83ee8b..4fd6a36 100644 --- a/lib/widget/live/live_room_action_bar.dart +++ b/lib/widget/live/live_room_action_bar.dart @@ -7,12 +7,14 @@ class LiveRoomActionBar extends StatelessWidget { super.key, required this.messageController, required this.onMessageChanged, + required this.onSendTap, required this.onGiftTap, required this.onChargeTap, }); final TextEditingController messageController; final ValueChanged onMessageChanged; + final VoidCallback onSendTap; final VoidCallback onGiftTap; final VoidCallback onChargeTap; @@ -51,7 +53,7 @@ class LiveRoomActionBar extends StatelessWidget { ), child: TextField( controller: messageController, - keyboardType: TextInputType.number, + keyboardType: TextInputType.text, style: TextStyle( fontSize: ScreenUtil().setWidth(14), height: 1, @@ -69,22 +71,26 @@ class LiveRoomActionBar extends StatelessWidget { border: InputBorder.none, ), onChanged: onMessageChanged, + onSubmitted: (_) => onSendTap(), ), ), ), SizedBox(width: 8.w), - Container( - width: 38.w, - height: 38.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(38.w)), - color: const Color.fromRGBO(0, 0, 0, .3), - ), - child: Center( - child: Image.asset( - Assets.imagesArrowR, - width: 16.w, - height: 16.w, + InkWell( + onTap: onSendTap, + child: Container( + width: 38.w, + height: 38.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(38.w)), + color: const Color.fromRGBO(0, 0, 0, .3), + ), + child: Center( + child: Image.asset( + Assets.imagesArrowR, + width: 16.w, + height: 16.w, + ), ), ), ), diff --git a/lib/widget/live/live_room_chat_item.dart b/lib/widget/live/live_room_chat_item.dart index e0378c6..b555ff0 100644 --- a/lib/widget/live/live_room_chat_item.dart +++ b/lib/widget/live/live_room_chat_item.dart @@ -1,9 +1,15 @@ import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/model/live/live_chat_message.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class LiveRoomChatItem extends StatelessWidget { - const LiveRoomChatItem({super.key}); + const LiveRoomChatItem({ + super.key, + required this.message, + }); + + final LiveChatMessage message; @override Widget build(BuildContext context) { @@ -13,23 +19,43 @@ class LiveRoomChatItem extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(Assets.imagesUserAvatar, width: 25.w, height: 25.w), + // 头像 + ClipOval( + child: message.avatar != null && message.avatar!.isNotEmpty + ? Image.network( + message.avatar!, + width: 25.w, + height: 25.w, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + Assets.imagesUserAvatar, + width: 25.w, + height: 25.w, + ); + }, + ) + : Image.asset( + Assets.imagesUserAvatar, + width: 25.w, + height: 25.w, + ), + ), SizedBox(width: 10.w), - SizedBox( - width: 224.w, + // 消息内容 + Expanded( child: RichText( text: TextSpan( children: [ TextSpan( - text: "沙发沙发:", + text: "${message.userName}:", style: TextStyle( fontSize: 11.w, color: const Color.fromRGBO(155, 138, 246, 1), ), ), TextSpan( - text: - "大家好啊!大家好啊!大家好啊!大家好啊!大家好啊!大家好啊!大家好啊!大家好啊!大家好啊!", + text: message.content, style: TextStyle(fontSize: 11.w, color: Colors.white), ), ], diff --git a/lib/widget/live/live_room_notice_chat_panel.dart b/lib/widget/live/live_room_notice_chat_panel.dart index 0630e9e..f35934e 100644 --- a/lib/widget/live/live_room_notice_chat_panel.dart +++ b/lib/widget/live/live_room_notice_chat_panel.dart @@ -1,13 +1,36 @@ +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/widget/live/live_room_chat_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; -class LiveRoomNoticeChatPanel extends StatelessWidget { +class LiveRoomNoticeChatPanel extends StatefulWidget { const LiveRoomNoticeChatPanel({super.key}); + @override + State createState() => _LiveRoomNoticeChatPanelState(); +} + +class _LiveRoomNoticeChatPanelState extends State { + final RoomController controller = Get.find(); + late final ScrollController scrollController; + + @override + void initState() { + super.initState(); + scrollController = ScrollController(); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { + return Container( height: 230.w, padding: EdgeInsets.only(left: 13.w, right: 9.w), @@ -15,29 +38,39 @@ class LiveRoomNoticeChatPanel extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - Text( - "欢迎来到直播间!严禁未成年人直播或礼物消费。严禁违法违规、低俗色情、吸烟酗酒、人身伤害等直播内容。理性消费如主播在直播中以不当方式诱导消费,请谨慎辨别。切勿私下交易,以防人身财产损失,谨防网络诈骗。", - style: TextStyle( - fontSize: 11.w, - color: const Color.fromRGBO(155, 138, 246, 1), + child: Obx(() { + // 监听消息列表变化,自动滚动到底部 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients) { + scrollController.animateTo( + scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + + return SingleChildScrollView( + controller: scrollController, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "欢迎来到直播间!严禁未成年人直播或礼物消费。严禁违法违规、低俗色情、吸烟酗酒、人身伤害等直播内容。理性消费如主播在直播中以不当方式诱导消费,请谨慎辨别。切勿私下交易,以防人身财产损失,谨防网络诈骗。", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(155, 138, 246, 1), + ), + ), + SizedBox(height: 15.w), + // 显示聊天消息列表 + ...controller.chatMessages.map( + (message) => LiveRoomChatItem(message: message), ), - ), - SizedBox(height: 15.w), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - const LiveRoomChatItem(), - ], - ), - ), + ], + ), + ); + }), ), SizedBox(width: 18.w), Image.asset( diff --git a/lib/widget/live/live_room_user_header.dart b/lib/widget/live/live_room_user_header.dart index f149e02..20f25b5 100644 --- a/lib/widget/live/live_room_user_header.dart +++ b/lib/widget/live/live_room_user_header.dart @@ -38,11 +38,28 @@ class LiveRoomUserHeader extends StatelessWidget { ), child: Row( children: [ - Image.asset( - avatarAsset, - width: 34.w, - height: 34.w, - ), + // 支持网络图片和本地资源 + avatarAsset.startsWith('http://') || avatarAsset.startsWith('https://') + ? ClipOval( + child: Image.network( + avatarAsset, + width: 34.w, + height: 34.w, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + Assets.imagesUserAvatar, + width: 34.w, + height: 34.w, + ); + }, + ), + ) + : Image.asset( + avatarAsset, + width: 34.w, + height: 34.w, + ), SizedBox(width: 7.w), Column( crossAxisAlignment: CrossAxisAlignment.start,