From 0da00c4d8ca23dbc6f0be344242511b659f3b1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=90=E8=B4=A4?= Date: Thu, 29 Jan 2026 18:00:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=BA=A6=E5=85=8B=E9=A3=8E=E6=8E=A7=E5=88=B6=EF=BC=8C=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=9B=B4=E6=92=AD=E9=97=B4=E5=A4=96=E8=A7=82=E4=BC=97?= =?UTF-8?q?=E9=82=80=E8=AF=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/controller/discover/room_controller.dart | 129 +++++++++++++++- lib/im/im_manager.dart | 95 ++++++++++-- lib/model/discover/live_abstract_data.dart | 15 ++ lib/model/mine/chat_static_data.dart | 7 +- lib/model/rtc/rtc_channel_detail.dart | 4 +- lib/network/api_urls.dart | 3 + lib/network/rtc_api.dart | 11 ++ lib/network/rtc_api.g.dart | 35 +++++ lib/pages/discover/settlement_page.dart | 39 +++-- lib/pages/mine/my_wallet_page.dart | 3 +- lib/rtc/rtc_manager.dart | 3 + lib/widget/live/audience_item.dart | 8 +- lib/widget/live/kick_list.dart | 16 +- .../live/live_room_anchor_showcase.dart | 29 +++- lib/widget/live/live_room_chat_item.dart | 5 +- .../live/live_room_invitation_list.dart | 77 +++++----- lib/widget/live/live_room_user_header.dart | 141 ++++++++++++++++-- .../live/live_room_user_profile_dialog.dart | 2 +- 18 files changed, 524 insertions(+), 98 deletions(-) create mode 100644 lib/model/discover/live_abstract_data.dart diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index 5e17d91..4fcd7c9 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -5,8 +5,10 @@ import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/controller/overlay_controller.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/model/discover/task_data.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; +import 'package:dating_touchme_app/model/mine/chat_static_data.dart'; import 'package:dating_touchme_app/model/rtc/link_mic_card_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; @@ -108,6 +110,49 @@ class RoomController extends GetxController with WidgetsBindingObserver { chatMessages.clear(); } + final durationMins = 0.obs; + + getDurationMins() async { + try { + final channelId = RTCManager.instance.currentChannelId; + final response = await _networkService.rtcApi.userGetRtcChannelAbstract(channelId: channelId ?? ""); + if (response.data.isSuccess && response.data.data != null) { + durationMins.value = response.data.data?.durationMins ?? 0; + } else { + + // 响应失败,抛出异常 + throw Exception(response.data.message ?? '获取数据失败'); + } + } catch(e){ + print('钱包数据获取失败: $e'); + SmartDialog.showToast('钱包数据获取失败'); + rethrow; + + } + } + + + final consumption = ChatStaticData(todayLiveDurationMins: 0).obs; + + getLiveData() async { + try { + final channelId = RTCManager.instance.currentChannelId; + final response = await _networkService.userApi.getChatStaticsInfo(); + if (response.data.isSuccess && response.data.data != null) { + consumption.value = response.data.data!; + } else { + + // 响应失败,抛出异常 + throw Exception(response.data.message ?? '获取数据失败'); + } + } catch(e){ + print('钱包数据获取失败: $e'); + SmartDialog.showToast('钱包数据获取失败'); + rethrow; + + } + } + final tab = 0.obs; changeTab(int i) async { @@ -126,6 +171,7 @@ class RoomController extends GetxController with WidgetsBindingObserver { audienceList.clear(); await getFriendList(); } + update(); listRefreshController.finishRefresh(IndicatorResult.success); listRefreshController.finishLoad(IndicatorResult.none); } @@ -155,6 +201,7 @@ class RoomController extends GetxController with WidgetsBindingObserver { } else { listRefreshController.finishLoad(IndicatorResult.noMore); } + update(); } else { // 响应失败,抛出异常 @@ -186,6 +233,7 @@ class RoomController extends GetxController with WidgetsBindingObserver { } else { listRefreshController.finishLoad(IndicatorResult.noMore); } + update(); } else { // 响应失败,抛出异常 @@ -232,6 +280,7 @@ class RoomController extends GetxController with WidgetsBindingObserver { } else { listRefreshController.finishLoad(IndicatorResult.noMore); } + update(); } else { // 响应失败,抛出异常 @@ -1211,13 +1260,37 @@ class RoomController extends GetxController with WidgetsBindingObserver { } } - inviteMic(List selectUserId) async { + + int calculateAge(String birthdayStr) { + final birthday = DateTime.parse(birthdayStr); // 自动识别 1996-1-20 + final today = DateTime.now(); + + int age = today.year - birthday.year; + + // 如果今年生日还没过,年龄要减 1 + if (today.month < birthday.month || + (today.month == birthday.month && today.day < birthday.day)) { + age--; + } + + return age; + } + + inviteMic(List> selectUserId) async { try { final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { + + final idIsNull = selectUserId + .where((e) => e["uid"] == 0) + .toList(); + final idNotNull = selectUserId + .where((e) => e["uid"] != 0) + .toList(); + final userIds = idNotNull.map((e) => e["userId"]).toList(); final messageData = { 'type': 'invite_to_mic', - 'userId': selectUserId, + 'userId': userIds, 'operatorId': GlobalData().userData?.id ?? '', 'operatorName': GlobalData().userData?.nickName ?? '', }; @@ -1226,6 +1299,58 @@ class RoomController extends GetxController with WidgetsBindingObserver { channelName: channelName, message: json.encode(messageData), ); + + final others = idIsNull.map((e) => e["userId"]).toList(); + + final channelId = RTCManager.instance.currentChannelId; + if (channelId == null || channelId.isEmpty) { + SmartDialog.showToast('频道ID不存在'); + return; + } + + // 获取房间详情 + final channelDetail = rtcChannelDetail.value; + final anchorName = channelDetail?.anchorInfo?.nickName ?? '主持人'; + final anchorAvatar = channelDetail?.anchorInfo?.profilePhoto ?? ''; + + // 确保头像和封面URL不为空且格式正确 + final cleanedAvatar = anchorAvatar.trim().replaceAll('`', ''); + final cleanedCover = cleanedAvatar; // 封面使用主持人头像 + + // 构建消息参数,包含房间信息(不需要 type 字段,因为 event 已经是 'live_room_invite') + final messageParams = { + 'channelId': channelId, + 'anchorAvatar': cleanedAvatar, + 'anchorName': anchorName, + 'coverImage': cleanedCover, // 封面使用主持人头像 + 'genderCode': GlobalData().userData?.genderCode.toString() ?? "", + "age": calculateAge( + (GlobalData().userData?.birthDate != null && GlobalData().userData!.birthDate!.isNotEmpty) ? + (GlobalData().userData?.birthDate ?? "") : GlobalData().userData?.birthYear != null && GlobalData().userData!.birthYear!.isNotEmpty ? + "${GlobalData().userData?.birthYear}-01-01" : "").toString(), + "provinceName": GlobalData().userData?.provinceName ?? "", + }; + + print('📤 [LiveRoomGuestListDialog] 发送房间邀请消息: anchorAvatar=$cleanedAvatar, coverImage=$cleanedCover'); + + others.forEach((e) async { + // 发送自定义消息 + final result = await IMManager.instance.sendCustomMessage( + e, + 'live_room_invite', + messageParams, + ); + + if (result != null) { + SmartDialog.showToast('邀请已发送'); + SmartDialog.dismiss(); + } else { + SmartDialog.showToast('邀请发送失败,id$e'); + } + }); + + + print('✅ 邀请消息已发送: 邀请用户ID $selectUserId'); // 刷新频道详情 diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index c266b31..65aa58c 100644 --- a/lib/im/im_manager.dart +++ b/lib/im/im_manager.dart @@ -1,5 +1,7 @@ import 'dart:io'; import 'dart:async'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart'; @@ -11,13 +13,17 @@ import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import '../controller/discover/room_controller.dart'; import '../controller/message/conversation_controller.dart'; import '../controller/message/chat_controller.dart'; import '../controller/global.dart'; +import '../controller/overlay_controller.dart'; import '../generated/assets.dart'; +import '../pages/discover/live_room_page.dart'; import '../pages/mine/login_page.dart'; import '../pages/message/chat_page.dart'; import '../network/user_api.dart'; +import '../rtc/rtc_manager.dart'; import '../widget/message/message_notification_dialog.dart'; import '../widget/message/video_call_invite_dialog.dart'; import '../pages/message/video_call_page.dart'; @@ -234,7 +240,6 @@ class IMManager { if(message.body.type == MessageType.CUSTOM){ final body = message.body as EMCustomMessageBody; if(body.event == "live_room_invite"){ - print(23232323232); SmartDialog.show( alignment: Alignment.center, maskColor: Colors.black.withOpacity(0.5), @@ -270,10 +275,32 @@ class IMManager { Positioned( left: 38.5.w, top: 0, - child: Image.asset( - Assets.imagesUserAvatar, - width: 100.w, - height: 100.w, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(100.w)), + child: CachedNetworkImage( + imageUrl: "${ body.params?["coverImage"] ?? ""}?x-oss-process=image/format,webp/resize,w_240", + + width: 100.w, + height: 100.w, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + color: Colors.white38, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 1.w, + color: Colors.grey, + ), + ), + ), + errorWidget: (context, url, error) => + Image.asset( + Assets.imagesUserAvatar, + + width: 100.w, + height: 100.w, + fit: BoxFit.cover, + ), + ), ), ), Positioned( @@ -288,7 +315,7 @@ class IMManager { ), SizedBox(height: 10.w,), Text( - "开心的橘子", + body.params?["anchorName"] ?? "", style: TextStyle( fontSize: 18.w, ), @@ -297,7 +324,7 @@ class IMManager { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if(false) Container( + if(body.params?["genderCode"] == "1") Container( width: 33.w, height: 13.w, margin: EdgeInsets.only(right: 6.w), @@ -315,7 +342,7 @@ class IMManager { ), SizedBox(width: 2.w,), Text( - "19", + body.params?["age"] ?? "", style: TextStyle( fontSize: 11.w, color: const Color.fromRGBO(255, 66, 236, 1) @@ -324,7 +351,7 @@ class IMManager { ], ), ), - Container( + if(body.params?["genderCode"] == "0") Container( width: 33.w, height: 13.w, margin: EdgeInsets.only(right: 6.w), @@ -342,7 +369,7 @@ class IMManager { ), SizedBox(width: 2.w,), Text( - "19", + body.params?["age"] ?? "", style: TextStyle( fontSize: 11.w, color: const Color.fromRGBO(120, 140, 255, 1) @@ -358,7 +385,7 @@ class IMManager { color: const Color.fromRGBO(245, 247, 255, 1) ), child: Text( - "北京", + body.params?["provinceName"] ?? "", style: TextStyle( fontSize: 12.w, fontWeight: FontWeight.w400 @@ -408,7 +435,12 @@ class IMManager { ) ], ), - ), + ).onTap(() { + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + }), Container( width: 128.w, height: 40.w, @@ -434,7 +466,44 @@ class IMManager { ) ], ), - ), + ).onTap(() async { + + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + // 获取当前直播间ID + final currentChannelId = RTCManager.instance.currentChannelId; + // 隐藏小窗口 + if (Get.isRegistered()) { + final overlayController = Get.find(); + overlayController.hide(); + } + + // 获取 RoomController + final roomController = Get.isRegistered() + ? Get.find() + : Get.put(RoomController()); + + // 如果频道ID一致,取消小窗口并进入直播间 + if (currentChannelId != null && currentChannelId == body.params?["channelId"]) { + // 如果当前不在 LiveRoomPage,则导航到 LiveRoomPage + final currentRoute = Get.currentRoute; + if (currentRoute != '/LiveRoomPage' && !currentRoute.contains('LiveRoomPage')) { + Get.to(() => const LiveRoomPage(id: 0)); + } + return; + } + + // 如果不一致,退出当前直播间并进入新的直播间 + if (currentChannelId != null && currentChannelId.isNotEmpty) { + // 退出当前直播间 + await roomController.leaveChannel(); + } + + // 加入新的直播间 + await roomController.joinChannel(body.params?["channelId"] ?? ""); + }), ], ) ], diff --git a/lib/model/discover/live_abstract_data.dart b/lib/model/discover/live_abstract_data.dart new file mode 100644 index 0000000..66804cf --- /dev/null +++ b/lib/model/discover/live_abstract_data.dart @@ -0,0 +1,15 @@ +class LiveAbstractData { + int? durationMins; + + LiveAbstractData({this.durationMins}); + + LiveAbstractData.fromJson(Map json) { + durationMins = json['durationMins']; + } + + Map toJson() { + final Map data = new Map(); + data['durationMins'] = this.durationMins; + return data; + } +} diff --git a/lib/model/mine/chat_static_data.dart b/lib/model/mine/chat_static_data.dart index 3bbf42e..495199d 100644 --- a/lib/model/mine/chat_static_data.dart +++ b/lib/model/mine/chat_static_data.dart @@ -4,8 +4,9 @@ class ChatStaticData { int? liveDurationMins; int? liveConsumptionAmount; int? todayCrossMicCount; + int? todayLiveDurationMins; - ChatStaticData({this.id, this.liveDurationMins, this.todayCrossMicCount, this.liveConsumptionAmount}); + ChatStaticData({this.id, this.liveDurationMins, this.todayCrossMicCount, this.liveConsumptionAmount, this.todayLiveDurationMins}); // 从JSON映射创建实例 factory ChatStaticData.fromJson(Map json) { @@ -14,6 +15,7 @@ class ChatStaticData { liveDurationMins: json['liveDurationMins'] ?? 0, liveConsumptionAmount: json['liveConsumptionAmount'] ?? 0, todayCrossMicCount: json['todayCrossMicCount'] ?? 0, + todayLiveDurationMins: json['todayLiveDurationMins'] ?? 0, ); } @@ -24,12 +26,13 @@ class ChatStaticData { 'liveDurationMins': liveDurationMins, 'todayCrossMicCount': todayCrossMicCount, 'liveConsumptionAmount': liveConsumptionAmount, + 'todayLiveDurationMins': todayLiveDurationMins, }; } @override String toString() { - return 'LoginData(id: $id, liveDurationMins: $liveDurationMins, liveConsumptionAmount: $liveConsumptionAmount)'; + return 'LoginData(id: $id, liveDurationMins: $liveDurationMins, liveConsumptionAmount: $liveConsumptionAmount, todayLiveDurationMins: $todayLiveDurationMins)'; } } \ No newline at end of file diff --git a/lib/model/rtc/rtc_channel_detail.dart b/lib/model/rtc/rtc_channel_detail.dart index f3c4800..2f4061e 100644 --- a/lib/model/rtc/rtc_channel_detail.dart +++ b/lib/model/rtc/rtc_channel_detail.dart @@ -45,11 +45,11 @@ class RtcSeatUserInfo { final int genderCode; final int seatNumber; final bool isFriend; - final bool isMicrophoneOn; + bool isMicrophoneOn; final bool isVideoOn; final int? uid; - const RtcSeatUserInfo({ + RtcSeatUserInfo({ required this.miId, required this.userId, required this.nickName, diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 500abb4..6178e10 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -169,4 +169,7 @@ class ApiUrls { static const String listMatchmakerTask = 'dating-agency-mall/user/get/user-task-complete'; + + static const String userGetRtcChannelAbstract = + 'dating-agency-chat-audio/user/get/rtc-channel/abstract'; } diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 05070c8..764e1aa 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -11,6 +11,8 @@ import 'package:dating_touchme_app/network/response_model.dart'; import 'package:dio/dio.dart'; import 'package:retrofit/retrofit.dart'; +import '../model/discover/live_abstract_data.dart'; + part 'rtc_api.g.dart'; /// RTC 相关接口 @@ -158,4 +160,13 @@ abstract class RtcApi { @Query('channelId') required String channelId, } ); + + + /// 获取用户道具连麦卡片 + @GET(ApiUrls.userGetRtcChannelAbstract) + Future>> userGetRtcChannelAbstract( + { + @Query('channelId') required String channelId, + } + ); } diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 88b81b4..d9460aa 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -872,6 +872,41 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>> + userGetRtcChannelAbstract({required 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/rtc-channel/abstract', + 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) => LiveAbstractData.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/pages/discover/settlement_page.dart b/lib/pages/discover/settlement_page.dart index dabaf60..fcf1598 100644 --- a/lib/pages/discover/settlement_page.dart +++ b/lib/pages/discover/settlement_page.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:dating_touchme_app/components/page_appbar.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; @@ -9,6 +10,7 @@ import 'package:get/get.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../controller/discover/room_controller.dart'; +import '../../controller/global.dart'; class SettlementPage extends StatefulWidget { const SettlementPage({super.key}); @@ -46,7 +48,7 @@ class _SettlementPageState extends State { child: Column( children: [ Text( - "开心的橘子", + GlobalData().userData!.nickName ?? "", style: TextStyle( fontSize: 14.w, color: Colors.white @@ -83,7 +85,7 @@ class _SettlementPageState extends State { ), children: [ TextSpan( - text: "0.35", + text: ((_roomController.consumption.value.todayLiveDurationMins ?? 0) / 60).toStringAsFixed(2), style: TextStyle( fontSize: 23.w, ) @@ -117,7 +119,7 @@ class _SettlementPageState extends State { ), children: [ TextSpan( - text: "0.35", + text: ((_roomController.consumption.value.todayLiveDurationMins ?? 0) / 60).toStringAsFixed(2), style: TextStyle( fontSize: 23.w, ) @@ -132,7 +134,7 @@ class _SettlementPageState extends State { ), ), Text( - "今日总开播", + "今日有效开播", style: TextStyle( fontSize: 12.w, color: const Color.fromRGBO(117, 98, 249, 1) @@ -151,13 +153,13 @@ class _SettlementPageState extends State { ), children: [ TextSpan( - text: "0.35", + text: "0.0", style: TextStyle( fontSize: 23.w, ) ), TextSpan( - text: "小时", + text: "元", style: TextStyle( fontSize: 12.w, ) @@ -166,7 +168,7 @@ class _SettlementPageState extends State { ), ), Text( - "今日总开播", + "今日收入", style: TextStyle( fontSize: 12.w, color: const Color.fromRGBO(117, 98, 249, 1) @@ -189,10 +191,29 @@ class _SettlementPageState extends State { ), child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(86.w)), - child: Image.asset( - Assets.imagesUserAvatar, + child: CachedNetworkImage( + imageUrl: "${ GlobalData().userData!.profilePhoto ?? ""}?x-oss-process=image/format,webp/resize,w_240", + width: 86.w, height: 86.w, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + color: Colors.white38, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 1.w, + color: Colors.grey, + ), + ), + ), + errorWidget: (context, url, error) => + Image.asset( + Assets.imagesUserAvatar, + + width: 86.w, + height: 86.w, + fit: BoxFit.cover, + ), ), ), ), diff --git a/lib/pages/mine/my_wallet_page.dart b/lib/pages/mine/my_wallet_page.dart index f70e1e8..410c1dd 100644 --- a/lib/pages/mine/my_wallet_page.dart +++ b/lib/pages/mine/my_wallet_page.dart @@ -353,12 +353,13 @@ class _InfoItemState extends State { widget.item.tradeType == 109 ? "管道收益" : widget.item.tradeType == 110 ? "红娘新手任务奖励" : widget.item.tradeType == 111 ? "聊天收益" : - widget.item.tradeType == 112 ? "礼物收益" : + widget.item.tradeType == 112 ? "聊天礼物收益" : widget.item.tradeType == 113 ? "邀请分成" : widget.item.tradeType == 114 ? "1V1语音" : widget.item.tradeType == 115 ? "1V1视频" : widget.item.tradeType == 116 ? "连麦收益" : widget.item.tradeType == 117 ? "任务奖励" : + widget.item.tradeType == 118 ? "直播礼物收益" : widget.item.tradeType == 201 ? "平台服务费" : widget.item.tradeType == 202 ? "提现" : ""}", style: TextStyle( diff --git a/lib/rtc/rtc_manager.dart b/lib/rtc/rtc_manager.dart index fb271c4..f8e9544 100644 --- a/lib/rtc/rtc_manager.dart +++ b/lib/rtc/rtc_manager.dart @@ -150,6 +150,9 @@ class RTCManager { channelName: _currentChannelId ?? '', message: json.encode({'type': 'join_room', 'uid': _currentUid}), ); + + final roomController = Get.find(); + roomController.sendChatMessage("新朋友来了"); Get.to(() => const LiveRoomPage(id: 0)); } } diff --git a/lib/widget/live/audience_item.dart b/lib/widget/live/audience_item.dart index 51da975..d3a4067 100644 --- a/lib/widget/live/audience_item.dart +++ b/lib/widget/live/audience_item.dart @@ -8,8 +8,8 @@ import '../../model/discover/audience_list_data.dart'; class AudienceItem extends StatefulWidget { final Records item; - final List selectUserId; - final Function(String) selectChange; + final List> selectUserId; + final Function(String, int) selectChange; const AudienceItem({super.key, required this.item, required this.selectUserId, required this.selectChange}); @override @@ -65,9 +65,9 @@ class _AudienceItemState extends State { ), Checkbox( - value: widget.selectUserId.contains(widget.item.userId), + value: widget.selectUserId.indexWhere((e) => e["userId"] == widget.item.userId) != -1, onChanged: (value) { - widget.selectChange(widget.item.userId ?? ""); + widget.selectChange(widget.item.userId ?? "", widget.item.userManagementId ?? 0); }, activeColor: const Color.fromRGBO(117, 98, 249, 1), side: const BorderSide(color: Colors.grey), diff --git a/lib/widget/live/kick_list.dart b/lib/widget/live/kick_list.dart index e678fc6..09c073a 100644 --- a/lib/widget/live/kick_list.dart +++ b/lib/widget/live/kick_list.dart @@ -21,14 +21,13 @@ class _KickListState extends State { - List selectUserId = []; + List> selectUserId = []; - selectChange(String userId){ - if (selectUserId.contains(userId)) { - selectUserId.remove(userId); - } else { - selectUserId.add(userId); - } + selectChange(String userId, int uid){ + final index = selectUserId.indexWhere((e) => e["userId"] == userId); + index == -1 + ? selectUserId.add({"userId": userId, "uid": uid}) + : selectUserId.removeAt(index); setState(() { }); @@ -167,9 +166,8 @@ class _KickListState extends State { ), ), ).onTap(() async { - final selectedA = _roomController.audienceList.where((item) => selectUserId.contains(item.userId)).toList(); final List dList = - selectedA.map((item) => item.userManagementId.toString()).toList(); + selectUserId.map((item) => item["uid"].toString()).toList(); await _roomController.kickUser(dList); // 隐藏键盘 diff --git a/lib/widget/live/live_room_anchor_showcase.dart b/lib/widget/live/live_room_anchor_showcase.dart index 59fa0ac..d764406 100644 --- a/lib/widget/live/live_room_anchor_showcase.dart +++ b/lib/widget/live/live_room_anchor_showcase.dart @@ -27,7 +27,7 @@ class _LiveRoomAnchorShowcaseState extends State { @override - Widget build(BuildContext context) { + build(BuildContext context) { return ValueListenableBuilder( valueListenable: _rtcManager.channelJoinedNotifier, builder: (context, joined, _) { @@ -155,7 +155,21 @@ class _LiveRoomAnchorShowcaseState extends State { height: 11.w, ), ), - ), + ).onTap(() { + if(_roomController.currentRole == CurrentRole.broadcaster){ + _roomController + .rtcChannelDetail + .value?.anchorInfo?.isMicrophoneOn = !(_roomController + .rtcChannelDetail + .value?.anchorInfo?.isMicrophoneOn ?? false); + RTCManager.instance.muteLocalAudio(!(_roomController + .rtcChannelDetail + .value?.anchorInfo?.isMicrophoneOn ?? false)); + setState(() { + + }); + } + }), ], ), ), @@ -367,7 +381,16 @@ class _LiveRoomAnchorShowcaseState extends State { height: 11.w, ), ), - ), + ).onTap(() { + if((isLeft && _roomController.currentRole == CurrentRole.maleAudience) || (!isLeft && _roomController.currentRole == CurrentRole.femaleAudience)){ + userInfo.isMicrophoneOn = !(userInfo.isMicrophoneOn ?? false); + + RTCManager.instance.muteLocalAudio(!(userInfo.isMicrophoneOn ?? false)); + setState(() { + + }); + } + }), SizedBox(width: 5.w), Text( userInfo.nickName, diff --git a/lib/widget/live/live_room_chat_item.dart b/lib/widget/live/live_room_chat_item.dart index 4205b43..99a0cad 100644 --- a/lib/widget/live/live_room_chat_item.dart +++ b/lib/widget/live/live_room_chat_item.dart @@ -107,7 +107,7 @@ class LiveRoomChatItem extends StatelessWidget { text: TextSpan( children: [ TextSpan( - text: "${message.userName}:", + text: "${message.userName}${message.content != "新朋友来了" ? ":" : " "}", style: TextStyle( fontSize: 11.w, color: const Color.fromRGBO(155, 138, 246, 1), @@ -115,7 +115,8 @@ class LiveRoomChatItem extends StatelessWidget { ), TextSpan( text: message.content, - style: TextStyle(fontSize: 11.w, color: Colors.white), + style: TextStyle(fontSize: 11.w, + color: message.content != "新朋友来了" ? Colors.white : const Color.fromRGBO(144, 144, 144, 1)), ), ], ), diff --git a/lib/widget/live/live_room_invitation_list.dart b/lib/widget/live/live_room_invitation_list.dart index 67a2f63..8aa9f10 100644 --- a/lib/widget/live/live_room_invitation_list.dart +++ b/lib/widget/live/live_room_invitation_list.dart @@ -33,14 +33,13 @@ class _LiveRoomInvitationListState extends State with Ti _tabController = TabController(length: 3, vsync: this); } - List selectUserId = []; - - selectChange(String userId){ - if (selectUserId.contains(userId)) { - selectUserId.remove(userId); - } else { - selectUserId.add(userId); - } + List> selectUserId = []; + + selectChange(String userId, int uid){ + final index = selectUserId.indexWhere((e) => e["userId"] == userId); + index == -1 + ? selectUserId.add({"userId": userId, "uid": uid}) + : selectUserId.removeAt(index); setState(() { }); @@ -129,36 +128,38 @@ class _LiveRoomInvitationListState extends State with Ti await _roomController.getFriendList(); } }, - child: ListView.separated( - // 关键:始终允许滚动,即使内容不足 - // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 - padding: EdgeInsets.only(left: 12, right: 12), - itemBuilder: (context, index) { - // 空数据状态 - if (_roomController.audienceList.isEmpty && index == 0) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('暂无数据'), - ], - ), - ); - } - // 数据项 - final item = _roomController.audienceList[index]; - return AudienceItem(item: item, selectUserId: selectUserId, selectChange: selectChange,); - }, - separatorBuilder: (context, index) { - // 空状态或加载状态时不显示分隔符 - if (_roomController.audienceList.isEmpty) { - return const SizedBox.shrink(); - } - return const SizedBox(height: 12); - }, - // 至少显示一个 item(用于显示加载或空状态) - itemCount: _roomController.audienceList.isEmpty ? 1 : _roomController.audienceList.length, - ), + child: Obx(() { + return ListView.separated( + // 关键:始终允许滚动,即使内容不足 + // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 + padding: EdgeInsets.only(left: 12, right: 12), + itemBuilder: (context, index) { + // 空数据状态 + if (_roomController.audienceList.isEmpty && index == 0) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('暂无数据'), + ], + ), + ); + } + // 数据项 + final item = _roomController.audienceList[index]; + return AudienceItem(item: item, selectUserId: selectUserId, selectChange: selectChange,); + }, + separatorBuilder: (context, index) { + // 空状态或加载状态时不显示分隔符 + if (_roomController.audienceList.isEmpty) { + return const SizedBox.shrink(); + } + return const SizedBox(height: 12); + }, + // 至少显示一个 item(用于显示加载或空状态) + itemCount: _roomController.audienceList.isEmpty ? 1 : _roomController.audienceList.length, + ); + }), ), ) ], diff --git a/lib/widget/live/live_room_user_header.dart b/lib/widget/live/live_room_user_header.dart index 244677d..86664aa 100644 --- a/lib/widget/live/live_room_user_header.dart +++ b/lib/widget/live/live_room_user_header.dart @@ -1,5 +1,6 @@ import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/controller/overlay_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/pages/discover/live_end_page.dart'; import 'package:dating_touchme_app/pages/discover/settlement_page.dart'; @@ -196,20 +197,136 @@ class LiveRoomUserHeader extends StatelessWidget { GestureDetector( onTap: () async { - await roomController.leaveChannel(); + await roomController.getDurationMins(); // 隐藏 overlay SmartDialog.dismiss(); - // 退出房间时清空RTM消息 - if (Get.isRegistered()) { - final roomController = Get.find(); - roomController.chatMessages.clear(); - } - // 如果还没有执行 pop,手动调用 Get.back() - Get.off(() => SettlementPage()); - // 等待页面关闭后再显示小窗口,确保小窗口能正确显示 - Future.delayed(const Duration(milliseconds: 200), () { - overlayController.hide(); - }); + + SmartDialog.show( + onDismiss: (){ + roomController.setDialogDismiss(false); + }, + builder: (context) { + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(16.w)), + child: Material( + color: Colors.white, + child: Container( + width: 311.w, + height: 189.w, + padding: EdgeInsets.symmetric( + vertical: 20.w, + horizontal: 27.w + ), + child: Column( + children: [ + Text( + "您当前正在视频相亲中\n" + "确定要退出吗?", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.w + ), + ), + Container( + margin: EdgeInsets.only(top: 9.w, bottom: 18.w), + height: 23.w, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, // 90deg = 从左到右 + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(117, 98, 249, 0), + Color.fromRGBO(117, 98, 249, 0.2), + Color.fromRGBO(117, 98, 249, 0.2), + Color.fromRGBO(117, 98, 249, 0.2), + Color.fromRGBO(117, 98, 249, 0), + ], + stops: [ + 0.0, + 0.2931, + 0.5389, + 0.7708, + 1.0, + ], + ), + ), + child: Center( + child: Text( + "今日有效露脸开播时长${roomController.durationMins.value}分钟", + style: TextStyle( + fontSize: 12.w, + color: const Color.fromRGBO(117, 98, 249, 1) + ), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 128.w, + height: 40.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12.w)), + color: const Color.fromRGBO(237, 237, 237, 1) + ), + child: Center( + child: Text( + "取消", + style: TextStyle( + fontSize: 15.w, + fontWeight: FontWeight.w400 + ), + ), + ), + ).onTap(() { + + SmartDialog.dismiss(); + }), + Container( + width: 128.w, + height: 40.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12.w)), + color: const Color.fromRGBO(117, 98, 249, 1) + ), + child: Center( + child: Text( + "确认", + style: TextStyle( + color: Colors.white, + fontSize: 15.w, + fontWeight: FontWeight.w400 + ), + ), + ), + ).onTap(() async { + // 退出房间时清空RTM消息 + await roomController.getLiveData(); + await roomController.leaveChannel(); + SmartDialog.dismiss(); + if (Get.isRegistered()) { + final roomController = Get.find(); + roomController.chatMessages.clear(); + } + + // 如果还没有执行 pop,手动调用 Get.back() + Get.off(() => SettlementPage()); + // 等待页面关闭后再显示小窗口,确保小窗口能正确显示 + Future.delayed(const Duration(milliseconds: 200), () { + overlayController.hide(); + }); + + }), + ], + ) + ], + ), + ), + ), + ); + }, + ); }, child: Row( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/widget/live/live_room_user_profile_dialog.dart b/lib/widget/live/live_room_user_profile_dialog.dart index 442fe32..290e5e9 100644 --- a/lib/widget/live/live_room_user_profile_dialog.dart +++ b/lib/widget/live/live_room_user_profile_dialog.dart @@ -61,7 +61,7 @@ void showUserProfileDialog( ), ), ).onTap(() async { - await roomController.inviteMic([message.userId]); + await roomController.inviteMic([{"userId": message.userId, "uid": message.uid}]); // 隐藏键盘 FocusScope.of(context).unfocus();