diff --git a/assets/images/room_video.png b/assets/images/room_video.png new file mode 100644 index 0000000..db2c147 Binary files /dev/null and b/assets/images/room_video.png differ diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index b7d4329..031e2b6 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -1,7 +1,7 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart'; -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/model/rtc/rtc_channel_detail.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'; @@ -12,18 +12,20 @@ import 'package:permission_handler/permission_handler.dart'; /// 直播房间相关控制器 class RoomController extends GetxController { RoomController({NetworkService? networkService}) - : _networkService = networkService ?? Get.find(); + : _networkService = networkService ?? Get.find(); final NetworkService _networkService; /// 当前频道信息 final Rxn rtcChannel = Rxn(); + final Rxn rtcChannelDetail = Rxn(); /// 聊天消息列表 final RxList chatMessages = [].obs; /// 消息服务实例 - final LiveChatMessageService _messageService = LiveChatMessageService.instance; + final LiveChatMessageService _messageService = + LiveChatMessageService.instance; @override void onInit() { @@ -54,10 +56,12 @@ class RoomController extends GetxController { /// 添加消息到列表(带去重和数量限制) 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秒内的相同消息视为重复 + final exists = chatMessages.any( + (m) => + m.userId == message.userId && + m.content == message.content && + (m.timestamp - message.timestamp).abs() < 1000, + ); // 1秒内的相同消息视为重复 if (exists) { print('⚠️ 消息已存在,跳过添加'); @@ -84,7 +88,12 @@ class RoomController extends GetxController { final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; - await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster); + await _joinRtcChannel( + base.data!.token, + base.data!.channelId, + base.data!.uid, + ClientRoleType.clientRoleBroadcaster, + ); } else { final message = base.message.isNotEmpty ? base.message : '创建频道失败'; SmartDialog.showToast(message); @@ -100,7 +109,12 @@ class RoomController extends GetxController { final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; - await _joinRtcChannel(base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleAudience); + await _joinRtcChannel( + base.data!.token, + channelName, + base.data!.uid, + ClientRoleType.clientRoleAudience, + ); } } catch (e) { SmartDialog.showToast('加入频道异常:$e'); @@ -111,9 +125,10 @@ class RoomController extends GetxController { String token, String channelName, int uid, - ClientRoleType roleType + ClientRoleType roleType, ) async { try { + await _fetchRtcChannelDetail(channelName); await RTCManager.instance.joinChannel( token: token, channelId: channelName, @@ -125,9 +140,24 @@ class RoomController extends GetxController { } } + Future _fetchRtcChannelDetail(String channelName) async { + try { + final response = await _networkService.rtcApi.getRtcChannelDetail( + channelName, + ); + final base = response.data; + if (base.isSuccess && base.data != null) { + rtcChannelDetail.value = base.data; + } + } catch (e) { + print('获取 RTC 频道详情失败:$e'); + } + } + /// 发送公屏消息 Future sendChatMessage(String content) async { - final channelName = rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId; + final channelName = + rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId; final result = await _messageService.sendMessage( content: content, @@ -152,8 +182,9 @@ class RoomController extends GetxController { return true; } - final permanentlyDenied = - statuses.values.any((status) => status.isPermanentlyDenied); + final permanentlyDenied = statuses.values.any( + (status) => status.isPermanentlyDenied, + ); if (permanentlyDenied) { SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); await openAppSettings(); @@ -167,4 +198,3 @@ class RoomController extends GetxController { await RTCManager.instance.leaveChannel(); } } - diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index dcd36c9..e242231 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -150,6 +150,7 @@ class Assets { static const String imagesRocket1 = 'assets/images/rocket1.svga'; static const String imagesRocket2 = 'assets/images/rocket2.svga'; static const String imagesRocket3 = 'assets/images/rocket3.svga'; + static const String imagesRoomVideo = 'assets/images/room_video.png'; static const String imagesRose = 'assets/images/rose.png'; static const String imagesRoseBanner = 'assets/images/rose_banner.png'; static const String imagesRoseGift = 'assets/images/rose_gift.png'; diff --git a/lib/network/api_service.g.dart b/lib/network/api_service.g.dart index bfee117..fc7d51d 100644 --- a/lib/network/api_service.g.dart +++ b/lib/network/api_service.g.dart @@ -8,7 +8,7 @@ part of 'api_service.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _ApiService implements ApiService { _ApiService(this._dio, {this.baseUrl, this.errorLogger}) { diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 74a7918..e74c38b 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -3,40 +3,69 @@ class ApiUrls { // 认证相关接口 static const String login = 'dating-agency-uec/authorize/by-captcha'; - static const String getVerificationCode = 'dating-agency-uec/authorize/get/auth-captcha'; - + static const String getVerificationCode = + 'dating-agency-uec/authorize/get/auth-captcha'; + // 用户相关接口 static const String getBaseUserInfo = 'dating-agency-uec/user/get/base-info'; static const String updatePhone = 'dating-agency-uec/user/modify/phone'; - static const String getMarriageInformationDetail = 'dating-agency-service/user/get/dongwo/marriage-information-detail'; - static const String registerMarriageInformation = 'dating-agency-service/user/register/marriage-information'; - static const String getHxUserToken = 'dating-agency-chat-audio/user/get/hx/user/token'; - static const String getApplyTempAuth = 'dating-agency-uec/get/apply-temp-auth'; - static const String saveCertificationAudit = 'dating-agency-service/user/save/certification/audit'; - static const String listVirtualCurrencyProduct = 'dating-agency-mall/user/list/virtual-currency-product'; - static const String getVirtualAccount = 'dating-agency-mall/user/get/virtual-account'; - static const String pageVirtualAccountRecord = 'dating-agency-mall/user/page/virtual-account-record'; - static const String getCertificationList = 'dating-agency-service/user/get/certification/item/all/list'; + static const String getMarriageInformationDetail = + 'dating-agency-service/user/get/dongwo/marriage-information-detail'; + static const String registerMarriageInformation = + 'dating-agency-service/user/register/marriage-information'; + static const String getHxUserToken = + 'dating-agency-chat-audio/user/get/hx/user/token'; + static const String getApplyTempAuth = + 'dating-agency-uec/get/apply-temp-auth'; + static const String saveCertificationAudit = + 'dating-agency-service/user/save/certification/audit'; + static const String listVirtualCurrencyProduct = + 'dating-agency-mall/user/list/virtual-currency-product'; + static const String getVirtualAccount = + 'dating-agency-mall/user/get/virtual-account'; + static const String pageVirtualAccountRecord = + 'dating-agency-mall/user/page/virtual-account-record'; + static const String getCertificationList = + 'dating-agency-service/user/get/certification/item/all/list'; static const String submitOrder = 'dating-agency-mall/user/submit/order'; - static const String getEducationList = 'dating-agency-service/user/get/education/list'; - static const String getIncomeList = 'dating-agency-service/user/get/income/list'; - static const String getMaritalStatusList = 'dating-agency-service/user/get/marital/status/list'; - static const String getPropertyList = 'dating-agency-service/user/get/property/permits'; - static const String getOccupationList = 'dating-agency-service/user/get/occupation/list'; - static const String getSwRtcToken = 'dating-agency-chat-audio/user/get/sw/rtc/token'; - static const String createRtcChannel = 'dating-agency-chat-audio/user/create/rtc-channel'; - static const String editOwnMarriageInformation = 'dating-agency-service/user/edit/own-marriage-information'; - static const String getSwRtmToken = 'dating-agency-chat-audio/user/get/sw/rtm/token'; - static const String listBankCardByIndividual = 'dating-agency-mall/user/list/bank-card/by-individual'; - static const String createBankCardByIndividual = 'dating-agency-mall/user/create/bank-card/by-individual'; - static const String recognizeBankCard = 'dating-agency-uec/user/recognize/bank-card'; - static const String calculateWithdrawServiceFee = 'dating-agency-mall/user/calculate/withdraw-service-fee'; - static const String applyWalletAccountWithdraw = 'dating-agency-mall/user/apply/wallet-account/withdraw'; - static const String pageWithdrawAudit = 'dating-agency-mall/user/page/withdraw-audit'; - static const String getMarriageInformationDetailsById = 'dating-agency-service/user/get/marriage/information/details/byid'; + static const String getEducationList = + 'dating-agency-service/user/get/education/list'; + static const String getIncomeList = + 'dating-agency-service/user/get/income/list'; + static const String getMaritalStatusList = + 'dating-agency-service/user/get/marital/status/list'; + static const String getPropertyList = + 'dating-agency-service/user/get/property/permits'; + static const String getOccupationList = + 'dating-agency-service/user/get/occupation/list'; + static const String getSwRtcToken = + 'dating-agency-chat-audio/user/get/sw/rtc/token'; + static const String createRtcChannel = + 'dating-agency-chat-audio/user/create/rtc-channel'; + static const String editOwnMarriageInformation = + 'dating-agency-service/user/edit/own-marriage-information'; + static const String getSwRtmToken = + 'dating-agency-chat-audio/user/get/sw/rtm/token'; + static const String getRtcChannelDetail = + 'dating-agency-chat-audio/user/get/dating-rtc-channel/detail'; + static const String listBankCardByIndividual = + 'dating-agency-mall/user/list/bank-card/by-individual'; + static const String createBankCardByIndividual = + 'dating-agency-mall/user/create/bank-card/by-individual'; + static const String recognizeBankCard = + 'dating-agency-uec/user/recognize/bank-card'; + static const String calculateWithdrawServiceFee = + 'dating-agency-mall/user/calculate/withdraw-service-fee'; + static const String applyWalletAccountWithdraw = + 'dating-agency-mall/user/apply/wallet-account/withdraw'; + static const String pageWithdrawAudit = + 'dating-agency-mall/user/page/withdraw-audit'; + static const String getMarriageInformationDetailsById = + 'dating-agency-service/user/get/marriage/information/details/byid'; //首页相关接口 - static const String getMarriageList = 'dating-agency-service/user/page/dongwo/marriage-information'; - + static const String getMarriageList = + 'dating-agency-service/user/page/dongwo/marriage-information'; + // 后续可以在此添加更多API端点 -} \ No newline at end of file +} diff --git a/lib/network/home_api.g.dart b/lib/network/home_api.g.dart index 81156a1..84908ed 100644 --- a/lib/network/home_api.g.dart +++ b/lib/network/home_api.g.dart @@ -8,7 +8,7 @@ part of 'home_api.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _HomeApi implements HomeApi { _HomeApi(this._dio, {this.baseUrl, this.errorLogger}); diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 860c19c..da3511f 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -1,4 +1,5 @@ import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; +import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/api_urls.dart'; import 'package:dating_touchme_app/network/response_model.dart'; import 'package:dio/dio.dart'; @@ -14,7 +15,7 @@ abstract class RtcApi { /// 创建实时音视频频道 @GET(ApiUrls.getSwRtcToken) Future>> getSwRtcToken( - @Query('channelId') String channelId, + @Query('channelId') String channelId, ); /// 获取声网 RTM Token @@ -24,5 +25,10 @@ abstract class RtcApi { /// 创建实时音视频频道(返回字符串) @POST(ApiUrls.createRtcChannel) Future>> createRtcChannel(); -} + /// 获取 RTC 频道详情 + @GET(ApiUrls.getRtcChannelDetail) + Future>> getRtcChannelDetail( + @Query('channelId') String channelId, + ); +} diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 8166434..d1b22db 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -8,7 +8,7 @@ part of 'rtc_api.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _RtcApi implements RtcApi { _RtcApi(this._dio, {this.baseUrl, this.errorLogger}); @@ -114,6 +114,40 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>> getRtcChannelDetail( + String channelId, + ) async { + final _extra = {}; + final queryParameters = {r'channelId': channelId}; + final _headers = {}; + const Map? _data = null; + final _options = + _setStreamType>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/get/dating-rtc-channel/detail', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late BaseResponse _value; + try { + _value = BaseResponse.fromJson( + _result.data!, + (json) => RtcChannelDetail.fromJson(json as Map), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/network/user_api.g.dart b/lib/network/user_api.g.dart index 9674381..c841dd3 100644 --- a/lib/network/user_api.g.dart +++ b/lib/network/user_api.g.dart @@ -8,7 +8,7 @@ part of 'user_api.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _UserApi implements UserApi { _UserApi(this._dio, {this.baseUrl, this.errorLogger}); diff --git a/lib/pages/discover/live_room_page.dart b/lib/pages/discover/live_room_page.dart index 3b9295b..370848d 100644 --- a/lib/pages/discover/live_room_page.dart +++ b/lib/pages/discover/live_room_page.dart @@ -89,7 +89,7 @@ class _LiveRoomPageState extends State { // 发送消息 await _roomController.sendChatMessage(content); - + // 清空输入框 _messageController.clear(); message = ''; @@ -159,29 +159,23 @@ class _LiveRoomPageState extends State { child: Column( children: [ SizedBox(height: 10.w), - 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, - ); - }, - ), + Obx(() { + final detail = _roomController.rtcChannelDetail.value; + final anchorInfo = detail?.anchorInfo; + + final userName = anchorInfo!.nickName; + final avatarAsset = anchorInfo.profilePhoto; + const popularityText = '0'; // TODO: 使用真实数据 + + return LiveRoomUserHeader( + userName: userName, + popularityText: popularityText, + avatarAsset: avatarAsset, + ); + }), SizedBox(height: 7.w), LiveRoomAnchorShowcase(), SizedBox(height: 5.w), - const LiveRoomSeatList(), - SizedBox(height: 5.w), const LiveRoomActiveSpeaker(), SizedBox(height: 9.w), const LiveRoomNoticeChatPanel(), diff --git a/lib/rtc/rtm_manager.dart b/lib/rtc/rtm_manager.dart index 2758b5c..684c21d 100644 --- a/lib/rtc/rtm_manager.dart +++ b/lib/rtc/rtm_manager.dart @@ -63,7 +63,8 @@ class RTMManager { await dispose(); final (status, client) = await RTM(appId, userId, config: config); - print('RTM初始化成功'); + print('----------RTM $userId'); + print(status.error ? '❌ RTM 初始化失败' : '✅ RTM 初始化成功'); if (status.error) { onOperationError?.call(status); @@ -74,13 +75,13 @@ class RTMManager { _currentUserId = userId; _isInitialized = true; _registerClientListeners(); - // final response = await _networkService.rtcApi.getSwRtmToken(); - // // 处理响应 - // if (response.data.isSuccess) { - await login('007eJxTYIhoZ/m/gMf0gdWv1PV2R6I/Lpry06W77sOR2BDuFv89JeEKDCbJRqmJlinJSSbJpiYmBqaWxokmhhYpaQZpKSmmhkYpE9/IZgrwMTDYOB4rZ2RgYmAEQhCfkcEcAPTmHg4='); - // } else { - // SmartDialog.showToast(response.data.message); - // } + final response = await _networkService.rtcApi.getSwRtmToken(); + // 处理响应 + if (response.data.isSuccess) { + await login(response.data.data!.token); + } else { + SmartDialog.showToast(response.data.message); + } return true; } diff --git a/lib/widget/live/live_room_notice_chat_panel.dart b/lib/widget/live/live_room_notice_chat_panel.dart index f35934e..f5be64f 100644 --- a/lib/widget/live/live_room_notice_chat_panel.dart +++ b/lib/widget/live/live_room_notice_chat_panel.dart @@ -9,7 +9,8 @@ class LiveRoomNoticeChatPanel extends StatefulWidget { const LiveRoomNoticeChatPanel({super.key}); @override - State createState() => _LiveRoomNoticeChatPanelState(); + State createState() => + _LiveRoomNoticeChatPanelState(); } class _LiveRoomNoticeChatPanelState extends State { @@ -30,7 +31,6 @@ class _LiveRoomNoticeChatPanelState extends State { @override Widget build(BuildContext context) { - return Container( height: 230.w, padding: EdgeInsets.only(left: 13.w, right: 9.w), @@ -73,14 +73,52 @@ class _LiveRoomNoticeChatPanelState extends State { }), ), SizedBox(width: 18.w), - Image.asset( - Assets.imagesAd, - width: 73.w, - fit: BoxFit.cover, + Container( + width: 120.w, + height: 55.w, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.w), + gradient: const LinearGradient( + colors: [Color(0xFF7C63FF), Color(0xFF987CFF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Row( + children: [ + Image.asset( + Assets.imagesRoomVideo, + width: 26.w, + ), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '免费连麦', + style: TextStyle( + fontSize: 13.w, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 2.w), + Text( + '剩余2张相亲卡', + style: TextStyle( + fontSize: 9.w, + color: Colors.white.withOpacity(0.8), + ), + ), + ], + ), + ], + ), ), ], ), ); } } - diff --git a/lib/widget/live/live_room_user_header.dart b/lib/widget/live/live_room_user_header.dart index 20f25b5..baae02c 100644 --- a/lib/widget/live/live_room_user_header.dart +++ b/lib/widget/live/live_room_user_header.dart @@ -39,8 +39,7 @@ class LiveRoomUserHeader extends StatelessWidget { child: Row( children: [ // 支持网络图片和本地资源 - avatarAsset.startsWith('http://') || avatarAsset.startsWith('https://') - ? ClipOval( + ClipOval( child: Image.network( avatarAsset, width: 34.w, @@ -54,11 +53,6 @@ class LiveRoomUserHeader extends StatelessWidget { ); }, ), - ) - : Image.asset( - avatarAsset, - width: 34.w, - height: 34.w, ), SizedBox(width: 7.w), Column( @@ -72,50 +66,8 @@ class LiveRoomUserHeader extends StatelessWidget { color: Colors.white, ), ), - SizedBox(height: 2.w), - Row( - children: [ - Image.asset( - fireIconAsset, - width: 10.w, - height: 12.w, - ), - SizedBox(width: 4.w), - Text( - popularityText, - style: TextStyle( - fontSize: 10.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ], - ), ], ), - SizedBox(width: 15.w), - GestureDetector( - onTap: onFollowTap, - child: Container( - width: 47.w, - height: 27.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(27.w)), - color: const Color.fromRGBO(253, 43, 84, 1), - ), - child: Center( - child: Text( - '关注', - style: TextStyle( - fontSize: 13.w, - color: Colors.white, - fontWeight: FontWeight.w500, - height: 1, - ), - ), - ), - ), - ), ], ), ),