diff --git a/lib/components/home_appbar.dart b/lib/components/home_appbar.dart index 057d2c6..8a98769 100644 --- a/lib/components/home_appbar.dart +++ b/lib/components/home_appbar.dart @@ -7,7 +7,8 @@ class HomeAppbar extends StatefulWidget { final List topNav; final void Function(int) changeNav; final Widget right; - const HomeAppbar({super.key, required this.topNav, required this.changeNav, this.right = const SizedBox()}); + final int? activeIndex; + const HomeAppbar({super.key, required this.topNav, required this.changeNav, this.right = const SizedBox(), this.activeIndex}); @override State createState() => _HomeAppbarState(); @@ -15,10 +16,12 @@ class HomeAppbar extends StatefulWidget { class _HomeAppbarState extends State { - int active = 0; + int get active => widget.activeIndex ?? _internalActive; + int _internalActive = 0; @override Widget build(BuildContext context) { + final currentActive = widget.activeIndex ?? _internalActive; return Container( padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), color: Colors.transparent, @@ -37,8 +40,10 @@ class _HomeAppbarState extends State { ...widget.topNav.asMap().entries.map((entry){ return InkWell( onTap: (){ - active = entry.key; - widget.changeNav(active); + if (widget.activeIndex == null) { + _internalActive = entry.key; + } + widget.changeNav(entry.key); setState(() { }); @@ -52,13 +57,13 @@ class _HomeAppbarState extends State { Text( entry.value, style: TextStyle( - fontSize: active == entry.key ? 19 : 17, + fontSize: currentActive == entry.key ? 19 : 17, color: const Color.fromRGBO(51, 51, 51, 1), - fontWeight: active == entry.key ? FontWeight.w600 : FontWeight.w400 + fontWeight: currentActive == entry.key ? FontWeight.w600 : FontWeight.w400 ), ), SizedBox(height: ScreenUtil().setWidth(4),), - if(active == entry.key) Image.asset( + if(currentActive == entry.key) Image.asset( Assets.imagesTabChangeIcon, width: 20, ) diff --git a/lib/controller/discover/discover_controller.dart b/lib/controller/discover/discover_controller.dart new file mode 100644 index 0000000..da68c80 --- /dev/null +++ b/lib/controller/discover/discover_controller.dart @@ -0,0 +1,68 @@ +import 'package:dating_touchme_app/model/discover/rtc_channel_model.dart'; +import 'package:dating_touchme_app/network/network_service.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +/// 发现页相关控制器 +class DiscoverController extends GetxController { + DiscoverController({NetworkService? networkService}) + : _networkService = networkService ?? Get.find(); + + final NetworkService _networkService; + + /// RTC 频道列表 + final rtcChannelList = [].obs; + + /// 加载状态 + final isLoading = false.obs; + + @override + void onInit() { + super.onInit(); + // 初始化时加载数据 + loadRtcChannelPage(); + } + + /// 获取 RTC 频道分页列表 + Future loadRtcChannelPage() async { + try { + isLoading.value = true; + final response = await _networkService.rtcApi.getRtcChannelPage(); + final base = response.data; + print('API 响应: isSuccess=${base.isSuccess}, data=${base.data}'); + if (base.isSuccess) { + if (base.data != null) { + // base.data 是 PaginatedResponse + final paginatedData = base.data!; + print( + '分页数据: total=${paginatedData.total}, records长度=${paginatedData.records.length}', + ); + + // 从 PaginatedResponse 的 records 中提取数据 + rtcChannelList.assignAll(paginatedData.records); + print('更新后的列表长度: ${rtcChannelList.length}'); + } else { + print('base.data 为 null'); + rtcChannelList.clear(); + } + } else { + final message = base.message.isNotEmpty ? base.message : '获取频道列表失败'; + print('API 请求失败: $message'); + SmartDialog.showToast(message); + rtcChannelList.clear(); + } + } catch (e, stackTrace) { + print('获取频道列表异常: $e'); + print('堆栈: $stackTrace'); + SmartDialog.showToast('获取频道列表异常:$e'); + rtcChannelList.clear(); + } finally { + isLoading.value = false; + } + } + + /// 刷新 RTC 频道列表 + Future refreshRtcChannelPage() async { + await loadRtcChannelPage(); + } +} diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index 595d12b..c0e8597 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -180,15 +180,18 @@ class RoomController extends GetxController { } 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, + 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, @@ -277,20 +280,23 @@ class RoomController extends GetxController { Future leaveChannel() async { isLive = false; - if (currentRole == CurrentRole.maleAudience || currentRole == CurrentRole.femaleAudience) { + if (currentRole == CurrentRole.maleAudience || + currentRole == CurrentRole.femaleAudience) { await RTCManager.instance.unpublish(currentRole); } - currentRole = CurrentRole.normalUser;; + 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'], - ); + final response = await _networkService.rtcApi + .getDatingRtcChannelUserDetail( + rtcChannel.value!.channelId, + message['uid'], + ); if (!response.data.isSuccess) { return; } @@ -301,55 +307,55 @@ class RoomController extends GetxController { 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; + 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; - } + 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') { + } else if (message['type'] == 'leave_chat') { if (message['role'] == 'male_audience') { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, diff --git a/lib/model/discover/rtc_channel_model.dart b/lib/model/discover/rtc_channel_model.dart new file mode 100644 index 0000000..d90e79f --- /dev/null +++ b/lib/model/discover/rtc_channel_model.dart @@ -0,0 +1,34 @@ +/// RTC 频道列表项实体类 +class RtcChannelModel { + final String channelId; + final String channelPic; + final String channelName; + + RtcChannelModel({ + required this.channelId, + required this.channelPic, + required this.channelName, + }); + + factory RtcChannelModel.fromJson(Map json) { + return RtcChannelModel( + channelId: json['channelId']?.toString() ?? '', + channelPic: json['channelPic']?.toString() ?? '', + channelName: json['channelName']?.toString() ?? '', + ); + } + + Map toJson() { + return { + 'channelId': channelId, + 'channelPic': channelPic, + 'channelName': channelName, + }; + } + + @override + String toString() { + return 'RtcChannelModel(channelId: $channelId, channelName: $channelName)'; + } +} + diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 548925e..0864030 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -56,6 +56,8 @@ class ApiUrls { '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 getRtcChannelPage = + 'dating-agency-chat-audio/user/page/rtc-channel'; static const String listBankCardByIndividual = 'dating-agency-mall/user/list/bank-card/by-individual'; static const String createBankCardByIndividual = diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 1d3cba6..54e351e 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -1,3 +1,4 @@ +import 'package:dating_touchme_app/model/discover/rtc_channel_model.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/api_urls.dart'; @@ -56,4 +57,8 @@ abstract class RtcApi { Future>> disconnectRtcChannel( @Body() Map data, ); + + /// 获取 RTC 频道分页列表 + @GET(ApiUrls.getRtcChannelPage) + Future>>> getRtcChannelPage(); } diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 3f0d213..bbcdead 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -287,6 +287,46 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>>> + getRtcChannelPage() async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = + _setStreamType< + HttpResponse>> + >( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/page/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) => PaginatedResponse.fromJson( + json as Map, + (json) => RtcChannelModel.fromJson(json as Map), + ), + ); + } 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/discover/dating_page.dart b/lib/pages/discover/dating_page.dart new file mode 100644 index 0000000..057ee77 --- /dev/null +++ b/lib/pages/discover/dating_page.dart @@ -0,0 +1,142 @@ +import 'package:dating_touchme_app/controller/discover/discover_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/pages/discover/live_item_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +import '../../controller/discover/room_controller.dart'; + +/// 相亲页面 +class DatingPage extends StatefulWidget { + const DatingPage({super.key}); + + @override + State createState() => _DatingPageState(); +} + +class _DatingPageState extends State + with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + late final DiscoverController discoverController; + late final TabController _tabController; + late final RoomController roomController; + @override + void initState() { + super.initState(); + if (Get.isRegistered()) { + discoverController = Get.find(); + } else { + discoverController = Get.put(DiscoverController()); + } + if (Get.isRegistered()) { + roomController = Get.find(); + } else { + roomController = Get.put(RoomController()); + } + _tabController = TabController(length: 4, vsync: this); + discoverController.loadRtcChannelPage(); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Column( + children: [ + TDTabBar( + tabs: [ + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('全部'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('同城'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 12, left: 12), + child: Text('相亲视频'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 12, left: 12), + child: Text('相亲语音'), + ), + ), + ], + backgroundColor: Colors.transparent, + labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), + selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), + unSelectedBgColor: Colors.transparent, + labelColor: Colors.white, + dividerHeight: 0, + tabAlignment: TabAlignment.start, + outlineType: TDTabBarOutlineType.capsule, + controller: _tabController, + showIndicator: false, + isScrollable: true, + onTap: (index) { + print('相亲页面 Tab: $index'); + }, + ), + Expanded( + child: Obx(() { + print('DatingPage Obx 触发,列表长度: ${discoverController.rtcChannelList.length}'); + print('isLoading: ${discoverController.isLoading.value}'); + if (discoverController.isLoading.value && + discoverController.rtcChannelList.isEmpty) { + return Center( + child: CircularProgressIndicator( + color: const Color.fromRGBO(108, 105, 244, 1), + ), + ); + } + if (discoverController.rtcChannelList.isEmpty) { + return Center( + child: Text( + '暂无直播频道', + style: TextStyle( + fontSize: 14.w, + color: Colors.white.withOpacity(0.7), + ), + ), + ); + } + return SingleChildScrollView( + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Wrap( + spacing: 7.w, + runSpacing: 7.w, + children: discoverController.rtcChannelList.map((channel) { + print('渲染频道: ${channel.channelId}, ${channel.channelName}'); + return LiveItemWidget( + channel: channel, + channelId: channel.channelId, + ).onTap(() async{ + await roomController.joinChannel(channel.channelId); + }); + }).toList(), + ), + ); + }), + ), + ], + ); + } + + @override + bool get wantKeepAlive => true; +} + diff --git a/lib/pages/discover/discover_page.dart b/lib/pages/discover/discover_page.dart index 3212786..5a69f78 100644 --- a/lib/pages/discover/discover_page.dart +++ b/lib/pages/discover/discover_page.dart @@ -1,11 +1,11 @@ import 'package:dating_touchme_app/components/home_appbar.dart'; import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/generated/assets.dart'; -import 'package:dating_touchme_app/pages/discover/live_room_page.dart'; +import 'package:dating_touchme_app/pages/discover/dating_page.dart'; +import 'package:dating_touchme_app/pages/discover/party_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:tdesign_flutter/tdesign_flutter.dart'; class DiscoverPage extends StatefulWidget { const DiscoverPage({super.key}); @@ -15,28 +15,19 @@ class DiscoverPage extends StatefulWidget { } class _DiscoverPageState extends State - with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + with AutomaticKeepAliveClientMixin { late final RoomController roomController; + late final PageController _pageController; List topNav = ["相亲", "聚会脱单"]; - List liveList = [ - {"isNew": true}, - {"isNew": true}, - {"isNew": false}, - {"isNew": false}, - {"isNew": false}, - {"isNew": false}, - {"isNew": false}, - {"isNew": false}, - {"isNew": false}, - {"isNew": false}, - ]; - int active = 0; void changeNav(int active) { - print("当前项: $active"); + setState(() { + this.active = active; + _pageController.jumpToPage(active); + }); } @override @@ -47,6 +38,13 @@ class _DiscoverPageState extends State } else { roomController = Get.put(RoomController()); } + _pageController = PageController(initialPage: 0); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); } @override @@ -68,6 +66,7 @@ class _DiscoverPageState extends State HomeAppbar( topNav: topNav, changeNav: changeNav, + activeIndex: active, right: InkWell( onTap: () async { await roomController.createRtcChannel(); @@ -92,40 +91,19 @@ class _DiscoverPageState extends State ), ), ), - TDTabBar( - tabs: [ - TDTab(child: Padding(padding: EdgeInsets.only(right: 16, left: 16), child: Text('全部'))), - TDTab(child: Padding(padding: EdgeInsets.only(right: 16, left: 16), child: Text('同城'))), - TDTab(child: Padding(padding: EdgeInsets.only(right: 12, left: 12), child: Text('相亲视频'))), - TDTab(child: Padding(padding: EdgeInsets.only(right: 12, left: 12), child: Text('相亲语音'))) - ], - // width: MediaQuery.of(context).size.width - 64, - backgroundColor: Colors.transparent, - labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), - selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), - unSelectedBgColor: Colors.transparent, - labelColor: Colors.white, - dividerHeight: 0, - tabAlignment: TabAlignment.start, - outlineType: TDTabBarOutlineType.capsule, - controller: TabController(length: 4, vsync: this), - showIndicator: false, - isScrollable: true, - onTap: (index){ - print(index); - }, - ), Expanded( - child: SingleChildScrollView( - child: Wrap( - spacing: 7.w, - runSpacing: 7.w, - children: [ - ...liveList.map((e) { - return LiveItem(item: e); - }), - ], - ), + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (index) { + setState(() { + active = index; + }); + }, + children: const [ + DatingPage(), + PartyPage(), + ], ), ), ], @@ -138,185 +116,3 @@ class _DiscoverPageState extends State @override bool get wantKeepAlive => true; } - -class LiveItem extends StatefulWidget { - final Map item; - const LiveItem({super.key, required this.item}); - - @override - State createState() => _LiveItemState(); -} - -class _LiveItemState extends State { - - late final RoomController roomController; - - @override - void initState() { - super.initState(); - if (Get.isRegistered()) { - roomController = Get.find(); - } else { - roomController = Get.put(RoomController()); - } - } - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () async{ - // Get.to(() => LiveRoomPage(id: 0)); - await roomController.joinChannel('1190140590348701696'); - }, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(10.w)), - child: Stack( - children: [ - Container( - width: 171.w, - height: 171.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10.w)), - ), - child: Image.network( - "https://picsum.photos/400", - width: 171.w, - height: 171.w, - ), - ), - Positioned( - top: 0, - left: 0, - child: Stack( - children: [ - Image.asset( - Assets.imagesSubscript, - width: 56.w, - height: 16.w, - ), - SizedBox( - height: 16.w, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(width: 5.w), - Image.asset( - Assets.imagesLocationIcon, - width: 6.w, - height: 7.w, - ), - SizedBox(width: 3.w), - Text( - "49.9km", - style: TextStyle( - fontSize: 8.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ), - if (widget.item["isNew"]) - Positioned( - top: 9.w, - right: 8.w, - child: Container( - width: 39.w, - height: 13.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(13.w)), - color: const Color.fromRGBO(0, 0, 0, .3), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 5.w, - height: 5.w, - margin: EdgeInsets.only(right: 3.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.w)), - color: const Color.fromRGBO(255, 209, 43, 1), - ), - ), - Text( - "等待", - style: TextStyle( - fontSize: 8.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ), - Positioned( - left: 9.w, - bottom: 6.w, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 64.w, - child: Text( - "一直一直在等你一直一直在等你......", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 8.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ), - SizedBox(height: 2.w), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "福州 | 28岁", - style: TextStyle( - fontSize: 11.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(width: 5.w), - if (widget.item["isNew"]) - Container( - width: 32.w, - height: 10.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(10.w), - ), - color: const Color.fromRGBO(255, 206, 28, .8), - ), - child: Center( - child: Text( - "新人", - style: TextStyle( - fontSize: 8.w, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/discover/live_item_widget.dart b/lib/pages/discover/live_item_widget.dart new file mode 100644 index 0000000..d7ae3c8 --- /dev/null +++ b/lib/pages/discover/live_item_widget.dart @@ -0,0 +1,219 @@ +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/model/discover/rtc_channel_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +/// 直播项组件 +class LiveItemWidget extends StatefulWidget { + final dynamic item; + final String? channelId; + final RtcChannelModel? channel; + const LiveItemWidget({ + super.key, + this.item, + this.channelId, + this.channel, + }); + + @override + State createState() => _LiveItemWidgetState(); +} + +class _LiveItemWidgetState extends State { + late final RoomController roomController; + + @override + void initState() { + super.initState(); + if (Get.isRegistered()) { + roomController = Get.find(); + } else { + roomController = Get.put(RoomController()); + } + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () async { + if (widget.channelId != null && widget.channelId!.isNotEmpty) { + await roomController.joinChannel(widget.channelId!); + } else if (widget.item is Map && widget.item['channelId'] != null) { + await roomController.joinChannel(widget.item['channelId'].toString()); + } + }, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(10.w)), + child: Stack( + children: [ + Container( + width: 171.w, + height: 171.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: widget.channel != null && + widget.channel!.channelPic.isNotEmpty + ? Image.network( + widget.channel!.channelPic, + width: 171.w, + height: 171.w, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('图片加载失败: ${widget.channel!.channelPic}'); + return Image.network( + "https://picsum.photos/400", + width: 171.w, + height: 171.w, + fit: BoxFit.cover, + ); + }, + ) + : Image.network( + "https://picsum.photos/400", + width: 171.w, + height: 171.w, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 0, + left: 0, + child: Stack( + children: [ + Image.asset( + Assets.imagesSubscript, + width: 56.w, + height: 16.w, + ), + SizedBox( + height: 16.w, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 5.w), + Image.asset( + Assets.imagesLocationIcon, + width: 6.w, + height: 7.w, + ), + SizedBox(width: 3.w), + Text( + "49.9km", + style: TextStyle( + fontSize: 8.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + if (widget.item != null && widget.item is Map && widget.item["isNew"] == true) + Positioned( + top: 9.w, + right: 8.w, + child: Container( + width: 39.w, + height: 13.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(13.w)), + color: const Color.fromRGBO(0, 0, 0, .3), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 5.w, + height: 5.w, + margin: EdgeInsets.only(right: 3.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(5.w)), + color: const Color.fromRGBO(255, 209, 43, 1), + ), + ), + Text( + "等待", + style: TextStyle( + fontSize: 8.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + Positioned( + left: 9.w, + bottom: 6.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 64.w, + child: Text( + widget.channel != null && widget.channel!.channelName.isNotEmpty + ? widget.channel!.channelName + : "一直一直在等你一直一直在等你......", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 8.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(height: 2.w), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "福州 | 28岁", + style: TextStyle( + fontSize: 11.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 5.w), + if (widget.item != null && widget.item is Map && widget.item["isNew"] == true) + Container( + width: 32.w, + height: 10.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(10.w), + ), + color: const Color.fromRGBO(255, 206, 28, .8), + ), + child: Center( + child: Text( + "新人", + style: TextStyle( + fontSize: 8.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} + diff --git a/lib/pages/discover/party_page.dart b/lib/pages/discover/party_page.dart new file mode 100644 index 0000000..a5975a0 --- /dev/null +++ b/lib/pages/discover/party_page.dart @@ -0,0 +1,130 @@ +import 'package:dating_touchme_app/controller/discover/discover_controller.dart'; +import 'package:dating_touchme_app/pages/discover/live_item_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +/// 聚会脱单页面 +class PartyPage extends StatefulWidget { + const PartyPage({super.key}); + + @override + State createState() => _PartyPageState(); +} + +class _PartyPageState extends State + with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + late final DiscoverController discoverController; + late final TabController _tabController; + + @override + void initState() { + super.initState(); + if (Get.isRegistered()) { + discoverController = Get.find(); + } else { + discoverController = Get.put(DiscoverController()); + } + _tabController = TabController(length: 4, vsync: this); + discoverController.loadRtcChannelPage(); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Column( + children: [ + TDTabBar( + tabs: [ + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('全部'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('同城'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 12, left: 12), + child: Text('相亲视频'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 12, left: 12), + child: Text('相亲语音'), + ), + ), + ], + backgroundColor: Colors.transparent, + labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), + selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), + unSelectedBgColor: Colors.transparent, + labelColor: Colors.white, + dividerHeight: 0, + tabAlignment: TabAlignment.start, + outlineType: TDTabBarOutlineType.capsule, + controller: _tabController, + showIndicator: false, + isScrollable: true, + onTap: (index) { + print('聚会脱单页面 Tab: $index'); + }, + ), + Expanded( + child: Obx(() { + if (discoverController.isLoading.value && + discoverController.rtcChannelList.isEmpty) { + return Center( + child: CircularProgressIndicator( + color: const Color.fromRGBO(108, 105, 244, 1), + ), + ); + } + if (discoverController.rtcChannelList.isEmpty) { + return Center( + child: Text( + '暂无直播频道', + style: TextStyle( + fontSize: 14.w, + color: Colors.white.withOpacity(0.7), + ), + ), + ); + } + return SingleChildScrollView( + child: Wrap( + spacing: 7.w, + runSpacing: 7.w, + children: [ + ...discoverController.rtcChannelList.map((channel) { + return LiveItemWidget( + channel: channel, + channelId: channel.channelId, + ); + }), + ], + ), + ); + }), + ), + ], + ); + } + + @override + bool get wantKeepAlive => true; +} + diff --git a/lib/rtc/rtc_manager.dart b/lib/rtc/rtc_manager.dart index 4403136..e7eca2e 100644 --- a/lib/rtc/rtc_manager.dart +++ b/lib/rtc/rtc_manager.dart @@ -16,7 +16,6 @@ class RTCManager { final ValueNotifier> remoteUsersNotifier = ValueNotifier>( [], ); - NetworkService get _networkService => NetworkService(); RtcEngine? get engine => _engine; bool get isInChannel => _isInChannel; int? get currentUid => _currentUid; diff --git a/lib/widget/live/live_room_anchor_showcase.dart b/lib/widget/live/live_room_anchor_showcase.dart index 7d9d697..eda01bf 100644 --- a/lib/widget/live/live_room_anchor_showcase.dart +++ b/lib/widget/live/live_room_anchor_showcase.dart @@ -128,7 +128,7 @@ class _LiveRoomAnchorShowcaseState extends State { Widget _buildAnchorVideo(bool joined, int? remoteUid) { final engine = _rtcManager.engine; - if (!joined || engine == null) { + if (_roomController.rtcChannelDetail.value?.anchorInfo == null || engine == null) { return _buildWaitingPlaceholder(); } print('joined: $joined'); @@ -138,26 +138,24 @@ class _LiveRoomAnchorShowcaseState extends State { child: SizedBox( width: 177.w, height: 175.w, - child: role == ClientRoleType.clientRoleBroadcaster + child: _roomController.currentRole == CurrentRole.broadcaster ? AgoraVideoView( controller: VideoViewController( rtcEngine: engine, canvas: const VideoCanvas(uid: 0), ), ) - : (_rtcManager.currentChannelId == null && _rtcManager.remoteUserIds.isEmpty - ? _buildWaitingPlaceholder() - : AgoraVideoView( + : AgoraVideoView( controller: VideoViewController.remote( rtcEngine: engine, canvas: VideoCanvas( - uid: _rtcManager.remoteUserIds.first, + uid: _roomController.rtcChannelDetail.value?.anchorInfo?.uid, ), connection: RtcConnection( channelId: _rtcManager.currentChannelId!, ), ), - )), + ), ), ); }