Browse Source

feat(call): 实现通话类型图标区分和RTC频道管理功能

- 根据通话类型显示不同的图标:视频通话显示视频图标,语音通话显示语音图标
- 为通话项添加颜色区分,发送方显示白色,接收方显示橙色
- 新增消费一对一RTC频道响应模型定义
- 实现观众离开时断开RTC频道连接功能
- 优化主播离开时的RTC频道销毁逻辑
- 为语音和视频通话分别显示对应的价格信息
master
Jolie 2 months ago
parent
commit
dde5bc76db
4 changed files with 69 additions and 5 deletions
  1. 17
      lib/controller/discover/room_controller.dart
  2. 42
      lib/model/rtc/consume_rtc_channel_response.dart
  3. 8
      lib/widget/message/call_item.dart
  4. 7
      lib/widget/message/call_type_selection_dialog.dart

17
lib/controller/discover/room_controller.dart

@ -375,7 +375,6 @@ class RoomController extends GetxController with WidgetsBindingObserver {
} }
Future<void> leaveChannel() async { Future<void> leaveChannel() async {
// RTC
if (currentRole == CurrentRole.broadcaster) { if (currentRole == CurrentRole.broadcaster) {
try { try {
// RTC API // RTC API
@ -393,6 +392,22 @@ class RoomController extends GetxController with WidgetsBindingObserver {
} catch (e) { } catch (e) {
print('❌ 销毁 RTC 频道异常: $e'); print('❌ 销毁 RTC 频道异常: $e');
} }
} else if (currentRole == CurrentRole.maleAudience || currentRole == CurrentRole.femaleAudience) {
try {
// RTC
final channelId = RTCManager.instance.currentChannelId;
if (channelId != null && channelId.isNotEmpty) {
final data = {'channelId': channelId};
final response = await _networkService.rtcApi.disconnectRtcChannel(data);
if (response.data.isSuccess) {
print('✅ [RoomController] 嘉宾已断开 RTC 频道连接,channelId: $channelId');
} else {
print('⚠️ [RoomController] 断开 RTC 频道连接失败: ${response.data.message}');
}
}
} catch (e) {
print('❌ [RoomController] 断开 RTC 频道连接异常: $e');
}
} }
isLive.value = false; isLive.value = false;

42
lib/model/rtc/consume_rtc_channel_response.dart

@ -0,0 +1,42 @@
/// RTC频道响应模型
class ConsumeRtcChannelResponse {
final bool isFree;
final int status;
final int availableBalance;
final int unitSellingBalance;
final String? code;
ConsumeRtcChannelResponse({
required this.isFree,
required this.status,
required this.availableBalance,
required this.unitSellingBalance,
this.code,
});
factory ConsumeRtcChannelResponse.fromJson(Map<String, dynamic> json) {
return ConsumeRtcChannelResponse(
isFree: json['isFree'] as bool? ?? false,
status: json['status'] as int? ?? 0,
availableBalance: json['availableBalance'] as int? ?? 0,
unitSellingBalance: json['unitSellingBalance'] as int? ?? 0,
code: json['code'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'isFree': isFree,
'status': status,
'availableBalance': availableBalance,
'unitSellingBalance': unitSellingBalance,
'code': code,
};
}
@override
String toString() {
return 'ConsumeRtcChannelResponse(isFree: $isFree, status: $status, availableBalance: $availableBalance, unitSellingBalance: $unitSellingBalance, code: $code)';
}
}

8
lib/widget/message/call_item.dart

@ -136,7 +136,12 @@ class CallItem extends StatelessWidget {
if (callStatus == CallStatus.rejected) { if (callStatus == CallStatus.rejected) {
return Assets.imagesRejectCall; return Assets.imagesRejectCall;
} else { } else {
return Assets.imagesAcceptCall;
//
if (callType == CallType.video) {
return Assets.imagesSendVideoCall;
} else {
return Assets.imagesSendCall;
}
} }
} }
} }
@ -225,6 +230,7 @@ class CallItem extends StatelessWidget {
width: 24.w, width: 24.w,
height: 24.w, height: 24.w,
fit: BoxFit.contain, fit: BoxFit.contain,
color: isSentByMe ? Colors.white : Colors.orange,
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
// //

7
lib/widget/message/call_type_selection_dialog.dart

@ -49,7 +49,8 @@ class CallTypeSelectionDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String price = _formatPrice(voiceProduct);
String voicePrice = _formatPrice(voiceProduct);
String videoPrice = _formatPrice(videoProduct);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
@ -72,7 +73,7 @@ class CallTypeSelectionDialog extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 16.w), padding: EdgeInsets.symmetric(vertical: 16.w),
child: Center( child: Center(
child: Text( child: Text(
price.isNotEmpty ? '语音通话 ($price)' : '语音通话',
voicePrice.isNotEmpty ? '语音通话 ($voicePrice)' : '语音通话',
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: const Color.fromRGBO(51, 51, 51, 1), color: const Color.fromRGBO(51, 51, 51, 1),
@ -98,7 +99,7 @@ class CallTypeSelectionDialog extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 16.w), padding: EdgeInsets.symmetric(vertical: 16.w),
child: Center( child: Center(
child: Text( child: Text(
price.isNotEmpty ? '视频通话 ($price)' : '视频通话',
videoPrice.isNotEmpty ? '视频通话 ($videoPrice)' : '视频通话',
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: const Color.fromRGBO(51, 51, 51, 1), color: const Color.fromRGBO(51, 51, 51, 1),

Loading…
Cancel
Save