From b57a3757fc4821e2a0efa162e2aef1f0985ef347 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Sun, 4 Jan 2026 16:14:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(call):=20=E6=B7=BB=E5=8A=A0=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E9=9F=B3=E9=A2=91=E4=BA=A7=E5=93=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增聊天音频产品API接口和模型定义 - 实现获取聊天音频产品列表功能 - 在通话类型选择对话框中显示动态价格信息 - 优化通话时长显示逻辑 - 更新视频通话回调为异步处理方式 --- lib/controller/message/call_controller.dart | 24 +++++++++++ lib/network/api_urls.dart | 3 ++ lib/network/rtc_api.dart | 7 +++ lib/network/rtc_api.g.dart | 43 +++++++++++++++++++ lib/pages/message/chat_page.dart | 24 +++++------ lib/widget/message/call_item.dart | 9 ++-- .../message/call_type_selection_dialog.dart | 32 +++++++++++++- lib/widget/message/chat_input_bar.dart | 6 +-- 8 files changed, 126 insertions(+), 22 deletions(-) diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index a2c9364..44b12bf 100644 --- a/lib/controller/message/call_controller.dart +++ b/lib/controller/message/call_controller.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/model/rtc/chat_audio_product_model.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'; @@ -173,6 +174,29 @@ class CallController extends GetxController { return createOneOnOneRtcChannel(type: 3); } + /// 获取聊天音频产品列表 + /// [toUserId] 目标用户ID + Future?> listChatAudioProduct(String toUserId) async { + try { + final response = await _networkService.rtcApi.listChatAudioProduct(toUserId); + if (response.data.isSuccess) { + print('✅ [CallController] 获取聊天音频产品列表成功'); + return response.data.data; + } else { + final message = response.data.message.isNotEmpty + ? response.data.message + : '获取聊天音频产品列表失败'; + SmartDialog.showToast(message); + print('❌ [CallController] 获取聊天音频产品列表失败: $message'); + return null; + } + } catch (e) { + SmartDialog.showToast('网络请求失败,请重试'); + print('❌ [CallController] 获取聊天音频产品列表异常: $e'); + return null; + } + } + /// 发起通话 /// [targetUserId] 目标用户ID /// [callType] 通话类型:语音或视频 diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 53c84ab..66b78ef 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -146,4 +146,7 @@ class ApiUrls { static const String getAppVersion = 'dating-agency-uec/user/get/app-version/update'; + static const String listChatAudioProduct = + 'dating-agency-chat-audio/user/list/chat-audio-product'; + } diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 622864f..11db022 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -1,5 +1,6 @@ import 'package:dating_touchme_app/model/discover/rtc_channel_model.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; +import 'package:dating_touchme_app/model/rtc/chat_audio_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'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; @@ -123,4 +124,10 @@ abstract class RtcApi { Future>> terminateOneOnOneRtcChannel( @Body() Map data, ); + + /// 获取聊天音频产品列表 + @GET(ApiUrls.listChatAudioProduct) + Future>>> listChatAudioProduct( + @Query('toUserId') String toUserId, + ); } diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index d141fc1..d21199c 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -706,6 +706,49 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>>> + listChatAudioProduct(String toUserId) async { + final _extra = {}; + final queryParameters = {r'toUserId': toUserId}; + final _headers = {}; + const Map? _data = null; + final _options = + _setStreamType>>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/list/chat-audio-product', + 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 is List + ? json + .map( + (i) => ChatAudioProductModel.fromJson( + i as Map, + ), + ) + .toList() + : List.empty(), + ); + } 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/chat_page.dart b/lib/pages/message/chat_page.dart index 2f401b5..cfca2aa 100644 --- a/lib/pages/message/chat_page.dart +++ b/lib/pages/message/chat_page.dart @@ -9,6 +9,7 @@ import '../../controller/message/chat_controller.dart'; import '../../controller/message/voice_player_manager.dart'; import '../../generated/assets.dart'; import '../../model/home/marriage_data.dart'; +import '../../model/rtc/chat_audio_product_model.dart'; import '../../../widget/message/chat_input_bar.dart'; import '../../../widget/message/message_item.dart'; import '../../../widget/message/chat_gift_popup.dart'; @@ -136,12 +137,16 @@ class _ChatPageState extends State { } // 显示通话类型选择弹框 - void _showCallTypeSelectionDialog(ChatController controller) { + void _showCallTypeSelectionDialog( + ChatController controller, { + List? products, + }) { // 隐藏键盘 FocusScope.of(context).unfocus(); SmartDialog.show( builder: (context) { return CallTypeSelectionDialog( + products: products, onVoiceCall: () async { // 发起语音通话并跳转到视频通话页面 final success = await CallController.instance.initiateCall( @@ -351,19 +356,12 @@ class _ChatPageState extends State { // 显示礼物弹窗 _showGiftPopup(); }, - // 语音通话回调(暂时隐藏) - // onVoiceCall: () async { - // // 发起语音通话 - // await CallController.instance.initiateCall( - // targetUserId: widget.userId, - // callType: CallType.voice, - // chatController: controller, - // ); - // }, // 视频通话回调 - onVideoCall: () { - // 显示通话类型选择弹框 - _showCallTypeSelectionDialog(controller); + onVideoCall: () async { + // 先调用获取聊天音频产品列表接口 + final products = await CallController.instance.listChatAudioProduct(widget.userId); + // 显示通话类型选择弹框,传入产品数据 + _showCallTypeSelectionDialog(controller, products: products); }, ), ], diff --git a/lib/widget/message/call_item.dart b/lib/widget/message/call_item.dart index 9e0add5..7e9150e 100644 --- a/lib/widget/message/call_item.dart +++ b/lib/widget/message/call_item.dart @@ -102,7 +102,8 @@ class CallItem extends StatelessWidget { int? _getCallDuration() { final callInfo = _parseCallInfo(); if (callInfo != null) { - return callInfo['callDuration'] as int?; + Get.log('通话信息: ${callInfo['callDuration']}'); + return int.tryParse(callInfo['callDuration'].toString()); } return null; } @@ -148,13 +149,13 @@ class CallItem extends StatelessWidget { if (callStatus == CallStatus.calling && duration != null) { // 通话中,显示时长 - return _formatDuration(duration); + return '通话中'; } else if (callStatus == CallStatus.cancelled) { return '已取消'; } else if (callStatus == CallStatus.rejected) { return '已拒绝'; - } else if(callStatus == CallStatus.terminated){ - return '已结束'; + } else if(callStatus == CallStatus.terminated && duration != null){ + return _formatDuration(duration); }else { return callType == CallType.video ? '我刚刚邀请你视频通话' : '我刚刚邀请你语音通话'; } diff --git a/lib/widget/message/call_type_selection_dialog.dart b/lib/widget/message/call_type_selection_dialog.dart index 3c793ea..21658d4 100644 --- a/lib/widget/message/call_type_selection_dialog.dart +++ b/lib/widget/message/call_type_selection_dialog.dart @@ -1,18 +1,46 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import '../../model/rtc/chat_audio_product_model.dart'; /// 通话类型选择弹框 class CallTypeSelectionDialog extends StatelessWidget { final VoidCallback? onVoiceCall; final VoidCallback? onVideoCall; + final List? products; const CallTypeSelectionDialog({ super.key, this.onVoiceCall, this.onVideoCall, + this.products, }); + /// 根据子分类获取产品(2102=语音通话,2103=视频通话) + ChatAudioProductModel? _getProductBySubCategory(int subCategory) { + if (products == null || products!.isEmpty) return null; + try { + return products!.firstWhere( + (product) => product.subCategory == subCategory, + ); + } catch (e) { + // 如果找不到匹配的产品,返回 null + return null; + } + } + + /// 获取语音通话产品 + ChatAudioProductModel? get voiceProduct => _getProductBySubCategory(2102); + + /// 获取视频通话产品 + ChatAudioProductModel? get videoProduct => _getProductBySubCategory(2103); + + /// 格式化价格显示 + String _formatPrice(ChatAudioProductModel? product) { + if (product == null) return '35玫瑰/分钟'; + return '${product.unitSellingPrice.toInt()}玫瑰/分钟'; + } + @override Widget build(BuildContext context) { return Container( @@ -37,7 +65,7 @@ class CallTypeSelectionDialog extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 16.w), child: Center( child: Text( - '语音通话 (35玫瑰/分钟)', + '语音通话 (${_formatPrice(voiceProduct)})', style: TextStyle( fontSize: 16.sp, color: const Color.fromRGBO(51, 51, 51, 1), @@ -63,7 +91,7 @@ class CallTypeSelectionDialog extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 16.w), child: Center( child: Text( - '视频通话 (35玫瑰/分钟)', + '视频通话 (${_formatPrice(videoProduct)})', style: TextStyle( fontSize: 16.sp, color: const Color.fromRGBO(51, 51, 51, 1), diff --git a/lib/widget/message/chat_input_bar.dart b/lib/widget/message/chat_input_bar.dart index 7ab847a..bfbd66a 100644 --- a/lib/widget/message/chat_input_bar.dart +++ b/lib/widget/message/chat_input_bar.dart @@ -13,7 +13,7 @@ class ChatInputBar extends StatefulWidget { final ValueChanged>? onImageSelected; final Function(String filePath, int seconds)? onVoiceRecorded; final VoidCallback? onVoiceCall; // 语音通话回调 - final VoidCallback? onVideoCall; // 视频通话回调 + final Future Function()? onVideoCall; // 视频通话回调 final VoidCallback? onGiftTap; // 礼物按钮回调 const ChatInputBar({ @@ -304,8 +304,8 @@ class _ChatInputBarState extends State { Assets.imagesVideoCall, width: 24.w, height: 24.w, - ).onTap(() { - widget.onVideoCall?.call(); + ).onTap(() async { + await widget.onVideoCall?.call(); }), // 礼物按钮 Image.asset(Assets.imagesGift, width: 24.w, height: 24.w).onTap(() {