diff --git a/lib/components/home_appbar.dart b/lib/components/home_appbar.dart index aa19ea2..2237a2e 100644 --- a/lib/components/home_appbar.dart +++ b/lib/components/home_appbar.dart @@ -34,7 +34,7 @@ class _HomeAppbarState extends State { children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, children: [ ...widget.topNav.asMap().entries.map((entry){ return InkWell( diff --git a/lib/controller/discover/search_page_controller.dart b/lib/controller/discover/search_page_controller.dart new file mode 100644 index 0000000..b973046 --- /dev/null +++ b/lib/controller/discover/search_page_controller.dart @@ -0,0 +1,60 @@ + +import 'dart:async'; + +import 'package:dating_touchme_app/network/network_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +import '../../model/discover/matchmaker_live_data.dart'; + +class SearchPageController extends GetxController { + SearchPageController({NetworkService? networkService}) + : _networkService = networkService ?? Get.find(); + + final NetworkService _networkService; + + + final FocusNode blankFocusNode = FocusNode(); + final searchController = TextEditingController().obs; + final name = "".obs; + + + + final matchmakerList = [].obs; + + Timer? _debounce; + + void onTextChanged(String text) { + // 取消上一次的计时 + _debounce?.cancel(); + + _debounce = Timer(const Duration(milliseconds: 500), () { + name.value = text; + searchData(); + }); + } + + searchData() async { + try{ + final response = await _networkService.rtcApi.userPageLiveMatchmaker( + + searchKey: name.value + ); + if (response.data.isSuccess && response.data.data != null) { + final data = response.data.data?.records ?? []; + + matchmakerList.value = data.toList(); + + } else { + + // 响应失败,抛出异常 + throw Exception(response.data.message ?? '获取数据失败'); + } + } catch(e) { + print('红娘搜索失败: $e'); + SmartDialog.showToast('红娘搜索失败'); + rethrow; + } + } +} \ No newline at end of file diff --git a/lib/controller/home/home_controller.dart b/lib/controller/home/home_controller.dart index 2dd90b5..93e838d 100644 --- a/lib/controller/home/home_controller.dart +++ b/lib/controller/home/home_controller.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:location_plugin/location_plugin.dart'; import '../../network/home_api.dart'; import '../../model/home/marriage_data.dart'; @@ -35,6 +37,14 @@ class HomeController extends GetxController { final timelineTab = 0.obs; + final RxnInt cityCode = RxnInt(); + + final _locationPlugin = LocationPlugin(); + String _status = 'Idle'; + String _cityStatus = 'Idle'; + Map? _location; + CityInfo? _cityInfo; + @override void onInit() { super.onInit(); @@ -43,142 +53,72 @@ class HomeController extends GetxController { // 初始化时加载数据 loadInitialData(); - // Timer.periodic(const Duration(seconds: 10), (t) { - // SmartDialog.show( - // alignment: Alignment.center, - // maskColor: Colors.black.withOpacity(0.5), - // onDismiss: () { - // - // }, - // builder: (context) { - // // return LiveRoomGuestListDialog( - // // initialTab: isMaleSeat ? 1 : 0, // 0: 女嘉宾, 1: 男嘉宾 - // // ); - // return ClipRRect( - // borderRadius: BorderRadius.all(Radius.circular(16.w)), - // child: Material( - // child: Stack( - // - // children: [ - // Container( - // width: 311.w, - // height: 210.w, - // color: Colors.white, - // padding: EdgeInsets.only( - // top: 53.w, - // left: 23.w, - // right: 23.w, - // bottom: 20.w - // ), - // child: Column( - // children: [ - // Text( - // "主持人邀请您视频连麦", - // style: TextStyle( - // fontSize: 21.w, - // color: const Color.fromRGBO(117, 98, 249, 1), - // fontWeight: FontWeight.w500 - // ), - // ), - // SizedBox(height: 15.w,), - // Text( - // "有相亲卡的用户免费", - // style: TextStyle( - // fontSize: 12.w, - // color: const Color.fromRGBO(87, 87, 87, 1), - // fontWeight: FontWeight.w500 - // ), - // ), - // SizedBox(height: 28.w,), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Container( - // width: 128.w, - // height: 40.w, - // decoration: BoxDecoration( - // borderRadius: BorderRadius.all(Radius.circular(12.w)), - // color: const Color.fromRGBO(237, 237, 237, 1) - // ), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Image.asset( - // Assets.imagesHangUpIcon, - // width: 26.w, - // height: 26.w, - // ), - // SizedBox(width: 5.w,), - // Text( - // "拒绝", - // style: TextStyle( - // fontSize: 15.w - // ), - // ) - // ], - // ), - // ), - // Container( - // width: 128.w, - // height: 40.w, - // decoration: BoxDecoration( - // borderRadius: BorderRadius.all(Radius.circular(12.w)), - // color: const Color.fromRGBO(117, 98, 249, 1) - // ), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Image.asset( - // Assets.imagesAnswerIcon, - // width: 18.w, - // height: 13.w, - // ), - // SizedBox(width: 5.w,), - // Text( - // "拒绝", - // style: TextStyle( - // fontSize: 15.w, - // color: Colors.white - // ), - // ) - // ], - // ), - // ), - // ], - // ) - // ], - // ), - // ), - // Positioned( - // top: 0, - // left: 0, - // child: Container( - // padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 15.w), - // decoration: BoxDecoration( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(16.w), - // bottomRight: Radius.circular(16.w), - // ), - // color: const Color.fromRGBO(117, 98, 249, 1) - // ), - // child: Text( - // "20玫瑰", - // style: TextStyle( - // fontSize: 14.w, - // color: Colors.white, - // fontWeight: FontWeight.w500 - // ), - // ), - // ), - // ) - // ], - // ), - // ), - // ); - // }, - // ); - // // 你的代码写这里 - // }); + } + + + + Future _fetchLocation() async { + + _status = 'Fetching location...'; + + try { + final location = await _locationPlugin.getCurrentLocation(); + + + if (location == null) { + + _status = 'Location unavailable'; + _cityStatus = 'Skip city lookup'; + _cityInfo = null; + + return; + } + + _location = location; + _status = 'Success'; + _cityStatus = 'Fetching city info...'; + + await _fetchCityInfo(location['latitude']!, location['longitude']!); + } on PlatformException catch (e) { + + _status = 'Failed: ${e.code} ${e.message ?? ''}'.trim(); + _cityStatus = 'Skip city lookup'; + _cityInfo = null; + + } catch (e) { + + _status = 'Failed: $e'; + _cityStatus = 'Skip city lookup'; + _cityInfo = null; + + } + } + + Future _fetchCityInfo(double latitude, double longitude) async { + try { + final info = await _locationPlugin.getCityInfo(latitude: latitude, longitude: longitude); + + print(info); + + _cityInfo = info; + _cityStatus = info == null ? 'No city info' : 'Success'; + + + cityCode.value = ((_cityInfo?.code ?? 0) ~/ 100) * 100; + + } on CityInfoLookupException catch (e) { + + + _cityInfo = null; + _cityStatus = 'Failed: ${e.code}'; + + } catch (e) { + + + _cityInfo = null; + _cityStatus = 'Failed: $e'; + + } } /// 加载初始数据(同时加载两个标签页的数据) @@ -187,7 +127,6 @@ class HomeController extends GetxController { print(12121); await Future.wait([ loadRecommendInitialData(), - loadNearbyInitialData(), ]); } @@ -225,6 +164,11 @@ class HomeController extends GetxController { /// 加载同城列表初始数据 Future loadNearbyInitialData() async { + + if(cityCode.value == null || cityCode.value == 0){ + return; + + } if (nearbyIsLoading.value) return; try { @@ -236,6 +180,7 @@ class HomeController extends GetxController { final result = await _fetchMarriageData( pageNum: 1, type: 1, + code: cityCode.value ); // 重置并更新同城列表 @@ -311,11 +256,11 @@ class HomeController extends GetxController { // final nextPage = nearbyPage.value + 1; final nextPage = nearbyPage.value; print('同城列表加载更多 - 当前页: ${nearbyPage.value}, 下一页: $nextPage'); - // 获取同城数据 (type=1) final result = await _fetchMarriageData( pageNum: nextPage, type: 1, + code: cityCode.value ); // 更新页码 @@ -394,6 +339,7 @@ class HomeController extends GetxController { final result = await _fetchMarriageData( pageNum: 1, type: 1, + code: cityCode.value ); // 更新同城列表 @@ -413,11 +359,17 @@ class HomeController extends GetxController { } /// 设置当前标签页 - void setSelectedTabIndex(int index) { + void setSelectedTabIndex(int index) async { print('Setting selected tab index to: $index'); selectedTabIndex.value = index; // 确保UI能够更新 update(); + if(index == 2 && nearbyFeed.isEmpty){ + if(cityCode.value == null || cityCode.value == 0){ + await _fetchLocation(); + } + loadNearbyInitialData(); + } } /// 获取当前标签页的列表数据 @@ -430,6 +382,7 @@ class HomeController extends GetxController { Future> _fetchMarriageData({ required int pageNum, required int type, + int? code }) async { try { print('_fetchMarriageData - pageNum: $pageNum, pageSize: $pageSize, type: $type'); @@ -438,6 +391,7 @@ class HomeController extends GetxController { pageNum: pageNum, pageSize: pageSize, type: type, + cityCode: type == 1 ? code : null ); if (response.data.isSuccess) { diff --git a/lib/controller/message/conversation_controller.dart b/lib/controller/message/conversation_controller.dart index 40638f7..7f2fe00 100644 --- a/lib/controller/message/conversation_controller.dart +++ b/lib/controller/message/conversation_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:app_badge_plus/app_badge_plus.dart'; @@ -65,6 +66,33 @@ class ConversationController extends GetxController { // 防抖定时器,用于避免频繁刷新会话列表 Timer? _refreshDebounceTimer; + + + final FocusNode blankFocusNode = FocusNode(); + final searchController = TextEditingController().obs; + final name = "".obs; + + final showSearch = false.obs; + + + + void onTextChanged(String text) { + name.value = text; + } + + search(){ + if(name.value == ""){ + _checkAndLoadConversations(); + } else { + List list = _userInfoCache.values.where((e) => e.nickName!.contains(name.value)).toList(); + List result = conversations.where((a) { + return list.any((b) => b.userId == a.id); + }).toList(); + conversations.value = result; + } + + } + /// 缓存用户信息(公开方法,供 ChatController 调用) void cacheUserInfo(String userId, ExtendedUserInfo userInfo) { diff --git a/lib/model/discover/matchmaker_live_data.dart b/lib/model/discover/matchmaker_live_data.dart new file mode 100644 index 0000000..a76d163 --- /dev/null +++ b/lib/model/discover/matchmaker_live_data.dart @@ -0,0 +1,108 @@ +class MatchmakerLiveData { + List? records; + int? total; + int? size; + int? current; + int? pages; + + MatchmakerLiveData( + {this.records, this.total, this.size, this.current, this.pages}); + + MatchmakerLiveData.fromJson(Map json) { + if (json['records'] != null) { + records = []; + json['records'].forEach((v) { + records!.add(new Records.fromJson(v)); + }); + } + total = json['total']; + size = json['size']; + current = json['current']; + pages = json['pages']; + } + + Map toJson() { + final Map data = new Map(); + if (this.records != null) { + data['records'] = this.records!.map((v) => v.toJson()).toList(); + } + data['total'] = this.total; + data['size'] = this.size; + data['current'] = this.current; + data['pages'] = this.pages; + return data; + } +} + +class Records { + String? userId; + String? miId; + String? nickName; + String? profilePhoto; + int? genderCode; + String? birthYear; + String? birthDate; + int? age; + int? provinceCode; + String? provinceName; + int? cityCode; + String? cityName; + Null? isLiveChannelId; + bool? liveStreaming; + int? uid; + + Records( + {this.userId, + this.miId, + this.nickName, + this.profilePhoto, + this.genderCode, + this.birthYear, + this.birthDate, + this.age, + this.provinceCode, + this.provinceName, + this.cityCode, + this.cityName, + this.isLiveChannelId, + this.liveStreaming, + this.uid}); + + Records.fromJson(Map json) { + userId = json['userId']; + miId = json['miId']; + nickName = json['nickName']; + profilePhoto = json['profilePhoto']; + genderCode = json['genderCode']; + birthYear = json['birthYear']; + birthDate = json['birthDate']; + age = json['age']; + provinceCode = json['provinceCode']; + provinceName = json['provinceName']; + cityCode = json['cityCode']; + cityName = json['cityName']; + isLiveChannelId = json['isLiveChannelId']; + liveStreaming = json['liveStreaming']; + uid = json['uid']; + } + + Map toJson() { + final Map data = new Map(); + data['userId'] = this.userId; + data['miId'] = this.miId; + data['nickName'] = this.nickName; + data['profilePhoto'] = this.profilePhoto; + data['genderCode'] = this.genderCode; + data['birthYear'] = this.birthYear; + data['birthDate'] = this.birthDate; + data['age'] = this.age; + data['provinceCode'] = this.provinceCode; + data['provinceName'] = this.provinceName; + data['cityCode'] = this.cityCode; + data['cityName'] = this.cityName; + data['isLiveChannelId'] = this.isLiveChannelId; + data['liveStreaming'] = this.liveStreaming; + data['uid'] = this.uid; + return data; + } +} diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 8e6e088..90d9d20 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -229,4 +229,7 @@ class ApiUrls { static const String userPageDongwoMatchmakerMarriageInformation = 'dating-agency-service/user/page/dongwo/matchmaker-marriage-information'; + static const String userPageLiveMatchmaker = + 'dating-agency-chat-audio/user/page/live-matchmaker'; + } diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 203ff04..7f2cbec 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -1,5 +1,6 @@ import 'package:dating_touchme_app/model/discover/audience_list_data.dart'; import 'package:dating_touchme_app/model/discover/live_income_data.dart'; +import 'package:dating_touchme_app/model/discover/matchmaker_live_data.dart'; import 'package:dating_touchme_app/model/discover/rtc_channel_model.dart'; import 'package:dating_touchme_app/model/discover/task_template_data.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; @@ -187,4 +188,11 @@ abstract class RtcApi { @Query('id') required String id, } ); + /// 获取用户道具连麦卡片 + @GET(ApiUrls.userPageLiveMatchmaker) + Future>> userPageLiveMatchmaker( + { + @Query('searchKey') required String searchKey, + } + ); } diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index f3df6f9..cbcea6a 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -980,6 +980,41 @@ class _RtcApi implements RtcApi { return httpResponse; } + @override + Future>> + userPageLiveMatchmaker({required String searchKey}) async { + final _extra = {}; + final queryParameters = {r'searchKey': searchKey}; + final _headers = {}; + const Map? _data = null; + final _options = + _setStreamType>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-chat-audio/user/page/live-matchmaker', + 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) => MatchmakerLiveData.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/discover_page.dart b/lib/pages/discover/discover_page.dart index 4a00c2b..3c7b04b 100644 --- a/lib/pages/discover/discover_page.dart +++ b/lib/pages/discover/discover_page.dart @@ -1,8 +1,10 @@ import 'package:dating_touchme_app/components/home_appbar.dart'; import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/pages/discover/dating_page.dart'; import 'package:dating_touchme_app/pages/discover/party_page.dart'; +import 'package:dating_touchme_app/pages/discover/search_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; @@ -78,19 +80,32 @@ class _DiscoverPageState extends State topNav: topNav, changeNav: changeNav, activeIndex: active, - right: TDButton( - text: roomController.matchmakerFlag.value ? '开始直播' : '申请红娘', - width: 60.w, - height: 24.h, - padding: EdgeInsetsGeometry.symmetric(horizontal: 4.w), - size: TDButtonSize.small, - type: TDButtonType.fill, - shape: TDButtonShape.round, - theme: roomController.matchmakerFlag.value ? TDButtonTheme.danger : TDButtonTheme.primary, - textStyle: TextStyle(fontSize: 10.w), - onTap: () async { - roomController.registerMatch(roomController.matchmakerFlag.value ? 2 : 1); - }, + + right: Row( + children: [ + + Icon( + Icons.search, + size: 30, + ).onTap((){ + Get.to(() => SearchPage()); + }), + SizedBox(width: 10,), + TDButton( + text: roomController.matchmakerFlag.value ? '开始直播' : '申请红娘', + width: 60.w, + height: 24.h, + padding: EdgeInsetsGeometry.symmetric(horizontal: 4.w), + size: TDButtonSize.small, + type: TDButtonType.fill, + shape: TDButtonShape.round, + theme: roomController.matchmakerFlag.value ? TDButtonTheme.danger : TDButtonTheme.primary, + textStyle: TextStyle(fontSize: 10.w), + onTap: () async { + roomController.registerMatch(roomController.matchmakerFlag.value ? 2 : 1); + }, + ) + ], ), ); }), diff --git a/lib/pages/discover/search_page.dart b/lib/pages/discover/search_page.dart new file mode 100644 index 0000000..7f74ee7 --- /dev/null +++ b/lib/pages/discover/search_page.dart @@ -0,0 +1,289 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dating_touchme_app/components/page_appbar.dart'; +import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/controller/discover/search_page_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/model/discover/matchmaker_live_data.dart'; +import 'package:dating_touchme_app/model/home/marriage_data.dart'; +import 'package:dating_touchme_app/model/home/user_info_data.dart'; +import 'package:dating_touchme_app/network/user_api.dart'; +import 'package:dating_touchme_app/pages/home/user_information_page.dart'; +import 'package:dating_touchme_app/pages/message/chat_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +class SearchPage extends StatelessWidget { + const SearchPage({super.key}); + + @override + Widget build(BuildContext context) { + return GetX( + init: SearchPageController(), + builder: (controller){ + return Scaffold( + appBar: PageAppbar(title: "搜索"), + body: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 15.w), + margin: EdgeInsets.only(bottom: 10.w), + child: Container( + height: 30.w, + padding: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(30.w)), + color: const Color.fromRGBO(200, 200, 200, 1.0) + ), + child: Row( + children: [ + Icon( + Icons.search, + size: 20, + color: Colors.grey, + ), + Expanded( + child: TextField( + focusNode: controller.blankFocusNode, + onTapOutside: (e){ + controller.blankFocusNode.unfocus(); + }, + controller: controller.searchController.value, + textAlignVertical: TextAlignVertical.center, + style: TextStyle( + fontSize: ScreenUtil().setWidth(13), + height: 1 + ), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.only( + left: 0, + right: -8 + ), + hintText: "输入趣恋恋ID/昵称进行搜索", + + border: const OutlineInputBorder( + borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 + ), + // 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ), + onChanged: controller.onTextChanged, + ), + ), + ], + ), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Column( + children: [ + if(controller.matchmakerList.isEmpty) Container( + padding: EdgeInsets.only(top: 45.w), + child: Column( + children: [ + Text( + "仅支持搜索", + style: TextStyle( + color: const Color.fromRGBO(121, 121, 121, 1) + ), + ), + Text( + "趣恋恋主持人" + ) + ], + ), + ), + ...controller.matchmakerList.map((e){ + return SearchItem(item: e,); + }), + ], + ), + ), + ), + ) + ], + ), + ); + }, + ); + } +} + +class SearchItem extends StatefulWidget { + final Records item; + const SearchItem({super.key, required this.item}); + + @override + State createState() => _SearchItemState(); +} + +class _SearchItemState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 10.w), + child: Row( + children: [ + CachedNetworkImage( + imageUrl: '${widget.item.profilePhoto}?x-oss-process=image/format,webp/resize,w_320', + width: 60, + height: 60, + fit: BoxFit.cover, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(60), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + errorWidget: (context, url, error) => Image.asset( + Assets.imagesUserAvatar, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + ), + SizedBox(width: 15.w,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + widget.item.nickName ?? '用户', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color.fromRGBO(51, 51, 51, 1), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox(width: 10.w,), + if(widget.item.liveStreaming ?? false)Container( + width: 33.w, + height: 13.w, + margin: EdgeInsets.only(left: 6.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(13.w)), + color: const Color.fromRGBO(234, 255, 219, 1) + ), + child: Center( + child: Text( + "在线", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(38, 199, 124, 1) + ), + ), + ), + ) + ], + ), + + const SizedBox(height: 6), + SizedBox( + height: 16, + child: Text( + '${widget.item.age ?? 0}岁 ${(widget.item.cityName ?? '') != "" ? "· " : ""}${widget.item.cityName ?? ''}', + style: TextStyle( + fontSize: 12, + color: Color.fromRGBO(51, 51, 51, 1), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + SizedBox(width: 15.w,), + Container( + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(30.w)), + color: const Color.fromRGBO(117, 98, 249, 1) + ), + child: Center( + child: Text( + "发消息", + style: TextStyle( + fontSize: 13.w, + color: Colors.white + ), + ), + ), + ).onTap(() async { + if (widget.item.userId?.isEmpty ?? false) { + SmartDialog.showToast('用户ID不存在,无法发送消息'); + return; + } + + try { + final _userApi = Get.find(); + final response = await _userApi.getDongwoMarriageInformationDetail(miId: widget.item.miId ?? ""); + if (response.data.isSuccess && response.data.data != null) { + + + // 使用工厂方法将 UserInfoData 转换为 MarriageData + final marriageData = MarriageData.fromUserInfoData(response.data.data ?? UserInfoData()); + + + await Get.to(() => ChatPage( + userId: widget.item.userId ?? "", + userData: marriageData, + )); + + + } else { + // 响应失败,设置错误信息 + final errorMsg = response.data.message ?? '获取数据失败'; + + SmartDialog.showToast(errorMsg); + } + } catch(e, stackTrace){ + print('获取用户信息失败: $e'); + print('堆栈跟踪: $stackTrace'); + + SmartDialog.showToast('获取用户信息失败,请稍后重试'); + // 不再 rethrow,避免导致闪退 + } + }) + ], + ), + ).onTap(() async { + if (widget.item.liveStreaming ?? false) { + late final RoomController roomController; + + if (Get.isRegistered()) { + roomController = Get.find(); + } else { + roomController = Get.put(RoomController()); + } + await roomController.joinChannel(widget.item.isLiveChannelId ?? ""); + } else { + Get.to(() => UserInformationPage(miId: widget.item.miId ?? "")); + } + + }); + } +} + diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 39046cd..17db8f6 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -61,10 +61,11 @@ class _HomePageState extends State return IndexedStack( index: controller.selectedTabIndex.value, children: const [ - // 推荐列表 - RecommendTab(), // 同城列表 AllTimeline(), + // 推荐列表 + RecommendTab(), + NearbyTab() ], ); }), @@ -86,9 +87,11 @@ class _HomePageState extends State mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - _buildTabButton(title: '推荐', index: 0, controller: controller), + _buildTabButton(title: '广场', index: 0, controller: controller), + const SizedBox(width: 28), + _buildTabButton(title: '推荐', index: 1, controller: controller), const SizedBox(width: 28), - _buildTabButton(title: '广场', index: 1, controller: controller), + _buildTabButton(title: '同城', index: 2, controller: controller), ], ), bottom: const PreferredSize( diff --git a/lib/pages/home/nearby_tab.dart b/lib/pages/home/nearby_tab.dart index d82b14b..dae13fa 100644 --- a/lib/pages/home/nearby_tab.dart +++ b/lib/pages/home/nearby_tab.dart @@ -96,6 +96,19 @@ class _NearbyTabState extends State with AutomaticKeepAliveClientMixi // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 padding: EdgeInsets.only(left: 12, right: 12), itemBuilder: (context, index) { + // 空数据状态 + if (controller.nearbyFeed.isEmpty && (controller.cityCode.value == null || controller.cityCode.value == 0) && index == 0) { + // 使用足够的高度确保可以滚动 + return SizedBox( + height: MediaQuery.of(context).size.height - totalBottomPadding, + child: const Center( + child: Text( + "暂未获取到定位,请稍后重试", + style: TextStyle(fontSize: 14, color: Color(0xFF999999)), + ), + ), + ); + } // 空数据状态 if (controller.nearbyFeed.isEmpty && index == 0) { // 使用足够的高度确保可以滚动 diff --git a/lib/pages/home/send_timeline.dart b/lib/pages/home/send_timeline.dart index ac26d13..7af13c1 100644 --- a/lib/pages/home/send_timeline.dart +++ b/lib/pages/home/send_timeline.dart @@ -10,8 +10,10 @@ import 'package:dating_touchme_app/network/home_api.dart'; import 'package:dating_touchme_app/oss/oss_manager.dart'; import 'package:dating_touchme_app/widget/emoji_panel.dart'; import 'package:dating_touchme_app/widget/emoji_panel.dart'; +import 'package:extended_text_field/extended_text_field.dart'; import 'package:flustars/flustars.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -19,6 +21,8 @@ import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; +import '../../widget/message/chat_input_bar.dart'; + class SendTimeline extends StatefulWidget { const SendTimeline({super.key}); @@ -499,58 +503,35 @@ class _SendTimelineState extends State { Expanded( child: Stack( children: [ - TextField( + + ExtendedTextField( controller: messageController, focusNode: focusNode, - minLines: 1, - maxLines: null, // 关键 - style: TextStyle( - fontSize: 14.sp, - color: messageController.text.contains('[emoji:') - ? Colors.transparent - : Colors.black, - ), decoration: InputDecoration( - contentPadding: EdgeInsets.symmetric( - vertical: 0, - horizontal: 0 - ), - hintText: "勇敢表达吧,你的有趣,总有人懂。", + border: InputBorder.none, + hintText: "请输入聊天内容~", hintStyle: TextStyle( fontSize: 14.sp, color: Colors.grey, ), - border: const OutlineInputBorder( - borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 - ), - // 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.all(Radius.circular(8.0)), - ), - enabledBorder: const OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.all(Radius.circular(8.0)), + contentPadding: EdgeInsets.only( + bottom: 10 ), ), - onChanged: (value){ - setState(() { - - }); - }, - ), - if (messageController.text.contains('[emoji:')) - Positioned.fill( - child: IgnorePointer( - child: SingleChildScrollView( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: buildInputContentWidgets(), - ), - ), - ), + inputFormatters: [ + // 可以添加其他格式化器,但不要添加过滤Unicode的规则 + FilteringTextInputFormatter.deny(RegExp(r'[\u200B]')), // 仅示例:过滤零宽空格 + ], + specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, // 文字始终显示为黑色,specialTextSpanBuilder 会处理表情 ), + onChanged: (value) { + setState(() {}); // 刷新以更新表情显示 + }, + ) ], ), ), diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index f088cc2..9dcbd65 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -1,4 +1,6 @@ +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/pages/home/send_timeline.dart'; import 'package:dating_touchme_app/pages/main/main_controller.dart'; import 'package:dating_touchme_app/pages/message/message_page.dart'; import 'package:dating_touchme_app/pages/mine/mine_page.dart'; @@ -89,7 +91,23 @@ class _MainPageState extends State { minePage, ], ), - bottomNavigationBar: _buildBottomNavigationBar(), + bottomNavigationBar: Stack( + clipBehavior: Clip.none, + children: [ + _buildBottomNavigationBar(), + Positioned( + top: -20, + left: 150, + child: Image.asset( + Assets.imagesPublish, + width: 60, + height: 60, + ).onTap((){ + Get.to(() => SendTimeline()); + }), + ) + ], + ), ), ); } @@ -115,6 +133,14 @@ class _MainPageState extends State { navigationTabs: [ tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0, 0), tabItem('找对象', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1, 0), + TDBottomTabBarTabConfig( + tabText: "", + selectedIcon: Image.asset(Assets.imagesHomePre, width: 30, height: 30, fit: BoxFit.cover, color: Colors.transparent,), + unselectedIcon: Image.asset(Assets.imagesHomeNol, width: 30, height: 30, fit: BoxFit.cover, color: Colors.transparent,), + onTap: (){ + Get.to(() => SendTimeline()); + } + ), tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 2, unreadCount), tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 3, 0), ] diff --git a/lib/pages/message/message_page.dart b/lib/pages/message/message_page.dart index 979a1d9..65df047 100644 --- a/lib/pages/message/message_page.dart +++ b/lib/pages/message/message_page.dart @@ -1,5 +1,7 @@ +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:flutter/material.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'conversation_tab.dart'; import 'friend_tab.dart'; @@ -16,12 +18,14 @@ class _MessagePageState extends State with AutomaticKeepAliveClient late TabController _tabController; final GlobalKey _filterButtonKey = GlobalKey(); + late ConversationController controller; + @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); // 注册ConversationController - Get.put(ConversationController()); + controller = Get.put(ConversationController()); } @override @@ -56,7 +60,6 @@ class _MessagePageState extends State with AutomaticKeepAliveClient backgroundColor: Colors.transparent, elevation: 0, automaticallyImplyLeading: false, - centerTitle: true, title: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -67,6 +70,99 @@ class _MessagePageState extends State with AutomaticKeepAliveClient ], ), actions: [ + Container( + width: 243, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if(controller.showSearch.value) Expanded( + child: Container( + height: 30.w, + padding: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(30.w)), + color: const Color.fromRGBO(200, 200, 200, 1.0) + ), + child: Row( + children: [ + Icon( + Icons.search, + size: 20, + color: Colors.grey, + ), + Expanded( + child: TextField( + focusNode: controller.blankFocusNode, + onTapOutside: (e){ + controller.blankFocusNode.unfocus(); + }, + controller: controller.searchController.value, + textAlignVertical: TextAlignVertical.center, + style: TextStyle( + fontSize: ScreenUtil().setWidth(13), + height: 1 + ), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.only( + left: 0, + right: -8 + ), + hintText: "搜索昵称", + + border: const OutlineInputBorder( + borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 + ), + // 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ), + onChanged: controller.onTextChanged, + ), + ), + SizedBox(width: 10,), + Row( + children: [ + Container( + height: 15, + width: 1, + color: const Color.fromRGBO(51, 51, 51, 1), + ), + SizedBox(width: 10,), + Text( + "搜索", + style: TextStyle( + fontSize: 12 + ), + ) + ], + ).onTap((){ + controller.search(); + }) + ], + ), + ), + ), + SizedBox(width: 10,), + Icon( + Icons.search, + size: 30, + ).onTap((){ + controller.showSearch.value = !controller.showSearch.value; + setState(() { + + }); + }) + ], + ), + ), + SizedBox(width: 10,), // 过滤器按钮 GestureDetector( key: _filterButtonKey, diff --git a/lib/rtc/rtc_manager.dart b/lib/rtc/rtc_manager.dart index 4b92439..4d8f985 100644 --- a/lib/rtc/rtc_manager.dart +++ b/lib/rtc/rtc_manager.dart @@ -587,20 +587,20 @@ class RTCManager { ), ); print('正在加入频道:$channelId,UID:$uid,类型:$rtcType'); - - await _engine?.enableContentInspect( - enabled: true, - config: ContentInspectConfig( - modules: [ - ContentInspectModule( - type: ContentInspectType.contentInspectImageModeration, - interval: 10 - ) - ], - moduleCount: 1 - ) - ); if (role == ClientRoleType.clientRoleBroadcaster) { + + await _engine?.enableContentInspect( + enabled: true, + config: ContentInspectConfig( + modules: [ + ContentInspectModule( + type: ContentInspectType.contentInspectImageModeration, + interval: 10 + ) + ], + moduleCount: 1 + ) + ); await _engine?.enableFaceDetection(true); } }