From 87c11464ad285bbbfcabfa870f0a53b20846e082 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Mon, 5 Jan 2026 17:15:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(call):=20=E6=B7=BB=E5=8A=A0=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5=E5=92=8C=E7=8E=AB?= =?UTF-8?q?=E7=91=B0=E4=B8=8D=E8=B6=B3=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在创建RTC频道时添加麦克风和摄像头权限检查 - 实现语音通话需要麦克风权限,视频通话需要摄像头和麦克风权限 - 添加toUserId参数到createOneOnOneRtcChannel方法 - 处理玫瑰不足情况(E0002错误码)并显示充值弹框 - 修复音频通话类型参数错误(2为视频,3为音频) - 在RtcChannelData模型中添加success和code字段 - 添加权限被永久拒绝时跳转系统设置功能 --- lib/controller/message/call_controller.dart | 94 ++++++++++++++++++--- lib/model/rtc/rtc_channel_data.dart | 8 ++ 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index b413d0d..fe5a865 100644 --- a/lib/controller/message/call_controller.dart +++ b/lib/controller/message/call_controller.dart @@ -133,7 +133,10 @@ class CallController extends GetxController { /// 创建一对一RTC频道 /// [type] 1为音频,2为视频 - Future createOneOnOneRtcChannel({required int type}) async { + Future createOneOnOneRtcChannel({ + required int type, + required String toUserId, + }) async { if (isCreatingChannel.value) { print('⚠️ 正在创建频道,请稍候'); return null; @@ -145,12 +148,34 @@ class CallController extends GetxController { return null; } + // 检查权限:语音通话需要麦克风,视频通话需要摄像头和麦克风 + final hasPermission = await _ensureCallPermissions(type); + if (!hasPermission) { + print('❌ [CallController] 权限检查失败,无法创建通话频道'); + return null; + } + isCreatingChannel.value = true; final response = await _networkService.rtcApi.createOneOnOneRtcChannel({ 'type': type, + 'toUserId': toUserId, }); if (response.data.isSuccess && response.data.data != null) { + if (!response.data.data!.success && response.data.data!.code == 'E0002') { + // 玫瑰不足,显示 toast 并弹出充值弹框 + SmartDialog.showToast('玫瑰不足请充值'); + Get.log('❌ 送礼失败: ${response.data.data}'); + // 使用 addPostFrameCallback 确保在下一帧显示弹框,避免与 toast 冲突 + WidgetsBinding.instance.addPostFrameCallback((_) { + SmartDialog.show( + alignment: Alignment.bottomCenter, + maskColor: Colors.black.withOpacity(0.5), + builder: (_) => const LiveRechargePopup(), + ); + }); + return null; + } rtcChannel.value = response.data.data; print('✅ 创建一对一RTC频道成功: ${response.data.data?.channelId}'); isCreatingChannel.value = false; @@ -166,16 +191,6 @@ class CallController extends GetxController { } } - /// 创建音频通话频道 - Future createAudioChannel() { - return createOneOnOneRtcChannel(type: 2); - } - - /// 创建视频通话频道 - Future createVideoChannel() { - return createOneOnOneRtcChannel(type: 3); - } - /// 获取聊天音频产品列表 /// [toUserId] 目标用户ID Future?> listChatAudioProduct(String toUserId) async { @@ -237,7 +252,10 @@ class CallController extends GetxController { // 发起通话前,先创建一对一 RTC 频道 final type = callType == CallType.video ? 3 : 2; // 1为音频,2为视频 - final channelData = await createOneOnOneRtcChannel(type: type); + final channelData = await createOneOnOneRtcChannel( + type: type, + toUserId: targetUserId, + ); _callUid = channelData?.uid; _callChannelId = channelData?.channelId; if (channelData == null) { @@ -372,6 +390,19 @@ class CallController extends GetxController { SmartDialog.showToast(connectResponse.data.message); return false; } + if (!connectResponse.data.data!['success'] && connectResponse.data.data!['code'] == 'E0002') { + // 玫瑰不足,显示 toast 并弹出充值弹框 + SmartDialog.showToast('玫瑰不足请充值'); + // 使用 addPostFrameCallback 确保在下一帧显示弹框,避免与 toast 冲突 + WidgetsBinding.instance.addPostFrameCallback((_) { + SmartDialog.show( + alignment: Alignment.bottomCenter, + maskColor: Colors.black.withOpacity(0.5), + builder: (_) => const LiveRechargePopup(), + ); + }); + return false; + } print('✅ [CallController] 已调用连接一对一RTC频道接口,channelId: $channelId'); // 根据通话类型设置摄像头状态 @@ -639,6 +670,45 @@ class CallController extends GetxController { print('✅ [CallController] 已加入 RTC 频道: $channelName'); } + /// 检查通话权限 + /// [type] 2=语音通话,3=视频通话 + Future _ensureCallPermissions(int type) async { + if (type == 2) { + // 语音通话:只需要麦克风权限 + final micStatus = await Permission.microphone.request(); + if (micStatus.isGranted) { + return true; + } + + if (micStatus.isPermanentlyDenied) { + SmartDialog.showToast('请在系统设置中开启麦克风权限'); + await openAppSettings(); + } else { + SmartDialog.showToast('请允许麦克风权限以进行语音通话'); + } + return false; + } else if (type == 3) { + // 视频通话:需要摄像头和麦克风权限 + final statuses = await [Permission.camera, Permission.microphone].request(); + final allGranted = statuses.values.every((status) => status.isGranted); + if (allGranted) { + return true; + } + + final permanentlyDenied = statuses.values.any( + (status) => status.isPermanentlyDenied, + ); + if (permanentlyDenied) { + SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); + await openAppSettings(); + } else { + SmartDialog.showToast('请允许摄像头和麦克风权限以进行视频通话'); + } + return false; + } + return false; + } + Future _ensureRtcPermissions() async { final statuses = await [Permission.camera, Permission.microphone].request(); final allGranted = statuses.values.every((status) => status.isGranted); diff --git a/lib/model/rtc/rtc_channel_data.dart b/lib/model/rtc/rtc_channel_data.dart index b5795c4..b76e04e 100644 --- a/lib/model/rtc/rtc_channel_data.dart +++ b/lib/model/rtc/rtc_channel_data.dart @@ -3,11 +3,15 @@ class RtcChannelData { final String channelId; final String token; final int uid; + final bool success; + final String code; RtcChannelData({ required this.channelId, required this.token, required this.uid, + required this.success, + required this.code, }); factory RtcChannelData.fromJson(Map json) { @@ -15,6 +19,8 @@ class RtcChannelData { channelId: json['channelId']?.toString() ?? '', token: json['token']?.toString() ?? '', uid: json['uid'] ?? 0, + success: json['success'] as bool? ?? false, + code: json['code']?.toString() ?? '', ); } @@ -23,6 +29,8 @@ class RtcChannelData { 'channelId': channelId, 'token': token, 'uid': uid, + 'success': success, + 'code': code, }; }