diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index e34f3ad..a01dbc6 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -3,6 +3,9 @@ import 'dart:convert'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; 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/model/discover/task_data.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/rtc/link_mic_card_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; @@ -14,6 +17,7 @@ import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:dating_touchme_app/service/live_chat_message_service.dart'; import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -239,6 +243,28 @@ class RoomController extends GetxController with WidgetsBindingObserver { } } + final taskData = TaskData().obs; + + getTaskData() async { + try{ + final response = await _networkService.userApi.userGetUserTaskComplete( + taskType: 1, + ); + if (response.data.isSuccess && response.data.data != null) { + final data = response.data.data ?? TaskData(); + taskData.value = data; + } else { + + // 响应失败,抛出异常 + throw Exception(response.data.message ?? '获取数据失败'); + } + } catch(e) { + print('任务列表获取失败: $e'); + SmartDialog.showToast('任务列表获取失败'); + rethrow; + } + } + void setDialogDismiss(bool flag){ isDialogShowing.value = flag; } @@ -888,6 +914,269 @@ class RoomController extends GetxController with WidgetsBindingObserver { } catch (e) { print('❌ 处理踢人消息失败: $e'); } + } else if(message['type'] == 'invite_to_mic'){ + // 处理踢人消息+ + try { + final userId = message['userId']; + final operatorName = message['operatorName']?.toString() ?? '主持人'; + + print('✅ 收到邀请消息: 被邀请用户ID $userId'); + + // 判断当前用户是否是被踢的用户 + if (userId.contains(GlobalData().userId ?? '')) { + if(rtcChannelDetail.value?.maleInfo == null && GlobalData().userData?.genderCode == 0 && currentRole != CurrentRole.broadcaster || + rtcChannelDetail.value?.femaleInfo == null && GlobalData().userData?.genderCode == 1 && currentRole != CurrentRole.broadcaster || + isLive.value && currentRole != CurrentRole.broadcaster && !isLive.value){ + setDialogDismiss(true); + SmartDialog.show( + alignment: Alignment.center, + maskColor: Colors.black.withOpacity(0.5), + onDismiss: () { + setDialogDismiss(false); + }, + builder: (context) { + // return LiveRoomGuestListDialog( + // initialTab: isMaleSeat ? 1 : 0, // 0: 女嘉宾, 1: 男嘉宾 + // ); + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(16.w)), + child: Material( + child: Stack( + + children: [ + Container( + width: 311.w, + height: 210.w, + color: Colors.white, + padding: EdgeInsets.only( + top: 53.w, + left: 23.w, + right: 23.w, + bottom: 20.w + ), + child: Column( + children: [ + Text( + "主持人邀请您视频连麦", + style: TextStyle( + fontSize: 21.w, + color: const Color.fromRGBO(117, 98, 249, 1), + fontWeight: FontWeight.w500 + ), + ), + SizedBox(height: 15.w,), + if(GlobalData().userData?.genderCode == 0) Text( + "有相亲卡的用户免费", + style: TextStyle( + fontSize: 12.w, + color: const Color.fromRGBO(87, 87, 87, 1), + fontWeight: FontWeight.w500 + ), + ), + SizedBox(height: 28.w,), + 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: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + Assets.imagesHangUpIcon, + width: 26.w, + height: 26.w, + ), + SizedBox(width: 5.w,), + Text( + "拒绝", + style: TextStyle( + fontSize: 15.w + ), + ) + ], + ), + ).onTap((){ + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + setDialogDismiss(false); + }), + 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: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + Assets.imagesAnswerIcon, + width: 18.w, + height: 13.w, + ), + SizedBox(width: 5.w,), + Text( + "接受", + style: TextStyle( + fontSize: 15.w, + color: Colors.white + ), + ) + ], + ), + ).onTap(() async { + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + setDialogDismiss(false); + + // 检查是否需要弹出充值弹框 + if (!isLive.value) { + final userData = GlobalData().userData; + final isMale = userData?.genderCode == 0; + if (isMale) { + final cardNum = linkMicCard.value?.num ?? 0; + // 如果显示"上麦20玫瑰"且玫瑰数量小于20,弹出充值弹框 + if (cardNum == 0 && roseCount.value < 20) { + setDialogDismiss(true); + SmartDialog.show( + alignment: Alignment.bottomCenter, + maskColor: Colors.black.withOpacity(0.5), + onDismiss: (){ + setDialogDismiss(false); + }, + builder: (_) => const LiveRechargePopup(), + ); + return; + } + } + } + + if(isLive.value){ + await leaveChat(); + }else{ + await joinChat(GlobalData().userData?.genderCode == 0 ? CurrentRole.maleAudience : CurrentRole.femaleAudience); + } + }), + ], + ) + ], + ), + ), + Positioned( + top: 0, + left: 0, + child: Container( + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.w), + bottomRight: Radius.circular(16.w), + ), + color: const Color.fromRGBO(117, 98, 249, 1) + ), + child: Text( + GlobalData().userData?.genderCode == 0 ? "20玫瑰" : "免费", + style: TextStyle( + fontSize: 14.w, + color: Colors.white, + fontWeight: FontWeight.w500 + ), + ), + ), + ) + ], + ), + ), + ); + }, + ); + } + + } else { + // 其他用户:刷新房间详情 + final channelName = RTCManager.instance.currentChannelId; + if (channelName != null && channelName.isNotEmpty) { + await fetchRtcChannelDetail(channelName); + } + print('✅ 其他用户收到邀请消息,已刷新房间详情'); + } + } catch (e) { + print('❌ 处理邀请消息失败: $e'); + } + + } else if(message['type'] == 'kick_more_user'){ + // 处理踢人消息+ + try { + final userId = message['userId'].map((e) => int.parse(e)).toList(); + final operatorName = message['operatorName']?.toString() ?? '主持人'; + + print('✅ 收到踢人消息: 被踢人用户ID $userId'); + + // 判断当前用户是否是被踢的用户 + if (userId.contains(rtcChannel.value?.uid ?? '')) { + + // 被踢用户:关闭小窗口 + if (Get.isRegistered()) { + try { + final overlayController = Get.find(); + overlayController.hide(); // 隐藏直播房间小窗 + } catch (e) { + print('关闭小窗口失败: $e'); + } + } + // 被踢用户:离开房间 + await leaveChannel(); + // 跳转到结束直播页面,并传入被踢出标识 + Get.off( + () => LiveEndPage(isKickedOut: true, operatorName: operatorName), + ); + + } else { + // 其他用户:刷新房间详情 + final channelName = RTCManager.instance.currentChannelId; + if (channelName != null && channelName.isNotEmpty) { + await fetchRtcChannelDetail(channelName); + } + print('✅ 其他用户收到踢人消息,已刷新房间详情'); + } + } catch (e) { + print('❌ 处理踢人消息失败: $e'); + } + + } else if (message['type'] == 'end_mic') { + // 处理踢人消息 + try { + final kickingUId = message['kickingUId']; + final operatorName = message['operatorName']?.toString() ?? '主持人'; + + print('✅ 收到下麦消息: 被下麦用户ID $kickingUId'); + + // 判断当前用户是否是被踢的用户 + if (rtcChannel.value?.uid == kickingUId) { + + await leaveChat(); + } else { + // 其他用户:刷新房间详情 + final channelName = RTCManager.instance.currentChannelId; + if (channelName != null && channelName.isNotEmpty) { + await fetchRtcChannelDetail(channelName); + } + print('✅ 其他用户收到下麦消息,已刷新房间详情'); + } + } catch (e) { + print('❌ 处理下麦消息失败: $e'); + } } } @@ -919,6 +1208,69 @@ class RoomController extends GetxController with WidgetsBindingObserver { matchmakerFlag.value = GlobalData().userData!.matchmakerFlag!; } + inviteMic(List selectUserId) async { + try { + final channelName = RTCManager.instance.currentChannelId; + if (channelName != null && channelName.isNotEmpty) { + final messageData = { + 'type': 'invite_to_mic', + 'userId': selectUserId, + 'operatorId': GlobalData().userData?.id ?? '', + 'operatorName': GlobalData().userData?.nickName ?? '', + }; + + await RTMManager.instance.publishChannelMessage( + channelName: channelName, + message: json.encode(messageData), + ); + print('✅ 邀请消息已发送: 邀请用户ID $selectUserId'); + + // 刷新频道详情 + await fetchRtcChannelDetail(channelName); + } + } catch (e){ + print('❌ 邀请用户异常: $e'); + SmartDialog.showToast('邀请用户失败'); + } + } + + kickUser(List selectUserId) async { + try { + final futures = selectUserId.map((e){ + + final requestData = {'channelId': rtcChannelDetail.value!.channelId, 'kickingUId': e}; + return _networkService.rtcApi.kickingRtcChannelUser( + requestData, + ); + }); + Future.wait(futures).then((e) async { + final channelName = RTCManager.instance.currentChannelId; + if (channelName != null && channelName.isNotEmpty) { + final messageData = { + 'type': 'kick_more_user', + 'userId': selectUserId, + 'operatorId': GlobalData().userData?.id ?? '', + 'operatorName': GlobalData().userData?.nickName ?? '', + }; + + await RTMManager.instance.publishChannelMessage( + channelName: channelName, + message: json.encode(messageData), + ); + print('✅ 邀请消息已发送: 邀请用户ID $selectUserId'); + + // 刷新频道详情 + await fetchRtcChannelDetail(channelName); + } + }); + + + } catch (e){ + print('❌ 邀请用户异常: $e'); + SmartDialog.showToast('邀请用户失败'); + } + } + /// 踢出 RTC 频道用户 Future kickingRtcChannelUser({ required String channelId, @@ -965,6 +1317,38 @@ class RoomController extends GetxController with WidgetsBindingObserver { } } + /// 踢出 RTC 频道用户 + Future endMic({ + required String channelId, + required int kickingUId, + }) async { + try { + + // 发送 RTM 消息通知所有用户 + final channelName = RTCManager.instance.currentChannelId; + if (channelName != null && channelName.isNotEmpty) { + final messageData = { + 'type': 'end_mic', + 'kickingUId': kickingUId, + 'operatorId': GlobalData().userData?.id ?? '', + 'operatorName': GlobalData().userData?.nickName ?? '', + }; + + await RTMManager.instance.publishChannelMessage( + channelName: channelName, + message: json.encode(messageData), + ); + print('✅ 下麦消息已发送: 下麦用户ID $kickingUId'); + + // 刷新频道详情 + await fetchRtcChannelDetail(channelName); + } + } catch (e) { + print('❌ 下麦用户异常: $e'); + SmartDialog.showToast('下麦用户失败'); + } + } + /// 获取用户道具连麦卡片 Future getUserPropLinkMicCard() async { try { diff --git a/lib/model/discover/task_data.dart b/lib/model/discover/task_data.dart new file mode 100644 index 0000000..1161ebd --- /dev/null +++ b/lib/model/discover/task_data.dart @@ -0,0 +1,116 @@ +class TaskData { + String? userTaskCompleteId; + int? taskGroup; + int? taskType; + int? stageCode; + String? taskName; + String? taskDesc; + String? taskStartDate; + String? taskEndDate; + String? rewardValue; + bool? completeStatus; + int? rewardReceiveStatus; + String? completeTime; + List? subList; + + TaskData( + {this.userTaskCompleteId, + this.taskGroup, + this.taskType, + this.stageCode, + this.taskName, + this.taskDesc, + this.taskStartDate, + this.taskEndDate, + this.rewardValue, + this.completeStatus, + this.rewardReceiveStatus, + this.completeTime, + this.subList}); + + TaskData.fromJson(Map json) { + userTaskCompleteId = json['userTaskCompleteId']; + taskGroup = json['taskGroup']; + taskType = json['taskType']; + stageCode = json['stageCode']; + taskName = json['taskName']; + taskDesc = json['taskDesc']; + taskStartDate = json['taskStartDate']; + taskEndDate = json['taskEndDate']; + rewardValue = json['rewardValue']; + completeStatus = json['completeStatus']; + rewardReceiveStatus = json['rewardReceiveStatus']; + completeTime = json['completeTime']; + if (json['subList'] != null) { + subList = []; + json['subList'].forEach((v) { + subList!.add(new SubList.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['userTaskCompleteId'] = this.userTaskCompleteId; + data['taskGroup'] = this.taskGroup; + data['taskType'] = this.taskType; + data['stageCode'] = this.stageCode; + data['taskName'] = this.taskName; + data['taskDesc'] = this.taskDesc; + data['taskStartDate'] = this.taskStartDate; + data['taskEndDate'] = this.taskEndDate; + data['rewardValue'] = this.rewardValue; + data['completeStatus'] = this.completeStatus; + data['rewardReceiveStatus'] = this.rewardReceiveStatus; + data['completeTime'] = this.completeTime; + if (this.subList != null) { + data['subList'] = this.subList!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class SubList { + String? subTaskName; + int? subTaskType; + String? subTaskDesc; + int? requiredCount; + int? completeCount; + bool? completeStatus; + String? completeTime; + int? sort; + + SubList( + {this.subTaskName, + this.subTaskType, + this.subTaskDesc, + this.requiredCount, + this.completeCount, + this.completeStatus, + this.completeTime, + this.sort}); + + SubList.fromJson(Map json) { + subTaskName = json['subTaskName']; + subTaskType = json['subTaskType']; + subTaskDesc = json['subTaskDesc']; + requiredCount = json['requiredCount']; + completeCount = json['completeCount']; + completeStatus = json['completeStatus']; + completeTime = json['completeTime']; + sort = json['sort']; + } + + Map toJson() { + final Map data = new Map(); + data['subTaskName'] = this.subTaskName; + data['subTaskType'] = this.subTaskType; + data['subTaskDesc'] = this.subTaskDesc; + data['requiredCount'] = this.requiredCount; + data['completeCount'] = this.completeCount; + data['completeStatus'] = this.completeStatus; + data['completeTime'] = this.completeTime; + data['sort'] = this.sort; + return data; + } +} diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 983a07c..b58b8a4 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -159,4 +159,7 @@ class ApiUrls { static const String userPageMicJoinRtcChannelUser = 'dating-agency-chat-audio/user/page/mic-join/rtc-channel-user'; + + static const String userGetUserTaskComplete = + 'dating-agency-mall/user/get/user-task-complete'; } diff --git a/lib/network/user_api.dart b/lib/network/user_api.dart index a547459..f9573d4 100644 --- a/lib/network/user_api.dart +++ b/lib/network/user_api.dart @@ -1,4 +1,5 @@ import 'package:dating_touchme_app/model/common/oss_data.dart'; +import 'package:dating_touchme_app/model/discover/task_data.dart'; import 'package:dating_touchme_app/model/home/user_info_data.dart'; import 'package:dating_touchme_app/model/mine/bank_card_data.dart'; import 'package:dating_touchme_app/model/mine/bank_card_ocr_data.dart'; @@ -276,4 +277,9 @@ abstract class UserApi { @Query('versionCode') required int code, }); + @GET(ApiUrls.userGetUserTaskComplete) + Future>> userGetUserTaskComplete({ + @Query('taskType') required int taskType, + }); + } diff --git a/lib/network/user_api.g.dart b/lib/network/user_api.g.dart index 095ff32..01ed186 100644 --- a/lib/network/user_api.g.dart +++ b/lib/network/user_api.g.dart @@ -1672,6 +1672,39 @@ class _UserApi implements UserApi { return httpResponse; } + @override + Future>> userGetUserTaskComplete({ + required int taskType, + }) async { + final _extra = {}; + final queryParameters = {r'taskType': taskType}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-mall/user/get/user-task-complete', + 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) => TaskData.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/widget/live/audience_item.dart b/lib/widget/live/audience_item.dart new file mode 100644 index 0000000..51da975 --- /dev/null +++ b/lib/widget/live/audience_item.dart @@ -0,0 +1,81 @@ + + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../model/discover/audience_list_data.dart'; + +class AudienceItem extends StatefulWidget { + final Records item; + final List selectUserId; + final Function(String) selectChange; + const AudienceItem({super.key, required this.item, required this.selectUserId, required this.selectChange}); + + @override + State createState() => _AudienceItemState(); +} + +class _AudienceItemState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(40.w)), + child: CachedNetworkImage( + imageUrl: "${widget.item.profilePhoto ?? ""}?x-oss-process=image/format,webp/resize,w_120", + + width: 40.w, + height: 40.w, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + ), + ), + SizedBox(width: 5.w,), + Column( + children: [ + Text( + widget.item.nickName ?? "", + style: TextStyle( + fontSize: 12.w + ), + ), + Text( + "${widget.item.age ?? ""}岁", + style: TextStyle( + fontSize: 12.w, + color: const Color.fromRGBO(121, 121, 121, 1) + ), + ), + ], + ) + ], + ), + + Checkbox( + value: widget.selectUserId.contains(widget.item.userId), + onChanged: (value) { + widget.selectChange(widget.item.userId ?? ""); + }, + activeColor: const Color.fromRGBO(117, 98, 249, 1), + side: const BorderSide(color: Colors.grey), + shape: const CircleBorder(), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ], + ), + ); + } +} diff --git a/lib/widget/live/disconnect_mic_dialog.dart b/lib/widget/live/disconnect_mic_dialog.dart index ad501ae..3e660bb 100644 --- a/lib/widget/live/disconnect_mic_dialog.dart +++ b/lib/widget/live/disconnect_mic_dialog.dart @@ -141,6 +141,45 @@ class DisconnectMicDialog extends StatelessWidget { ), SizedBox(height: 10.w), // 解除连麦按钮 + GestureDetector( + onTap: isConnected + ? () async { + // 解除连麦 + await roomController.endMic( + channelId: roomController.rtcChannelDetail.value!.channelId, + kickingUId: userInfo.uid ?? 0); + SmartDialog.dismiss(); + } + : null, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w), + decoration: BoxDecoration( + color: isConnected ? const Color(0xFF8B5CF6) : Colors.grey[300], + borderRadius: BorderRadius.circular(20.w), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + Assets.imagesMicOff, + width: 14.w, + color: isConnected ? Colors.white : Colors.grey[600], + ), + SizedBox(width: 4.w), + Text( + isConnected ? '解除连麦' : '已解除连麦', + style: TextStyle( + fontSize: 12.sp, + color: isConnected ? Colors.white : Colors.grey[600], + ), + ), + ], + ), + ), + ), + + SizedBox(height: 10.w), + // 踢出房间按钮 GestureDetector( onTap: isConnected ? () async { @@ -161,13 +200,13 @@ class DisconnectMicDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Image.asset( - Assets.imagesMicOff, + Assets.imagesGetOutIcon, width: 14.w, color: isConnected ? Colors.white : Colors.grey[600], ), SizedBox(width: 4.w), Text( - isConnected ? '解除连麦' : '已解除连麦', + isConnected ? '踢出房间' : '已踢出房间', style: TextStyle( fontSize: 12.sp, color: isConnected ? Colors.white : Colors.grey[600], diff --git a/lib/widget/live/kick_list.dart b/lib/widget/live/kick_list.dart new file mode 100644 index 0000000..e678fc6 --- /dev/null +++ b/lib/widget/live/kick_list.dart @@ -0,0 +1,189 @@ +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:easy_refresh/easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +import 'audience_item.dart'; + +class KickList extends StatefulWidget { + const KickList({super.key}); + + @override + State createState() => _KickListState(); +} + +class _KickListState extends State { + + final RoomController _roomController = Get.find(); + + + + List selectUserId = []; + + selectChange(String userId){ + if (selectUserId.contains(userId)) { + selectUserId.remove(userId); + } else { + selectUserId.add(userId); + } + setState(() { + + }); + } + + @override + Widget build(BuildContext context) { + return Material( + borderRadius: BorderRadius.vertical(top: Radius.circular(9.w)), + color: Colors.white, + child: Container( + width: 375.w, + height: 300.w, + padding: EdgeInsets.all(10.w), + child: Column( + children: [ + Text( + "在线用户", + style: TextStyle( + fontSize: 16.w, + fontWeight: FontWeight.w700 + ), + ), + Expanded( + child: EasyRefresh( + + controller: _roomController.listRefreshController, + header: const ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '刷新中...', + processingText: '刷新中...', + processedText: '刷新完成', + failedText: '刷新失败', + noMoreText: '没有更多数据', + showMessage: false + ), + footer: ClassicFooter( + dragText: '上拉加载', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败', + noMoreText: '没有更多数据', + showMessage: false + ), + // 下拉刷新 + onRefresh: () async { + print('推荐列表下拉刷新被触发'); + _roomController.page.value = 1; + _roomController.audienceList.clear(); + await _roomController.getAudienceList(); + _roomController.listRefreshController.finishRefresh(IndicatorResult.success); + _roomController.listRefreshController.finishLoad(IndicatorResult.none); + }, + // 上拉加载更多 + onLoad: () async { + print('推荐列表上拉加载被触发, hasMore: '); + _roomController.page.value += 1; + await _roomController.getAudienceList(); + }, + 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, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 170.w, + height: 42.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(42.w)), + color: const Color.fromRGBO(237, 237, 237, 1) + ), + child: Center( + child: Text( + "取消", + style: TextStyle( + fontSize: 14.w, + fontWeight: FontWeight.w500 + ), + ), + ), + ).onTap((){ + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + _roomController.setDialogDismiss(false); + }), + Container( + width: 170.w, + height: 42.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(42.w)), + color: const Color.fromRGBO(117, 98, 249, 1) + ), + child: Center( + child: Text( + "确认", + style: TextStyle( + fontSize: 14.w, + color: Colors.white, + fontWeight: FontWeight.w500 + ), + ), + ), + ).onTap(() async { + final selectedA = _roomController.audienceList.where((item) => selectUserId.contains(item.userId)).toList(); + final List dList = + selectedA.map((item) => item.userManagementId.toString()).toList(); + await _roomController.kickUser(dList); + + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + _roomController.setDialogDismiss(false); + selectUserId.clear(); + }) + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/widget/live/live_room_invitation_list.dart b/lib/widget/live/live_room_invitation_list.dart index 934cc2e..67a2f63 100644 --- a/lib/widget/live/live_room_invitation_list.dart +++ b/lib/widget/live/live_room_invitation_list.dart @@ -10,6 +10,8 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; +import 'audience_item.dart'; + class LiveRoomInvitationList extends StatefulWidget { const LiveRoomInvitationList({super.key}); @@ -31,6 +33,19 @@ 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); + } + setState(() { + + }); + } + @override Widget build(BuildContext context) { return Material( @@ -132,7 +147,7 @@ class _LiveRoomInvitationListState extends State with Ti } // 数据项 final item = _roomController.audienceList[index]; - return AudienceItem(item: item); + return AudienceItem(item: item, selectUserId: selectUserId, selectChange: selectChange,); }, separatorBuilder: (context, index) { // 空状态或加载状态时不显示分隔符 @@ -151,258 +166,68 @@ class _LiveRoomInvitationListState extends State with Ti ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 170.w, - height: 42.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(42.w)), - color: const Color.fromRGBO(237, 237, 237, 1) - ), - child: Center( - child: Text( - "取消", - style: TextStyle( - fontSize: 14.w, - fontWeight: FontWeight.w500 - ), - ), - ), - ), - Container( - width: 170.w, - height: 42.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(42.w)), - color: const Color.fromRGBO(117, 98, 249, 1) - ), - child: Center( - child: Text( - "确认", - style: TextStyle( - fontSize: 14.w, - color: Colors.white, - fontWeight: FontWeight.w500 - ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 170.w, + height: 42.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(42.w)), + color: const Color.fromRGBO(237, 237, 237, 1) ), - ), - ).onTap(() { - _roomController.setDialogDismiss(true); - SmartDialog.show( - alignment: Alignment.center, - maskColor: Colors.black.withOpacity(0.5), - onDismiss: () { - _roomController.setDialogDismiss(false); - }, - builder: (context) { - // return LiveRoomGuestListDialog( - // initialTab: isMaleSeat ? 1 : 0, // 0: 女嘉宾, 1: 男嘉宾 - // ); - return ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(16.w)), - child: Material( - child: Stack( - - children: [ - Container( - width: 311.w, - height: 210.w, - color: Colors.white, - padding: EdgeInsets.only( - top: 53.w, - left: 23.w, - right: 23.w, - bottom: 20.w - ), - child: Column( - children: [ - Text( - "主持人邀请您视频连麦", - style: TextStyle( - fontSize: 21.w, - color: const Color.fromRGBO(117, 98, 249, 1), - fontWeight: FontWeight.w500 - ), - ), - SizedBox(height: 15.w,), - Text( - "有相亲卡的用户免费", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(87, 87, 87, 1), - fontWeight: FontWeight.w500 - ), - ), - SizedBox(height: 28.w,), - 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: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - Assets.imagesHangUpIcon, - width: 26.w, - height: 26.w, - ), - SizedBox(width: 5.w,), - Text( - "拒绝", - style: TextStyle( - fontSize: 15.w - ), - ) - ], - ), - ), - 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: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - Assets.imagesAnswerIcon, - width: 18.w, - height: 13.w, - ), - SizedBox(width: 5.w,), - Text( - "拒绝", - style: TextStyle( - fontSize: 15.w, - color: Colors.white - ), - ) - ], - ), - ), - ], - ) - ], - ), - ), - Positioned( - top: 0, - left: 0, - child: Container( - padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 15.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.w), - bottomRight: Radius.circular(16.w), - ), - color: const Color.fromRGBO(117, 98, 249, 1) - ), - child: Text( - "20玫瑰", - style: TextStyle( - fontSize: 14.w, - color: Colors.white, - fontWeight: FontWeight.w500 - ), - ), - ), - ) - ], - ), + child: Center( + child: Text( + "取消", + style: TextStyle( + fontSize: 14.w, + fontWeight: FontWeight.w500 ), - ); - }, - ); - }) - ], - ) - ], - ), - ), - ); - } -} - -class AudienceItem extends StatefulWidget { - final Records item; - const AudienceItem({super.key, required this.item}); - - @override - State createState() => _AudienceItemState(); -} - -class _AudienceItemState extends State { - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.symmetric(vertical: 10.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(40.w)), - child: CachedNetworkImage( - imageUrl: "${widget.item.profilePhoto ?? ""}?x-oss-process=image/format,webp/resize,w_120", - - width: 40.w, - height: 40.w, - imageBuilder: (context, imageProvider) => Container( - decoration: BoxDecoration( - image: DecorationImage( - image: imageProvider, - fit: BoxFit.cover, ), ), - ), - ), - ), - SizedBox(width: 5.w,), - Column( - children: [ - Text( - widget.item.nickName ?? "", - style: TextStyle( - fontSize: 12.w + ).onTap((){ + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + _roomController.setDialogDismiss(false); + }), + Container( + width: 170.w, + height: 42.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(42.w)), + color: const Color.fromRGBO(117, 98, 249, 1) ), - ), - Text( - "${widget.item.age ?? ""}岁", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(121, 121, 121, 1) + child: Center( + child: Text( + "确认", + style: TextStyle( + fontSize: 14.w, + color: Colors.white, + fontWeight: FontWeight.w500 + ), + ), ), - ), + ).onTap(() async { + await _roomController.inviteMic(selectUserId); + + // 隐藏键盘 + FocusScope.of(context).unfocus(); + // 隐藏 overlay + SmartDialog.dismiss(); + _roomController.setDialogDismiss(false); + selectUserId.clear(); + }) ], - ) - ], - ), - - Checkbox( - value: false, - onChanged: (value) { - - }, - activeColor: const Color.fromRGBO(117, 98, 249, 1), - side: const BorderSide(color: Colors.grey), - shape: const CircleBorder(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ], + ), + ) + ], + ), ), ); } } - diff --git a/lib/widget/live/live_room_notice_chat_panel.dart b/lib/widget/live/live_room_notice_chat_panel.dart index 5bf1743..42629e1 100644 --- a/lib/widget/live/live_room_notice_chat_panel.dart +++ b/lib/widget/live/live_room_notice_chat_panel.dart @@ -3,6 +3,7 @@ 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/model/discover/task_data.dart'; import 'package:dating_touchme_app/pages/discover/task_detail.dart'; import 'package:dating_touchme_app/widget/live/live_room_chat_item.dart'; import 'package:dating_touchme_app/widget/live/live_recharge_popup.dart'; @@ -216,13 +217,14 @@ class _LiveRoomNoticeChatPanelState extends State { if(isHost) Image.asset( Assets.imagesDailyTasks, width: 70.w, - ).onTap((){ + ).onTap(() async { // 隐藏键盘 FocusScope.of(context).unfocus(); // 隐藏 overlay SmartDialog.dismiss(); roomController.setDialogDismiss(true); + await roomController.getTaskData(); SmartDialog.show( alignment: Alignment.bottomCenter, @@ -304,126 +306,9 @@ class _LiveRoomNoticeChatPanelState extends State { padding: EdgeInsets.symmetric(horizontal: 12.w), child: Column( children: [ - Container( - width: 350.w, - margin: EdgeInsets.only(bottom: 10.w), - padding: EdgeInsets.only( - top: 15.w, - right: 10.w, - bottom: 21.w, - left: 12.w - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "开播4小时", - style: TextStyle( - fontSize: 14.w - ), - ), - Text( - "50/240分钟", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(144, 144, 144, 1) - ), - ) - ], - ), - SizedBox(height: 10.w,), - TDProgress( - type: TDProgressType.linear, - value: 0.5, - strokeWidth: 6, - progressLabelPosition: TDProgressLabelPosition.inside, - showLabel: false, - ) - ], - ), - ), - Container( - width: 350.w, - margin: EdgeInsets.only(bottom: 10.w), - padding: EdgeInsets.only( - top: 15.w, - right: 10.w, - bottom: 21.w, - left: 12.w - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "开播4小时", - style: TextStyle( - fontSize: 14.w - ), - ), - Text( - "50/240分钟", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(144, 144, 144, 1) - ), - ) - ], - ), - SizedBox(height: 10.w,), - TDProgress( - type: TDProgressType.linear, - value: 0.5, - strokeWidth: 6, - progressLabelPosition: TDProgressLabelPosition.inside, - showLabel: false, - ) - ], - ), - ), - Container( - width: 350.w, - margin: EdgeInsets.only(bottom: 10.w), - padding: EdgeInsets.only( - top: 15.w, - right: 10.w, - bottom: 21.w, - left: 12.w - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "开播4小时", - style: TextStyle( - fontSize: 14.w - ), - ), - Text( - "50/240分钟", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(144, 144, 144, 1) - ), - ) - ], - ), - SizedBox(height: 10.w,), - TDProgress( - type: TDProgressType.linear, - value: 0.5, - strokeWidth: 6, - progressLabelPosition: TDProgressLabelPosition.inside, - showLabel: false, - ) - ], - ), - ), + ...roomController.taskData.value.subList!.map((e){ + return TaskItem(item: e,); + }) ], ), ), @@ -443,3 +328,57 @@ class _LiveRoomNoticeChatPanelState extends State { ); } } + +class TaskItem extends StatefulWidget { + final SubList item; + const TaskItem({super.key, required this.item}); + + @override + State createState() => _TaskItemState(); +} + +class _TaskItemState extends State { + @override + Widget build(BuildContext context) { + return Container( + width: 350.w, + margin: EdgeInsets.only(bottom: 10.w), + padding: EdgeInsets.only( + top: 15.w, + right: 10.w, + bottom: 21.w, + left: 12.w + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.item.subTaskDesc ?? "", + style: TextStyle( + fontSize: 14.w + ), + ), + Text( + "${widget.item.completeCount ?? ""}/${widget.item.requiredCount ?? ""}${widget.item.sort == 1 ? "分钟" : widget.item.sort == 2 ? "对" : "人"}", + style: TextStyle( + fontSize: 12.w, + color: const Color.fromRGBO(144, 144, 144, 1) + ), + ) + ], + ), + SizedBox(height: 10.w,), + TDProgress( + type: TDProgressType.linear, + value: ((widget.item.completeCount ?? 0) / (widget.item.requiredCount ?? 0)), + strokeWidth: 6, + progressLabelPosition: TDProgressLabelPosition.inside, + showLabel: false, + ) + ], + ), + ); + } +} diff --git a/lib/widget/live/live_room_user_header.dart b/lib/widget/live/live_room_user_header.dart index 045efaf..19027d8 100644 --- a/lib/widget/live/live_room_user_header.dart +++ b/lib/widget/live/live_room_user_header.dart @@ -2,6 +2,8 @@ import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/controller/overlay_controller.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/widget/live/disconnect_mic_dialog.dart'; +import 'package:dating_touchme_app/widget/live/kick_list.dart'; +import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -114,6 +116,8 @@ class LiveRoomUserHeader extends StatelessWidget { // 只有当有嘉宾在连麦时才显示"解除连麦"选项 GestureDetector( onTap: () async { + roomController.audienceList.clear(); + await roomController.getAudienceList(); // 隐藏键盘 FocusScope.of(context).unfocus(); // 隐藏 overlay @@ -129,166 +133,7 @@ class LiveRoomUserHeader extends StatelessWidget { }, builder: (_) { - return Material( - borderRadius: BorderRadius.vertical(top: Radius.circular(9.w)), - color: Colors.white, - child: Container( - width: 375.w, - height: 300.w, - padding: EdgeInsets.all(10.w), - child: Column( - children: [ - Text( - "在麦用户", - style: TextStyle( - fontSize: 16.w, - fontWeight: FontWeight.w700 - ), - ), - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - Container( - margin: EdgeInsets.symmetric(vertical: 10.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Image.asset( - Assets.imagesUserAvatar, - width: 40.w, - height: 40.w, - ), - SizedBox(width: 5.w,), - Column( - children: [ - Text( - "开心", - style: TextStyle( - fontSize: 12.w - ), - ), - Text( - "22岁", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(121, 121, 121, 1) - ), - ), - ], - ) - ], - ), - - Checkbox( - value: false, - onChanged: (value) { - - }, - activeColor: const Color.fromRGBO(117, 98, 249, 1), - side: const BorderSide(color: Colors.grey), - shape: const CircleBorder(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ], - ), - ), - Container( - margin: EdgeInsets.symmetric(vertical: 10.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Image.asset( - Assets.imagesUserAvatar, - width: 40.w, - height: 40.w, - ), - SizedBox(width: 5.w,), - Column( - children: [ - Text( - "开心", - style: TextStyle( - fontSize: 12.w - ), - ), - Text( - "22岁", - style: TextStyle( - fontSize: 12.w, - color: const Color.fromRGBO(121, 121, 121, 1) - ), - ), - ], - ) - ], - ), - - Checkbox( - value: false, - onChanged: (value) { - - }, - activeColor: const Color.fromRGBO(117, 98, 249, 1), - side: const BorderSide(color: Colors.grey), - shape: const CircleBorder(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ], - ), - ), - ], - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 170.w, - height: 42.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(42.w)), - color: const Color.fromRGBO(237, 237, 237, 1) - ), - child: Center( - child: Text( - "取消", - style: TextStyle( - fontSize: 14.w, - fontWeight: FontWeight.w500 - ), - ), - ), - ), - Container( - width: 170.w, - height: 42.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(42.w)), - color: const Color.fromRGBO(117, 98, 249, 1) - ), - child: Center( - child: Text( - "确认", - style: TextStyle( - fontSize: 14.w, - color: Colors.white, - fontWeight: FontWeight.w500 - ), - ), - ), - ) - ], - ) - ], - ), - ), - ); + return KickList(); }, ); },