diff --git a/assets/images/example_content.png b/assets/images/example_content.png deleted file mode 100644 index 4f450bc..0000000 Binary files a/assets/images/example_content.png and /dev/null differ diff --git a/assets/images/room_video.png b/assets/images/room_video.png new file mode 100644 index 0000000..db2c147 Binary files /dev/null and b/assets/images/room_video.png differ diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index b7d4329..595d12b 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -1,7 +1,7 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart'; -import 'package:agora_token_generator/agora_token_generator.dart'; -import 'package:dating_touchme_app/model/live/live_chat_message.dart'; +import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; +import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:dating_touchme_app/service/live_chat_message_service.dart'; @@ -9,21 +9,36 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; +import '../../model/live/live_chat_message.dart'; + +// 当前角色 +enum CurrentRole { + broadcaster, //主持 + maleAudience, //男嘉宾 + femaleAudience, //女嘉宾 + audience, //观众 + normalUser, //普通用户 +} + /// 直播房间相关控制器 class RoomController extends GetxController { RoomController({NetworkService? networkService}) - : _networkService = networkService ?? Get.find(); + : _networkService = networkService ?? Get.find(); final NetworkService _networkService; + CurrentRole currentRole = CurrentRole.normalUser; + bool isLive = false; /// 当前频道信息 final Rxn rtcChannel = Rxn(); + final Rxn rtcChannelDetail = Rxn(); /// 聊天消息列表 final RxList chatMessages = [].obs; /// 消息服务实例 - final LiveChatMessageService _messageService = LiveChatMessageService.instance; + final LiveChatMessageService _messageService = + LiveChatMessageService.instance; @override void onInit() { @@ -54,10 +69,12 @@ class RoomController extends GetxController { /// 添加消息到列表(带去重和数量限制) void _addMessage(LiveChatMessage message) { // 去重:检查是否已存在相同的消息(基于 userId + content + timestamp) - final exists = chatMessages.any((m) => - m.userId == message.userId && - m.content == message.content && - (m.timestamp - message.timestamp).abs() < 1000); // 1秒内的相同消息视为重复 + final exists = chatMessages.any( + (m) => + m.userId == message.userId && + m.content == message.content && + (m.timestamp - message.timestamp).abs() < 1000, + ); // 1秒内的相同消息视为重复 if (exists) { print('⚠️ 消息已存在,跳过添加'); @@ -68,7 +85,7 @@ class RoomController extends GetxController { print('✅ 消息已添加到列表,当前消息数: ${chatMessages.length}'); // 限制消息数量,最多保留100条 - if (chatMessages.length > 100) { + if (chatMessages.length > 300) { chatMessages.removeAt(0); print('📝 消息列表已满,移除最旧的消息'); } @@ -84,7 +101,14 @@ class RoomController extends GetxController { final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; - await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster); + currentRole = CurrentRole.broadcaster; + isLive = true; + await _joinRtcChannel( + base.data!.token, + base.data!.channelId, + base.data!.uid, + ClientRoleType.clientRoleBroadcaster, + ); } else { final message = base.message.isNotEmpty ? base.message : '创建频道失败'; SmartDialog.showToast(message); @@ -100,7 +124,13 @@ class RoomController extends GetxController { final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; - await _joinRtcChannel(base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleAudience); + currentRole = CurrentRole.normalUser; + await _joinRtcChannel( + base.data!.token, + channelName, + base.data!.uid, + ClientRoleType.clientRoleAudience, + ); } } catch (e) { SmartDialog.showToast('加入频道异常:$e'); @@ -111,9 +141,10 @@ class RoomController extends GetxController { String token, String channelName, int uid, - ClientRoleType roleType + ClientRoleType roleType, ) async { try { + await _fetchRtcChannelDetail(channelName); await RTCManager.instance.joinChannel( token: token, channelId: channelName, @@ -125,9 +156,94 @@ class RoomController extends GetxController { } } + Future joinChat(CurrentRole role) async { + final data = { + 'channelId': RTCManager.instance.currentChannelId, + 'seatNumber': role == CurrentRole.maleAudience ? 1 : 2, + 'isMicrophoneOn': role != CurrentRole.normalUser ? true : false, + 'isVideoOn': + role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience + ? true + : false, + }; + final response = await _networkService.rtcApi.connectRtcChannel(data); + if (!response.data.isSuccess) { + SmartDialog.showToast(response.data.message); + return; + } + currentRole = role; + if (role == CurrentRole.maleAudience || + role == CurrentRole.femaleAudience) { + await RTCManager.instance.publishVideo(role); + } else { + await RTCManager.instance.publishAudio(); + } + RtcSeatUserInfo userInfo = RtcSeatUserInfo( + uid: rtcChannel.value?.uid, + miId: GlobalData().userData?.id ?? '', + userId: GlobalData().userData?.id ?? '', + nickName: GlobalData().userData?.nickName ?? '', + profilePhoto: GlobalData().userData?.profilePhoto ?? '', + seatNumber: role == CurrentRole.maleAudience ? 1 : 2, + isFriend: false, + isMicrophoneOn: true, + isVideoOn: role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience ? true : false, + genderCode: GlobalData().userData?.genderCode ?? 0, + ); + final newDetail = RtcChannelDetail( + channelId: rtcChannelDetail.value!.channelId, + anchorInfo: rtcChannelDetail.value!.anchorInfo, + maleInfo: role == CurrentRole.maleAudience ? userInfo : null, + femaleInfo: role == CurrentRole.femaleAudience ? userInfo : null, + ); + rtcChannelDetail.value = newDetail; + isLive = true; + } + + Future leaveChat() async { + final data = {'channelId': RTCManager.instance.currentChannelId}; + final response = await _networkService.rtcApi.disconnectRtcChannel(data); + if (response.data.isSuccess) { + isLive = false; + await RTCManager.instance.unpublish(currentRole); + if (currentRole == CurrentRole.maleAudience) { + final newDetail = RtcChannelDetail( + channelId: rtcChannelDetail.value!.channelId, + anchorInfo: rtcChannelDetail.value!.anchorInfo, + maleInfo: null, + femaleInfo: rtcChannelDetail.value!.femaleInfo, + ); + rtcChannelDetail.value = newDetail; + } else if (currentRole == CurrentRole.femaleAudience) { + final newDetail = RtcChannelDetail( + channelId: rtcChannelDetail.value!.channelId, + anchorInfo: rtcChannelDetail.value!.anchorInfo, + maleInfo: rtcChannelDetail.value!.maleInfo, + femaleInfo: null, + ); + rtcChannelDetail.value = newDetail; + } + currentRole = CurrentRole.normalUser; + } + } + + Future _fetchRtcChannelDetail(String channelName) async { + try { + final response = await _networkService.rtcApi.getRtcChannelDetail( + channelName, + ); + final base = response.data; + if (base.isSuccess && base.data != null) { + rtcChannelDetail.value = base.data; + } + } catch (e) { + print('获取 RTC 频道详情失败:$e'); + } + } + /// 发送公屏消息 Future sendChatMessage(String content) async { - final channelName = rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId; + final channelName = RTCManager.instance.currentChannelId; final result = await _messageService.sendMessage( content: content, @@ -140,11 +256,6 @@ class RoomController extends GetxController { } } - /// 发送消息(保留原有方法,用于兼容) - Future sendMessage(String message) async { - await sendChatMessage(message); - } - Future _ensureRtcPermissions() async { final statuses = await [Permission.camera, Permission.microphone].request(); final allGranted = statuses.values.every((status) => status.isGranted); @@ -152,8 +263,9 @@ class RoomController extends GetxController { return true; } - final permanentlyDenied = - statuses.values.any((status) => status.isPermanentlyDenied); + final permanentlyDenied = statuses.values.any( + (status) => status.isPermanentlyDenied, + ); if (permanentlyDenied) { SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); await openAppSettings(); @@ -164,7 +276,97 @@ class RoomController extends GetxController { } Future leaveChannel() async { + isLive = false; + if (currentRole == CurrentRole.maleAudience || currentRole == CurrentRole.femaleAudience) { + await RTCManager.instance.unpublish(currentRole); + } + currentRole = CurrentRole.normalUser;; await RTCManager.instance.leaveChannel(); } -} + /// 接收RTC消息 + Future receiveRTCMessage(Map message) async { + if (message['type'] == 'join_chat') { + final response = await _networkService.rtcApi.getDatingRtcChannelUserDetail( + rtcChannel.value!.channelId, + message['uid'], + ); + if (!response.data.isSuccess) { + return; + } + final currentDetail = rtcChannelDetail.value; + if (currentDetail == null) { + return; + } + if (message['role'] == 'male_audience') { + final userData = response.data.data; + // if (userData != null) { + final maleInfo = RtcSeatUserInfo( + miId: rtcChannelDetail.value!.anchorInfo!.miId, + userId: rtcChannelDetail.value!.anchorInfo!.userId, + nickName: rtcChannelDetail.value!.anchorInfo!.nickName, + profilePhoto: rtcChannelDetail.value!.anchorInfo!.profilePhoto, + genderCode: rtcChannelDetail.value!.anchorInfo!.genderCode, + seatNumber: rtcChannelDetail.value!.anchorInfo!.seatNumber, + isFriend: rtcChannelDetail.value!.anchorInfo!.isFriend, + isMicrophoneOn: rtcChannelDetail.value!.anchorInfo!.isMicrophoneOn, + isVideoOn: rtcChannelDetail.value!.anchorInfo!.isVideoOn, + uid: message['uid'] is int + ? message['uid'] as int + : int.tryParse(message['uid']?.toString() ?? ''), + ); + final newDetail = RtcChannelDetail( + channelId: currentDetail.channelId, + anchorInfo: currentDetail.anchorInfo, + maleInfo: maleInfo, + femaleInfo: currentDetail.femaleInfo, + ); + rtcChannelDetail.value = newDetail; + // } + } else if (message['role'] == 'female_audience') { + final userData = response.data.data; + // if (userData != null) { + final femaleInfo = RtcSeatUserInfo( + miId: rtcChannelDetail.value!.anchorInfo!.miId, + userId: rtcChannelDetail.value!.anchorInfo!.userId, + nickName: rtcChannelDetail.value!.anchorInfo!.nickName, + profilePhoto: rtcChannelDetail.value!.anchorInfo!.profilePhoto, + genderCode: rtcChannelDetail.value!.anchorInfo!.genderCode, + seatNumber: rtcChannelDetail.value!.anchorInfo!.seatNumber, + isFriend: rtcChannelDetail.value!.anchorInfo!.isFriend, + isMicrophoneOn: rtcChannelDetail.value!.anchorInfo!.isMicrophoneOn, + isVideoOn: rtcChannelDetail.value!.anchorInfo!.isVideoOn, + uid: message['uid'] is int + ? message['uid'] as int + : int.tryParse(message['uid']?.toString() ?? ''), + ); + final newDetail = RtcChannelDetail( + channelId: currentDetail.channelId, + anchorInfo: currentDetail.anchorInfo, + maleInfo: currentDetail.maleInfo, + femaleInfo: femaleInfo, + ); + rtcChannelDetail.value = newDetail; + } + // } + }else if (message['type'] == 'leave_chat') { + if (message['role'] == 'male_audience') { + final newDetail = RtcChannelDetail( + channelId: rtcChannelDetail.value!.channelId, + anchorInfo: rtcChannelDetail.value!.anchorInfo, + maleInfo: null, + femaleInfo: rtcChannelDetail.value!.femaleInfo, + ); + rtcChannelDetail.value = newDetail; + } else if (message['role'] == 'female_audience') { + final newDetail = RtcChannelDetail( + channelId: rtcChannelDetail.value!.channelId, + anchorInfo: rtcChannelDetail.value!.anchorInfo, + maleInfo: rtcChannelDetail.value!.maleInfo, + femaleInfo: null, + ); + rtcChannelDetail.value = newDetail; + } + } + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index dcd36c9..e242231 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -150,6 +150,7 @@ class Assets { static const String imagesRocket1 = 'assets/images/rocket1.svga'; static const String imagesRocket2 = 'assets/images/rocket2.svga'; static const String imagesRocket3 = 'assets/images/rocket3.svga'; + static const String imagesRoomVideo = 'assets/images/room_video.png'; static const String imagesRose = 'assets/images/rose.png'; static const String imagesRoseBanner = 'assets/images/rose_banner.png'; static const String imagesRoseGift = 'assets/images/rose_gift.png'; diff --git a/lib/model/rtc/rtc_channel_detail.dart b/lib/model/rtc/rtc_channel_detail.dart new file mode 100644 index 0000000..f3c4800 --- /dev/null +++ b/lib/model/rtc/rtc_channel_detail.dart @@ -0,0 +1,111 @@ +class RtcChannelDetail { + final String channelId; + final RtcSeatUserInfo? anchorInfo; + final RtcSeatUserInfo? maleInfo; + final RtcSeatUserInfo? femaleInfo; + + const RtcChannelDetail({ + required this.channelId, + this.anchorInfo, + this.maleInfo, + this.femaleInfo, + }); + + factory RtcChannelDetail.fromJson(Map json) { + return RtcChannelDetail( + channelId: json['channelId']?.toString() ?? '', + anchorInfo: _parseSeatInfo(json['anchorInfo']), + maleInfo: _parseSeatInfo(json['maleInfo']), + femaleInfo: _parseSeatInfo(json['femaleInfo']), + ); + } + + Map toJson() { + return { + 'channelId': channelId, + 'anchorInfo': anchorInfo?.toJson(), + 'maleInfo': maleInfo?.toJson(), + 'femaleInfo': femaleInfo?.toJson(), + }; + } + + static RtcSeatUserInfo? _parseSeatInfo(dynamic value) { + if (value is Map) { + return RtcSeatUserInfo.fromJson(value); + } + return null; + } +} + +class RtcSeatUserInfo { + final String miId; + final String userId; + final String nickName; + final String profilePhoto; + final int genderCode; + final int seatNumber; + final bool isFriend; + final bool isMicrophoneOn; + final bool isVideoOn; + final int? uid; + + const RtcSeatUserInfo({ + required this.miId, + required this.userId, + required this.nickName, + required this.profilePhoto, + required this.genderCode, + required this.seatNumber, + required this.isFriend, + required this.isMicrophoneOn, + required this.isVideoOn, + this.uid, + }); + + factory RtcSeatUserInfo.fromJson(Map json) { + return RtcSeatUserInfo( + miId: json['miId']?.toString() ?? '', + userId: json['userId']?.toString() ?? '', + nickName: json['nickName']?.toString() ?? '', + profilePhoto: json['profilePhoto']?.toString() ?? '', + genderCode: json['genderCode'] is int + ? json['genderCode'] as int + : int.tryParse(json['genderCode']?.toString() ?? '0') ?? 0, + seatNumber: json['seatNumber'] is int + ? json['seatNumber'] as int + : int.tryParse(json['seatNumber']?.toString() ?? '0') ?? 0, + isFriend: _parseBool(json['isFriend']), + isMicrophoneOn: _parseBool(json['isMicrophoneOn']), + isVideoOn: _parseBool(json['isVideoOn']), + uid: json['uid'] is int + ? json['uid'] as int + : int.tryParse(json['uid']?.toString() ?? ''), + ); + } + + Map toJson() { + return { + 'miId': miId, + 'userId': userId, + 'nickName': nickName, + 'profilePhoto': profilePhoto, + 'genderCode': genderCode, + 'seatNumber': seatNumber, + 'isFriend': isFriend, + 'isMicrophoneOn': isMicrophoneOn, + 'isVideoOn': isVideoOn, + 'uid': uid, + }; + } + + static bool _parseBool(dynamic value) { + if (value is bool) return value; + if (value is num) return value != 0; + if (value is String) { + return value == '1' || + value.toLowerCase() == 'true' || + value.toLowerCase() == 'yes'; + } + return false; + } +} diff --git a/lib/network/api_service.g.dart b/lib/network/api_service.g.dart index bfee117..fc7d51d 100644 --- a/lib/network/api_service.g.dart +++ b/lib/network/api_service.g.dart @@ -8,7 +8,7 @@ part of 'api_service.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _ApiService implements ApiService { _ApiService(this._dio, {this.baseUrl, this.errorLogger}) { diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 74a7918..d707a22 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -3,40 +3,77 @@ class ApiUrls { // 认证相关接口 static const String login = 'dating-agency-uec/authorize/by-captcha'; - static const String getVerificationCode = 'dating-agency-uec/authorize/get/auth-captcha'; - + static const String getVerificationCode = + 'dating-agency-uec/authorize/get/auth-captcha'; + // 用户相关接口 static const String getBaseUserInfo = 'dating-agency-uec/user/get/base-info'; static const String updatePhone = 'dating-agency-uec/user/modify/phone'; - static const String getMarriageInformationDetail = 'dating-agency-service/user/get/dongwo/marriage-information-detail'; - static const String registerMarriageInformation = 'dating-agency-service/user/register/marriage-information'; - static const String getHxUserToken = 'dating-agency-chat-audio/user/get/hx/user/token'; - static const String getApplyTempAuth = 'dating-agency-uec/get/apply-temp-auth'; - static const String saveCertificationAudit = 'dating-agency-service/user/save/certification/audit'; - static const String listVirtualCurrencyProduct = 'dating-agency-mall/user/list/virtual-currency-product'; - static const String getVirtualAccount = 'dating-agency-mall/user/get/virtual-account'; - static const String pageVirtualAccountRecord = 'dating-agency-mall/user/page/virtual-account-record'; - static const String getCertificationList = 'dating-agency-service/user/get/certification/item/all/list'; + static const String getMarriageInformationDetail = + 'dating-agency-service/user/get/dongwo/marriage-information-detail'; + static const String registerMarriageInformation = + 'dating-agency-service/user/register/marriage-information'; + static const String getHxUserToken = + 'dating-agency-chat-audio/user/get/hx/user/token'; + static const String getApplyTempAuth = + 'dating-agency-uec/get/apply-temp-auth'; + static const String saveCertificationAudit = + 'dating-agency-service/user/save/certification/audit'; + static const String listVirtualCurrencyProduct = + 'dating-agency-mall/user/list/virtual-currency-product'; + static const String getVirtualAccount = + 'dating-agency-mall/user/get/virtual-account'; + static const String pageVirtualAccountRecord = + 'dating-agency-mall/user/page/virtual-account-record'; + static const String getCertificationList = + 'dating-agency-service/user/get/certification/item/all/list'; static const String submitOrder = 'dating-agency-mall/user/submit/order'; - static const String getEducationList = 'dating-agency-service/user/get/education/list'; - static const String getIncomeList = 'dating-agency-service/user/get/income/list'; - static const String getMaritalStatusList = 'dating-agency-service/user/get/marital/status/list'; - static const String getPropertyList = 'dating-agency-service/user/get/property/permits'; - static const String getOccupationList = 'dating-agency-service/user/get/occupation/list'; - static const String getSwRtcToken = 'dating-agency-chat-audio/user/get/sw/rtc/token'; - static const String createRtcChannel = 'dating-agency-chat-audio/user/create/rtc-channel'; - static const String editOwnMarriageInformation = 'dating-agency-service/user/edit/own-marriage-information'; - static const String getSwRtmToken = 'dating-agency-chat-audio/user/get/sw/rtm/token'; - static const String listBankCardByIndividual = 'dating-agency-mall/user/list/bank-card/by-individual'; - static const String createBankCardByIndividual = 'dating-agency-mall/user/create/bank-card/by-individual'; - static const String recognizeBankCard = 'dating-agency-uec/user/recognize/bank-card'; - static const String calculateWithdrawServiceFee = 'dating-agency-mall/user/calculate/withdraw-service-fee'; - static const String applyWalletAccountWithdraw = 'dating-agency-mall/user/apply/wallet-account/withdraw'; - static const String pageWithdrawAudit = 'dating-agency-mall/user/page/withdraw-audit'; - static const String getMarriageInformationDetailsById = 'dating-agency-service/user/get/marriage/information/details/byid'; + static const String getEducationList = + 'dating-agency-service/user/get/education/list'; + static const String getIncomeList = + 'dating-agency-service/user/get/income/list'; + static const String getMaritalStatusList = + 'dating-agency-service/user/get/marital/status/list'; + static const String getPropertyList = + 'dating-agency-service/user/get/property/permits'; + static const String getOccupationList = + 'dating-agency-service/user/get/occupation/list'; + static const String getSwRtcToken = + 'dating-agency-chat-audio/user/get/sw/rtc/token'; + static const String createRtcChannel = + 'dating-agency-chat-audio/user/create/rtc-channel'; + static const String editOwnMarriageInformation = + 'dating-agency-service/user/edit/own-marriage-information'; + static const String getSwRtmToken = + 'dating-agency-chat-audio/user/get/sw/rtm/token'; + static const String getRtcChannelDetail = + 'dating-agency-chat-audio/user/get/dating-rtc-channel/detail'; + static const String connectRtcChannel = + 'dating-agency-chat-audio/user/connect/rtc-channel'; + static const String getDatingRtcChannelUserDetail = + 'dating-agency-chat-audio/user/get/dating-rtc-channel-user/detail'; + static const String enableRtcChannelUserAudio = + 'dating-agency-chat-audio/user/enable/rtc-channel-user/audio'; + static const String disconnectRtcChannel = + 'dating-agency-chat-audio/user/disconnect/rtc-channel'; + static const String listBankCardByIndividual = + 'dating-agency-mall/user/list/bank-card/by-individual'; + static const String createBankCardByIndividual = + 'dating-agency-mall/user/create/bank-card/by-individual'; + static const String recognizeBankCard = + 'dating-agency-uec/user/recognize/bank-card'; + static const String calculateWithdrawServiceFee = + 'dating-agency-mall/user/calculate/withdraw-service-fee'; + static const String applyWalletAccountWithdraw = + 'dating-agency-mall/user/apply/wallet-account/withdraw'; + static const String pageWithdrawAudit = + 'dating-agency-mall/user/page/withdraw-audit'; + static const String getMarriageInformationDetailsById = + 'dating-agency-service/user/get/marriage/information/details/byid'; //首页相关接口 - static const String getMarriageList = 'dating-agency-service/user/page/dongwo/marriage-information'; - + static const String getMarriageList = + 'dating-agency-service/user/page/dongwo/marriage-information'; + // 后续可以在此添加更多API端点 -} \ No newline at end of file +} diff --git a/lib/network/home_api.g.dart b/lib/network/home_api.g.dart index 81156a1..84908ed 100644 --- a/lib/network/home_api.g.dart +++ b/lib/network/home_api.g.dart @@ -8,7 +8,7 @@ part of 'home_api.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _HomeApi implements HomeApi { _HomeApi(this._dio, {this.baseUrl, this.errorLogger}); diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 860c19c..1d3cba6 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -1,4 +1,5 @@ import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; +import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/api_urls.dart'; import 'package:dating_touchme_app/network/response_model.dart'; import 'package:dio/dio.dart'; @@ -14,7 +15,7 @@ abstract class RtcApi { /// 创建实时音视频频道 @GET(ApiUrls.getSwRtcToken) Future>> getSwRtcToken( - @Query('channelId') String channelId, + @Query('channelId') String channelId, ); /// 获取声网 RTM Token @@ -24,5 +25,35 @@ abstract class RtcApi { /// 创建实时音视频频道(返回字符串) @POST(ApiUrls.createRtcChannel) Future>> createRtcChannel(); -} + /// 获取 RTC 频道详情 + @GET(ApiUrls.getRtcChannelDetail) + Future>> getRtcChannelDetail( + @Query('channelId') String channelId, + ); + + /// 连接 RTC 频道 + @POST(ApiUrls.connectRtcChannel) + Future>> connectRtcChannel( + @Body() Map data, + ); + + /// 获取 RTC 频道用户详情 + @GET(ApiUrls.getDatingRtcChannelUserDetail) + Future>> getDatingRtcChannelUserDetail( + @Query('channelId') String channelId, + @Query('uId') int uId, + ); + + /// 启用/禁用 RTC 频道用户音频 + @POST(ApiUrls.enableRtcChannelUserAudio) + Future>> enableRtcChannelUserAudio( + @Body() Map data, + ); + + /// 断开 RTC 频道连接 + @POST(ApiUrls.disconnectRtcChannel) + Future>> disconnectRtcChannel( + @Body() Map data, + ); +} diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 8166434..3f0d213 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -8,7 +8,7 @@ part of 'rtc_api.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _RtcApi implements RtcApi { _RtcApi(this._dio, {this.baseUrl, this.errorLogger}); @@ -114,6 +114,179 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>> getRtcChannelDetail( + String channelId, + ) async { + final _extra = {}; + final queryParameters = {r'channelId': channelId}; + final _headers = {}; + const Map? _data = null; + final _options = + _setStreamType>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/get/dating-rtc-channel/detail', + 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) => RtcChannelDetail.fromJson(json as Map), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + @override + Future>> connectRtcChannel( + 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/connect/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>> + getDatingRtcChannelUserDetail(String channelId, int uId) async { + final _extra = {}; + final queryParameters = { + r'channelId': channelId, + r'uId': uId, + }; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/get/dating-rtc-channel-user/detail', + 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) => RtcSeatUserInfo.fromJson(json as Map), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + @override + Future>> enableRtcChannelUserAudio( + 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/enable/rtc-channel-user/audio', + 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>> disconnectRtcChannel( + 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/disconnect/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/network/user_api.g.dart b/lib/network/user_api.g.dart index 9674381..c841dd3 100644 --- a/lib/network/user_api.g.dart +++ b/lib/network/user_api.g.dart @@ -8,7 +8,7 @@ part of 'user_api.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter class _UserApi implements UserApi { _UserApi(this._dio, {this.baseUrl, this.errorLogger}); diff --git a/lib/pages/discover/discover_page.dart b/lib/pages/discover/discover_page.dart index e1ae252..3212786 100644 --- a/lib/pages/discover/discover_page.dart +++ b/lib/pages/discover/discover_page.dart @@ -62,7 +62,6 @@ class _DiscoverPageState extends State ), Container( padding: EdgeInsets.symmetric(horizontal: 12.w), - height: MediaQuery.of(context).size.height - 64, // constraints: BoxConstraints(minHeight: ScreenUtil().setHeight(800)), child: Column( children: [ @@ -167,7 +166,7 @@ class _LiveItemState extends State { return InkWell( onTap: () async{ // Get.to(() => LiveRoomPage(id: 0)); - await roomController.joinChannel('1189028638616588288'); + await roomController.joinChannel('1190140590348701696'); }, child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(10.w)), diff --git a/lib/pages/discover/live_room_page.dart b/lib/pages/discover/live_room_page.dart index 3b9295b..370848d 100644 --- a/lib/pages/discover/live_room_page.dart +++ b/lib/pages/discover/live_room_page.dart @@ -89,7 +89,7 @@ class _LiveRoomPageState extends State { // 发送消息 await _roomController.sendChatMessage(content); - + // 清空输入框 _messageController.clear(); message = ''; @@ -159,29 +159,23 @@ class _LiveRoomPageState extends State { child: Column( children: [ SizedBox(height: 10.w), - Builder( - builder: (context) { - // 从 GlobalData 获取当前用户信息 - final userData = GlobalData().userData; - final userName = userData?.nickName ?? '用户'; - // 人气数可以从接口获取,这里先用默认值或从其他地方获取 - final popularityText = '0'; // TODO: 从接口获取真实人气数 - - return LiveRoomUserHeader( - userName: userName, - popularityText: popularityText, - avatarAsset: (userData?.profilePhoto != null && - userData!.profilePhoto!.isNotEmpty) - ? userData.profilePhoto! - : Assets.imagesUserAvatar, - ); - }, - ), + Obx(() { + final detail = _roomController.rtcChannelDetail.value; + final anchorInfo = detail?.anchorInfo; + + final userName = anchorInfo!.nickName; + final avatarAsset = anchorInfo.profilePhoto; + const popularityText = '0'; // TODO: 使用真实数据 + + return LiveRoomUserHeader( + userName: userName, + popularityText: popularityText, + avatarAsset: avatarAsset, + ); + }), SizedBox(height: 7.w), LiveRoomAnchorShowcase(), SizedBox(height: 5.w), - const LiveRoomSeatList(), - SizedBox(height: 5.w), const LiveRoomActiveSpeaker(), SizedBox(height: 9.w), const LiveRoomNoticeChatPanel(), diff --git a/lib/pages/home/nearby_tab.dart b/lib/pages/home/nearby_tab.dart index b4c5ff3..d82b14b 100644 --- a/lib/pages/home/nearby_tab.dart +++ b/lib/pages/home/nearby_tab.dart @@ -94,7 +94,7 @@ class _NearbyTabState extends State with AutomaticKeepAliveClientMixi height: MediaQuery.of(context).size.height - totalBottomPadding, child: ListView.separated( // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 - padding: EdgeInsets.only(left: 12, right: 12, bottom: 12), + padding: EdgeInsets.only(left: 12, right: 12), itemBuilder: (context, index) { // 空数据状态 if (controller.nearbyFeed.isEmpty && index == 0) { diff --git a/lib/pages/home/recommend_tab.dart b/lib/pages/home/recommend_tab.dart index b6cfb19..3f617a1 100644 --- a/lib/pages/home/recommend_tab.dart +++ b/lib/pages/home/recommend_tab.dart @@ -37,6 +37,18 @@ class _RecommendTabState extends State final tabBarHeight = 64.0; final totalBottomPadding = bottomPadding + tabBarHeight; return Obx(() { + if (controller.recommendIsLoading.value && controller.recommendFeed.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载数据中...'), + ], + ), + ); + } return EasyRefresh( controller: _refreshController, header: const ClassicHeader( @@ -90,38 +102,32 @@ class _RecommendTabState extends State _refreshController.finishLoad(IndicatorResult.fail); } }, - child: SizedBox( - height: MediaQuery.of(context).size.height - totalBottomPadding, - child: ListView.separated( + child: ListView.separated( // 关键:始终允许滚动,即使内容不足 // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 - padding: EdgeInsets.only(left: 12, right: 12, bottom: 12), + padding: EdgeInsets.only(left: 12, right: 12), itemBuilder: (context, index) { // 空数据状态 if (controller.recommendFeed.isEmpty && index == 0) { // 使用足够的高度确保可以滚动 if (controller.recommendIsLoading.value) { - return SizedBox( - height: MediaQuery.of(context).size.height - totalBottomPadding, - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text('加载数据中...'), - ], - ), + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载数据中...'), + ], ), ); } else { - return SizedBox( - height: MediaQuery.of(context).size.height - totalBottomPadding, - child: const Center( - child: Text( - "暂无数据", - style: TextStyle(fontSize: 14, color: Color(0xFF999999)), - ), + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('暂无数据'), + ], ), ); } @@ -140,7 +146,6 @@ class _RecommendTabState extends State // 至少显示一个 item(用于显示加载或空状态) itemCount: controller.recommendFeed.isEmpty ? 1 : controller.recommendFeed.length, ) - ), ); }); } diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index cd3f599..1876b4c 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -1,5 +1,4 @@ import 'package:dating_touchme_app/generated/assets.dart'; -import 'package:dating_touchme_app/pages/main/tabbar/main_tab_bar.dart'; import 'package:dating_touchme_app/pages/message/message_page.dart'; import 'package:dating_touchme_app/pages/mine/mine_page.dart'; import 'package:dating_touchme_app/rtc/rtm_manager.dart'; @@ -10,6 +9,7 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:dating_touchme_app/controller/mine/user_controller.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../extension/router_service.dart'; import '../../widget/double_tap_to_exit_widget.dart'; @@ -67,18 +67,6 @@ class _MainPageState extends State { initRTM() async { String? userId = storage.read('userId'); await RTMManager.instance.initialize(appId: '4c2ea9dcb4c5440593a418df0fdd512d', userId: userId ?? ''); - SmartDialog.show( - alignment: Alignment.center, - builder: (context){ - return SVGAEasyPlayer( - assetsName: Assets.imagesRocket2, - fit: BoxFit.contain, - ); - } - ); - Future.delayed(const Duration(seconds: 4), () { - SmartDialog.dismiss(); - }); } @override @@ -87,29 +75,44 @@ class _MainPageState extends State { child: Scaffold( backgroundColor: Colors.transparent, resizeToAvoidBottomInset: false, - body: Stack( - alignment: Alignment.bottomCenter, + body: PageView( + physics: const NeverScrollableScrollPhysics(), + controller: pageController, children: [ - PageView( - physics: const NeverScrollableScrollPhysics(), - controller: pageController, - children: [ - homePage, // 使用成员变量引用 - discoverPage, - messagePage, - minePage, - ], - ), - MainTabBar( - initialIndex: currentIndex, - onTabChanged: (index) { - currentIndex = index; - pageController.jumpToPage(index); - }, - ), + homePage, // 使用成员变量引用 + discoverPage, + messagePage, + minePage, ], ), + bottomNavigationBar: TDBottomTabBar( + currentIndex: currentIndex, + TDBottomTabBarBasicType.iconText, + componentType: TDBottomTabBarComponentType.normal, + useVerticalDivider: false, + navigationTabs: [ + tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0), + tabItem('找对象', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1), + tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 2), + tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 3), + ] + ) ), ); } + + /// 底部导航栏item + TDBottomTabBarTabConfig tabItem(String title, String selectedIcon, String unselectedIcon, int index) { + return TDBottomTabBarTabConfig( + tabText: title, + selectedIcon: Image.asset(selectedIcon, width: 25, height: 25, fit: BoxFit.cover), + unselectedIcon: Image.asset(unselectedIcon, width: 25, height: 25, fit: BoxFit.cover), + selectTabTextStyle: TextStyle(color: Color(0xFFED4AC3)), + unselectTabTextStyle: TextStyle(color: Color(0xFF999999)), + onTap: () { + currentIndex = index; + pageController.jumpToPage(index); + }, + ); + } } diff --git a/lib/pages/main/tabbar/main_tab_bar.dart b/lib/pages/main/tabbar/main_tab_bar.dart deleted file mode 100644 index d1fbece..0000000 --- a/lib/pages/main/tabbar/main_tab_bar.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'dart:io'; -import 'package:collection/collection.dart'; -import 'package:dating_touchme_app/extension/ex_context.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; - -import '../../../generated/assets.dart'; -import 'main_tab_btn.dart'; -import 'main_tab_item.dart'; - -class MainTabBar extends StatefulWidget { - const MainTabBar({super.key, this.initialIndex = 0, this.onTabChanged}); - - final int initialIndex; - final void Function(int index)? onTabChanged; - - @override - State createState() => _MainTabBarState(); -} - -class _MainTabBarState extends State { - int _selecteIndex = 0; - - final List items = const [ - MainTabItem( - title: "首页", - icon: Assets.imagesHomeNol, - selectedIcon: Assets.imagesHomePre, - ), - MainTabItem( - title: "找对象", - icon: Assets.imagesDiscoverNol, - selectedIcon: Assets.imagesDiscoverPre, - ), - MainTabItem( - title: "消息", - icon: Assets.imagesMessageNol, - selectedIcon: Assets.imagesMessagePre, - ), - MainTabItem( - title: "我的", - icon: Assets.imagesMineNol, - selectedIcon: Assets.imagesMinePre, - ), - ]; - - @override - void initState() { - _selecteIndex = widget.initialIndex; - super.initState(); - } - - @override - void didUpdateWidget(covariant MainTabBar oldWidget) { - super.didUpdateWidget(oldWidget); - if (_selecteIndex != widget.initialIndex) { - _selecteIndex = widget.initialIndex; - } - } - - @override - Widget build(BuildContext context) { - return Builder(builder: (context) { - if (Platform.isIOS) { - if (context.bottomPadding > 0) { - return Container( - height: 64.w, - color: Colors.white, - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: items.mapIndexed((index, item) { - return MainTabButton( - selected: index == _selecteIndex, - isRedDot: item.showRedDot, - title: item.title, - onTap: () { - if (_selecteIndex != index) { - _selecteIndex = index; - setState(() {}); - widget.onTabChanged?.call(index); - } - }, - icon: item.icon, - selectedIcon: item.selectedIcon, - ); - }).toList()), - SizedBox( - height: 22.w, - ) - ], - ) - ], - ), - ); - } else { - return SafeArea( - top: false, - child: Container( - height: 64.w, - color: Colors.white, - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: items.mapIndexed((index, item) { - return MainTabButton( - selected: index == _selecteIndex, - isRedDot: item.showRedDot, - title: item.title, - onTap: () { - if (_selecteIndex != index) { - _selecteIndex = index; - setState(() {}); - widget.onTabChanged?.call(index); - } - }, - icon: item.icon, - selectedIcon: item.selectedIcon, - ); - }).toList()), - SizedBox( - height: 14.w, - ) - ], - ) - ], - ), - ), - ); - } - } else { - return SafeArea( - top: false, - child: Container( - // padding: EdgeInsets.only(bottom: 14.w), - // margin: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 14.w), - height: 64.w, - color: Colors.white, - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: items.mapIndexed((index, item) { - return MainTabButton( - selected: index == _selecteIndex, - isRedDot: item.showRedDot, - title: item.title, - onTap: () { - if (_selecteIndex != index) { - _selecteIndex = index; - setState(() {}); - widget.onTabChanged?.call(index); - } - }, - icon: item.icon, - selectedIcon: item.selectedIcon, - ); - }).toList()), - SizedBox( - height: 14.w, - ) - ], - ) - ], - ), - ), - ); - } - }); - } -} diff --git a/lib/pages/main/tabbar/main_tab_btn.dart b/lib/pages/main/tabbar/main_tab_btn.dart deleted file mode 100644 index 5b61c36..0000000 --- a/lib/pages/main/tabbar/main_tab_btn.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; - -class MainTabButton extends StatefulWidget { - final VoidCallback? onTap; - final bool isRedDot; - final bool selected; - final String icon; - final String title; - final String selectedIcon; - - const MainTabButton({ - super.key, - required this.selected, - required this.icon, - required this.selectedIcon, - this.isRedDot = false, - this.onTap, - required this.title - }); - - @override - State createState() => MainTabButtonState(); -} - -class MainTabButtonState extends State { - @override - Widget build(BuildContext context) { - if (widget.isRedDot) { - return Expanded( - child: GestureDetector( - onTap: widget.onTap, - child: Container( - color: Colors.transparent, - alignment: Alignment.center, - child: Stack( - clipBehavior: Clip.none, - children: [ - Column( - children: [ - SizedBox(height: 5.w,), - widget.selected - ? Image.asset(widget.selectedIcon, width: 25.w, height: 25.w) - : Image.asset(widget.icon, width: 25.w, height: 25.w), - Text(widget.title, style: const TextStyle( - fontSize: 12 - ),) - ], - ), - ], - ), - ), - )); - } - - return Expanded( - child: GestureDetector( - onTap: widget.onTap, - child: Container( - alignment: Alignment.center, - color: Colors.transparent, - child: Column( - children: [ - SizedBox(height: 5.w,), - widget.selected - ? Image.asset(widget.selectedIcon, width: 25.w, height: 25.w) - : Image.asset(widget.icon, width: 25.w, height: 25.w), - Text(widget.title, style: const TextStyle( - fontSize: 12 - ),) - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/main/tabbar/main_tab_item.dart b/lib/pages/main/tabbar/main_tab_item.dart deleted file mode 100644 index 3c79969..0000000 --- a/lib/pages/main/tabbar/main_tab_item.dart +++ /dev/null @@ -1,13 +0,0 @@ -class MainTabItem { - final String title; - final String icon; - final String selectedIcon; - final bool showRedDot; - - const MainTabItem({ - required this.title, - required this.icon, - required this.selectedIcon, - this.showRedDot = false, - }); -} diff --git a/lib/rtc/rtc_manager.dart b/lib/rtc/rtc_manager.dart index a88da39..4403136 100644 --- a/lib/rtc/rtc_manager.dart +++ b/lib/rtc/rtc_manager.dart @@ -5,6 +5,8 @@ import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; +import '../controller/discover/room_controller.dart'; +import '../network/network_service.dart'; import '../pages/discover/live_room_page.dart'; /// RTC 管理器,负责管理声网音视频通话功能 @@ -14,6 +16,7 @@ class RTCManager { final ValueNotifier> remoteUsersNotifier = ValueNotifier>( [], ); + NetworkService get _networkService => NetworkService(); RtcEngine? get engine => _engine; bool get isInChannel => _isInChannel; int? get currentUid => _currentUid; @@ -118,7 +121,6 @@ class RTCManager { _currentChannelId = connection.channelId; print('加入频道成功,频道名:${connection.channelId},耗时:${elapsed}ms'); if (connection.localUid == _currentUid) { - await RTMManager.instance.subscribe(_currentChannelId ?? ''); await RTMManager.instance.publishChannelMessage( channelName: _currentChannelId ?? '', @@ -130,7 +132,7 @@ class RTCManager { onJoinChannelSuccess!(connection, elapsed); } }, - onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { + onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) async{ print('用户加入,UID:$remoteUid'); _handleRemoteUserJoined(remoteUid); if (onUserJoined != null) { @@ -480,4 +482,48 @@ class RTCManager { if (!removed) return; remoteUsersNotifier.value = List.unmodifiable(_remoteUserIds); } + + /// 发布视频 + Future publishVideo(CurrentRole role) async { + await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + await _engine?.muteLocalAudioStream(false); + await _engine?.muteLocalVideoStream(false); + await RTMManager.instance.publishChannelMessage( + channelName: _currentChannelId ?? '', + message: json.encode({ + 'type': 'join_chat', + 'uid': _currentUid, + 'role': role == CurrentRole.maleAudience + ? 'male_audience' + : 'female_audience', + }), + ); + } + + /// 发布音频 + Future publishAudio() async { + await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + await _engine?.muteLocalAudioStream(false); + await _engine?.muteLocalVideoStream(true); + await RTMManager.instance.publishChannelMessage( + channelName: _currentChannelId ?? '', + message: json.encode({ + 'type': 'join_chat', + 'uid': _currentUid, + 'role': 'audience', + }), + ); + } + + /// 取消发布视频 + Future unpublish(CurrentRole role) async { + await _engine?.setClientRole(role: ClientRoleType.clientRoleAudience); + await _engine?.muteLocalAudioStream(true); + await _engine?.muteLocalVideoStream(true); + await RTMManager.instance.publishChannelMessage( + channelName: _currentChannelId ?? '', + message: json.encode({'type': 'leave_chat', 'uid': _currentUid, 'role': role == CurrentRole.maleAudience + ? 'male_audience' : 'female_audience',}), + ); + } } diff --git a/lib/rtc/rtm_manager.dart b/lib/rtc/rtm_manager.dart index 2758b5c..494846b 100644 --- a/lib/rtc/rtm_manager.dart +++ b/lib/rtc/rtm_manager.dart @@ -63,7 +63,7 @@ class RTMManager { await dispose(); final (status, client) = await RTM(appId, userId, config: config); - print('RTM初始化成功'); + print(status.error ? '❌ RTM 初始化失败' : '✅ RTM 初始化成功'); if (status.error) { onOperationError?.call(status); @@ -74,13 +74,13 @@ class RTMManager { _currentUserId = userId; _isInitialized = true; _registerClientListeners(); - // final response = await _networkService.rtcApi.getSwRtmToken(); - // // 处理响应 - // if (response.data.isSuccess) { - await login('007eJxTYIhoZ/m/gMf0gdWv1PV2R6I/Lpry06W77sOR2BDuFv89JeEKDCbJRqmJlinJSSbJpiYmBqaWxokmhhYpaQZpKSmmhkYpE9/IZgrwMTDYOB4rZ2RgYmAEQhCfkcEcAPTmHg4='); - // } else { - // SmartDialog.showToast(response.data.message); - // } + final response = await _networkService.rtcApi.getSwRtmToken(); + // 处理响应 + if (response.data.isSuccess) { + await login(response.data.data!.token); + } else { + SmartDialog.showToast(response.data.message); + } return true; } diff --git a/lib/service/live_chat_message_service.dart b/lib/service/live_chat_message_service.dart index 847d913..c4d0323 100644 --- a/lib/service/live_chat_message_service.dart +++ b/lib/service/live_chat_message_service.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:agora_rtm/agora_rtm.dart'; +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/model/live/live_chat_message.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; /// 直播间聊天消息服务 @@ -56,16 +58,19 @@ class LiveChatMessageService { } /// 处理接收到的消息 - void _handleIncomingMessage(MessageEvent event) { + void _handleIncomingMessage(MessageEvent event) async{ try { // 解析消息内容 final messageText = _parseMessageContent(event.message); final messageData = json.decode(messageText) as Map; - + print('📥 收到消息: $messageData'); // 只处理聊天消息类型 if (messageData['type'] == 'chat_message') { final chatMessage = LiveChatMessage.fromJson(messageData); onMessageReceived?.call(chatMessage); + }else{ + RoomController controller = Get.find(); + await controller.receiveRTCMessage(messageData); } } catch (e, stackTrace) { final error = '解析RTM消息失败: $e'; diff --git a/lib/widget/live/live_room_anchor_showcase.dart b/lib/widget/live/live_room_anchor_showcase.dart index 90bc023..7d9d697 100644 --- a/lib/widget/live/live_room_anchor_showcase.dart +++ b/lib/widget/live/live_room_anchor_showcase.dart @@ -1,8 +1,12 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; class LiveRoomAnchorShowcase extends StatefulWidget { const LiveRoomAnchorShowcase({super.key}); @@ -13,6 +17,7 @@ class LiveRoomAnchorShowcase extends StatefulWidget { class _LiveRoomAnchorShowcaseState extends State { final RTCManager _rtcManager = RTCManager.instance; + final RoomController _roomController = Get.find(); @override Widget build(BuildContext context) { @@ -94,19 +99,25 @@ class _LiveRoomAnchorShowcaseState extends State { ], ), SizedBox(height: 5.w), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildSideAnchorCard( - isLeft: true, - micIcon: Assets.imagesMicClose, - ), - _buildSideAnchorCard( - isLeft: false, - micIcon: Assets.imagesMicOpen, - ), - ], - ), + Obx(() { + // 直接访问响应式变量以触发更新 + _roomController.rtcChannelDetail.value; + final rtcChannelDetail = + _roomController.rtcChannelDetail.value; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildSideAnchorCard( + isLeft: true, + userInfo: rtcChannelDetail?.maleInfo, + ), + _buildSideAnchorCard( + isLeft: false, + userInfo: rtcChannelDetail?.femaleInfo, + ), + ], + ); + }), ], ); }, @@ -134,12 +145,14 @@ class _LiveRoomAnchorShowcaseState extends State { canvas: const VideoCanvas(uid: 0), ), ) - : (_rtcManager.currentChannelId == null + : (_rtcManager.currentChannelId == null && _rtcManager.remoteUserIds.isEmpty ? _buildWaitingPlaceholder() : AgoraVideoView( controller: VideoViewController.remote( rtcEngine: engine, - canvas: VideoCanvas(uid: _rtcManager.remoteUserIds.first), + canvas: VideoCanvas( + uid: _rtcManager.remoteUserIds.first, + ), connection: RtcConnection( channelId: _rtcManager.currentChannelId!, ), @@ -169,17 +182,64 @@ class _LiveRoomAnchorShowcaseState extends State { ); } - Widget _buildSideAnchorCard({required bool isLeft, required String micIcon}) { + Widget _buildSideAnchorCard({ + required bool isLeft, + RtcSeatUserInfo? userInfo, + }) { + final engine = _rtcManager.engine; + final joined = _rtcManager.channelJoinedNotifier.value; + + // 判断是否是当前用户 + final bool isCurrentUser = + _roomController.currentRole == CurrentRole.maleAudience || + _roomController.currentRole == CurrentRole.femaleAudience; return Stack( children: [ - Container( - width: 177.w, - height: 175.w, - decoration: BoxDecoration( - borderRadius: isLeft - ? BorderRadius.horizontal(left: Radius.circular(18.w)) - : BorderRadius.horizontal(right: Radius.circular(18.w)), - color: const Color.fromRGBO(47, 10, 94, 1), + ClipRRect( + borderRadius: isLeft + ? BorderRadius.horizontal(left: Radius.circular(18.w)) + : BorderRadius.horizontal(right: Radius.circular(18.w)), + child: SizedBox( + width: 177.w, + height: 175.w, + child: + userInfo != null && + userInfo.uid != null && + joined && + engine != null + ? AgoraVideoView( + controller: isCurrentUser + ? VideoViewController( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 0), + ) + : VideoViewController.remote( + rtcEngine: engine, + canvas: VideoCanvas(uid: userInfo.uid!), + connection: RtcConnection( + channelId: _rtcManager.currentChannelId ?? '', + ), + ), + ) + : Container( + decoration: BoxDecoration( + borderRadius: isLeft + ? BorderRadius.horizontal(left: Radius.circular(18.w)) + : BorderRadius.horizontal( + right: Radius.circular(18.w), + ), + color: const Color.fromRGBO(47, 10, 94, 1), + ), + child: Center( + child: Text( + '等待${isLeft ? "男" : "女"}嘉宾', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 12.w, + ), + ), + ), + ), ), ), Positioned( @@ -222,34 +282,41 @@ class _LiveRoomAnchorShowcaseState extends State { ), ), ), - Positioned( - left: 5.w, - bottom: 5.w, - child: Row( - children: [ - Container( - width: 20.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(4.w)), - color: const Color.fromRGBO(0, 0, 0, .65), - ), - child: Center( - child: Image.asset(micIcon, width: 10.w, height: 11.w), + if (userInfo != null) + Positioned( + left: 5.w, + bottom: 5.w, + child: Row( + children: [ + Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + color: const Color.fromRGBO(0, 0, 0, .65), + ), + child: Center( + child: Image.asset( + userInfo.isMicrophoneOn + ? Assets.imagesMicOpen + : Assets.imagesMicClose, + width: 10.w, + height: 11.w, + ), + ), ), - ), - SizedBox(width: 5.w), - Text( - "飞翔的企鹅", - style: TextStyle( - fontSize: 11.w, - color: Colors.white, - fontWeight: FontWeight.w500, + SizedBox(width: 5.w), + Text( + userInfo.nickName, + style: TextStyle( + fontSize: 11.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), ), - ), - ], + ], + ), ), - ), ], ); } diff --git a/lib/widget/live/live_room_notice_chat_panel.dart b/lib/widget/live/live_room_notice_chat_panel.dart index f35934e..4de83f3 100644 --- a/lib/widget/live/live_room_notice_chat_panel.dart +++ b/lib/widget/live/live_room_notice_chat_panel.dart @@ -1,4 +1,6 @@ import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/controller/global.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/widget/live/live_room_chat_item.dart'; import 'package:flutter/material.dart'; @@ -9,7 +11,8 @@ class LiveRoomNoticeChatPanel extends StatefulWidget { const LiveRoomNoticeChatPanel({super.key}); @override - State createState() => _LiveRoomNoticeChatPanelState(); + State createState() => + _LiveRoomNoticeChatPanelState(); } class _LiveRoomNoticeChatPanelState extends State { @@ -30,7 +33,6 @@ class _LiveRoomNoticeChatPanelState extends State { @override Widget build(BuildContext context) { - return Container( height: 230.w, padding: EdgeInsets.only(left: 13.w, right: 9.w), @@ -73,14 +75,65 @@ class _LiveRoomNoticeChatPanelState extends State { }), ), SizedBox(width: 18.w), - Image.asset( - Assets.imagesAd, - width: 73.w, - fit: BoxFit.cover, - ), + Obx((){ + if(controller.rtcChannelDetail.value?.maleInfo == null && GlobalData().userData?.genderCode == 0 && controller.currentRole != CurrentRole.broadcaster || + controller.rtcChannelDetail.value?.femaleInfo == null && GlobalData().userData?.genderCode == 1 && controller.currentRole != CurrentRole.broadcaster || + controller.isLive && controller.currentRole != CurrentRole.broadcaster){ + return Container( + width: 120.w, + height: 55.w, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.w), + gradient: LinearGradient( + colors: controller.isLive ? [Colors.grey, Colors.grey] : [Color(0xFF7C63FF), Color(0xFF987CFF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Row( + children: [ + Image.asset( + Assets.imagesRoomVideo, + width: 26.w, + ), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + controller.isLive ? '申请连麦中' : '免费连麦', + style: TextStyle( + fontSize: 13.w, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 2.w), + controller.isLive ? const SizedBox() :Text( + '剩余2张相亲卡', + style: TextStyle( + fontSize: 9.w, + color: Colors.white.withOpacity(0.8), + ), + ), + ], + ), + ], + ), + ).onTap(() async{ + if(controller.isLive){ + await controller.leaveChat(); + }else{ + await controller.joinChat(GlobalData().userData?.genderCode == 0 ? CurrentRole.maleAudience : CurrentRole.femaleAudience); + } + }); + } + return const SizedBox(); + }), ], ), ); } } - diff --git a/lib/widget/live/live_room_user_header.dart b/lib/widget/live/live_room_user_header.dart index 20f25b5..baae02c 100644 --- a/lib/widget/live/live_room_user_header.dart +++ b/lib/widget/live/live_room_user_header.dart @@ -39,8 +39,7 @@ class LiveRoomUserHeader extends StatelessWidget { child: Row( children: [ // 支持网络图片和本地资源 - avatarAsset.startsWith('http://') || avatarAsset.startsWith('https://') - ? ClipOval( + ClipOval( child: Image.network( avatarAsset, width: 34.w, @@ -54,11 +53,6 @@ class LiveRoomUserHeader extends StatelessWidget { ); }, ), - ) - : Image.asset( - avatarAsset, - width: 34.w, - height: 34.w, ), SizedBox(width: 7.w), Column( @@ -72,50 +66,8 @@ class LiveRoomUserHeader extends StatelessWidget { color: Colors.white, ), ), - SizedBox(height: 2.w), - Row( - children: [ - Image.asset( - fireIconAsset, - width: 10.w, - height: 12.w, - ), - SizedBox(width: 4.w), - Text( - popularityText, - style: TextStyle( - fontSize: 10.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ], - ), ], ), - SizedBox(width: 15.w), - GestureDetector( - onTap: onFollowTap, - child: Container( - width: 47.w, - height: 27.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(27.w)), - color: const Color.fromRGBO(253, 43, 84, 1), - ), - child: Center( - child: Text( - '关注', - style: TextStyle( - fontSize: 13.w, - color: Colors.white, - fontWeight: FontWeight.w500, - height: 1, - ), - ), - ), - ), - ), ], ), ), diff --git a/location_plugin/example/pubspec.lock b/location_plugin/example/pubspec.lock index 678892a..36a3835 100644 --- a/location_plugin/example/pubspec.lock +++ b/location_plugin/example/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.0" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" cupertino_icons: @@ -46,7 +46,7 @@ packages: description: name: cupertino_icons sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.8" fake_async: @@ -54,7 +54,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" file: @@ -62,7 +62,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" flutter: @@ -80,7 +80,7 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" flutter_test: @@ -98,7 +98,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.0" http_parser: @@ -106,7 +106,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" integration_test: @@ -119,7 +119,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -127,7 +127,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -135,7 +135,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -143,7 +143,7 @@ packages: description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" location_plugin: @@ -158,7 +158,7 @@ packages: description: name: matcher sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.17" material_color_utilities: @@ -166,7 +166,7 @@ packages: description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.11.1" meta: @@ -174,7 +174,7 @@ packages: description: name: meta sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.16.0" path: @@ -182,7 +182,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" platform: @@ -190,7 +190,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -198,7 +198,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" process: @@ -206,7 +206,7 @@ packages: description: name: process sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.5" sky_engine: @@ -219,7 +219,7 @@ packages: description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.1" stack_trace: @@ -227,7 +227,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -235,7 +235,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -243,7 +243,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" sync_http: @@ -251,7 +251,7 @@ packages: description: name: sync_http sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.1" term_glyph: @@ -259,7 +259,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -267,7 +267,7 @@ packages: description: name: test_api sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.6" typed_data: @@ -275,7 +275,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" vector_math: @@ -283,7 +283,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -291,7 +291,7 @@ packages: description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.0.2" web: @@ -299,7 +299,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" webdriver: @@ -307,7 +307,7 @@ packages: description: name: webdriver sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" sdks: