Browse Source

feat(call): 添加RTC通话拒绝和取消功能并优化通话时长计时

- 添加refuseOneOnOneRtcChannel和cancelOneOnOneRtcChannel API接口
- 实现发起方收到accept消息后自动启动通话计时器
- 优化VideoCallPage中的响应式变量监听和UI状态判断
- 移除未使用的IMManager导入
- 更新通话状态显示逻辑,区分呼叫中和已接通状态
master
Jolie 3 months ago
parent
commit
20938d7421
5 changed files with 114 additions and 10 deletions
  1. 6
      lib/controller/message/call_controller.dart
  2. 5
      lib/network/api_urls.dart
  3. 12
      lib/network/rtc_api.dart
  4. 68
      lib/network/rtc_api.g.dart
  5. 33
      lib/pages/message/video_call_page.dart

6
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 消息,执行退出逻辑');

5
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';

12
lib/network/rtc_api.dart

@ -99,4 +99,16 @@ abstract class RtcApi {
///
@GET(ApiUrls.getUserPropLinkMicCard)
Future<HttpResponse<BaseResponse<LinkMicCardModel>>> getUserPropLinkMicCard();
/// RTC频道
@POST(ApiUrls.refuseOneOnOneRtcChannel)
Future<HttpResponse<BaseResponse<dynamic>>> refuseOneOnOneRtcChannel(
@Body() Map<String, dynamic> data,
);
/// RTC频道
@POST(ApiUrls.cancelOneOnOneRtcChannel)
Future<HttpResponse<BaseResponse<dynamic>>> cancelOneOnOneRtcChannel(
@Body() Map<String, dynamic> data,
);
}

68
lib/network/rtc_api.g.dart

@ -570,6 +570,74 @@ class _RtcApi implements RtcApi {
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> refuseOneOnOneRtcChannel(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
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<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.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<HttpResponse<BaseResponse<dynamic>>> cancelOneOnOneRtcChannel(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
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<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.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<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

33
lib/pages/message/video_call_page.dart

@ -391,8 +391,15 @@ class _VideoCallPageState extends State<VideoCallPage> {
///
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<VideoCallPage> {
/// /
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<VideoCallPage> {
}
//
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<VideoCallPage> {
///
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<VideoCallPage> {
}
//
if (_isCallConnected && !showControls.value) {
if (isCallConnected && !showControls.value) {
return const SizedBox.shrink();
}

Loading…
Cancel
Save