From 20938d74216856202bb81937b4e8907572b12712 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 30 Dec 2025 21:45:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(call):=20=E6=B7=BB=E5=8A=A0RTC=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E6=8B=92=E7=BB=9D=E5=92=8C=E5=8F=96=E6=B6=88=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E9=80=9A=E8=AF=9D=E6=97=B6?= =?UTF-8?q?=E9=95=BF=E8=AE=A1=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加refuseOneOnOneRtcChannel和cancelOneOnOneRtcChannel API接口 - 实现发起方收到accept消息后自动启动通话计时器 - 优化VideoCallPage中的响应式变量监听和UI状态判断 - 移除未使用的IMManager导入 - 更新通话状态显示逻辑,区分呼叫中和已接通状态 --- lib/controller/message/call_controller.dart | 6 +- lib/network/api_urls.dart | 5 ++ lib/network/rtc_api.dart | 12 ++++ lib/network/rtc_api.g.dart | 68 +++++++++++++++++++++ lib/pages/message/video_call_page.dart | 33 +++++++--- 5 files changed, 114 insertions(+), 10 deletions(-) diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index f2c47f7..6c2c717 100644 --- a/lib/controller/message/call_controller.dart +++ b/lib/controller/message/call_controller.dart @@ -5,7 +5,6 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:agora_rtm/agora_rtm.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dating_touchme_app/generated/assets.dart'; -import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; @@ -148,7 +147,7 @@ class CallController extends GetxController { if (messageData['type'] == 'call_message') { final event = messageData['event'] as String?; if (event == 'accept') { - // 发起方收到 accept 消息,设置远端用户 UID + // 发起方收到 accept 消息,设置远端用户 UID 并启动计时器 final uid = messageData['uid']; if (uid != null) { remoteUid.value = uid is int ? uid : int.tryParse(uid.toString()); @@ -156,6 +155,9 @@ class CallController extends GetxController { '📞 [CallController] 收到 accept 消息,设置 remoteUid: ${remoteUid.value}', ); } + // 发起方收到 accept 消息后,启动通话计时器 + _startCallTimer(); + print('📞 [CallController] 收到 accept 消息,已启动通话计时器'); } else if (event == 'hangup') { // 收到挂断消息,执行退出逻辑 print('📞 [CallController] 收到 hangup 消息,执行退出逻辑'); diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 599da59..d8b24b4 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -131,6 +131,11 @@ class ApiUrls { static const String getUserPropLinkMicCard = 'dating-agency-chat-audio/user/get/user-prop/link-mic-card'; + static const String refuseOneOnOneRtcChannel = + 'dating-agency-chat-audio/user/refuse/one-on-one/rtc-channel'; + + static const String cancelOneOnOneRtcChannel = + 'dating-agency-chat-audio/user/cancel/one-on-one/rtc-channel'; static const String getAppVersion = 'dating-agency-uec/user/get/app-version/update'; diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index e46519e..d3573c7 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -99,4 +99,16 @@ abstract class RtcApi { /// 获取用户道具连麦卡片 @GET(ApiUrls.getUserPropLinkMicCard) Future>> getUserPropLinkMicCard(); + + /// 拒绝一对一RTC频道 + @POST(ApiUrls.refuseOneOnOneRtcChannel) + Future>> refuseOneOnOneRtcChannel( + @Body() Map data, + ); + + /// 取消一对一RTC频道 + @POST(ApiUrls.cancelOneOnOneRtcChannel) + Future>> cancelOneOnOneRtcChannel( + @Body() Map data, + ); } diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 5e0170f..9951441 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -570,6 +570,74 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>> refuseOneOnOneRtcChannel( + Map data, + ) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(data); + final _options = _setStreamType>>( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/refuse/one-on-one/rtc-channel', + 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) => json as dynamic, + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + @override + Future>> cancelOneOnOneRtcChannel( + Map data, + ) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(data); + final _options = _setStreamType>>( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/cancel/one-on-one/rtc-channel', + 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) => json as dynamic, + ); + } 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/message/video_call_page.dart b/lib/pages/message/video_call_page.dart index a382333..b9efd49 100644 --- a/lib/pages/message/video_call_page.dart +++ b/lib/pages/message/video_call_page.dart @@ -391,8 +391,15 @@ class _VideoCallPageState extends State { /// 构建用户信息 Widget _buildUserInfo() { return Obx(() { - // 如果已接通,不显示头像和昵称 - if (_isCallConnected) { + // 在 Obx 中直接访问响应式变量,确保建立监听关系 + final callSession = _callController.currentCall.value; + final callDuration = _callController.callDurationSeconds.value; + final isCallConnected = callSession != null && callDuration > 0; + + print('📞 [VideoCallPage] _buildUserInfo Obx 重建,isCallConnected: $isCallConnected, callDuration: $callDuration'); + + // 如果已接通,不显示头像和昵称(适用于发起方和接收方) + if (isCallConnected) { return const SizedBox.shrink(); } @@ -443,8 +450,13 @@ class _VideoCallPageState extends State { /// 构建通话时长/状态文本 Widget _buildCallDuration() { return Obx(() { + // 在 Obx 中直接访问响应式变量,确保建立监听关系 + final callSession = _callController.currentCall.value; + final callDuration = _callController.callDurationSeconds.value; + final isCallConnected = callSession != null && callDuration > 0; + // 如果是被呼叫方且未接通,显示邀请文字 - if (!widget.isInitiator && !_isCallConnected) { + if (!widget.isInitiator && !isCallConnected) { final isVideoCall = widget.callType == 'video'; final inviteText = isVideoCall ? '邀请你视频通话' : '邀请你语音通话'; @@ -466,19 +478,19 @@ class _VideoCallPageState extends State { } // 如果已接通但控制按钮已隐藏,不显示时长 - if (_isCallConnected && !showControls.value) { + if (isCallConnected && !showControls.value) { return const SizedBox.shrink(); } // 呼叫方或已接通,显示时长或"正在呼叫中" - final duration = Duration(seconds: _callController.callDurationSeconds.value); + final duration = Duration(seconds: callDuration); return Positioned( bottom: MediaQuery.of(context).size.height * 0.25, left: 0, right: 0, child: Center( child: Text( - _isCallConnected ? _formatDuration(duration) : '正在呼叫中', + isCallConnected ? _formatDuration(duration) : '正在呼叫中', style: TextStyle( color: Colors.white, fontSize: 16.sp, @@ -493,8 +505,13 @@ class _VideoCallPageState extends State { /// 构建控制按钮 Widget _buildControlButtons() { return Obx(() { + // 在 Obx 中直接访问响应式变量,确保建立监听关系 + final callSession = _callController.currentCall.value; + final callDuration = _callController.callDurationSeconds.value; + final isCallConnected = callSession != null && callDuration > 0; + // 如果是被呼叫方且未接通,显示"拒绝"和"接听"按钮 - if (!widget.isInitiator && !_isCallConnected) { + if (!widget.isInitiator && !isCallConnected) { return Positioned( bottom: 40.h, left: 0, @@ -524,7 +541,7 @@ class _VideoCallPageState extends State { } // 如果已接通但控制按钮已隐藏,不显示按钮 - if (_isCallConnected && !showControls.value) { + if (isCallConnected && !showControls.value) { return const SizedBox.shrink(); }