From e0e4bbc53f967d0b6f90ba350fc8c751ea07ee5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=90=E8=B4=A4?= Date: Mon, 30 Mar 2026 18:10:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/nearby_user.png | Bin 0 -> 23159 bytes .../home/friend_footprint_controller.dart | 6 + lib/controller/home/home_controller.dart | 115 ++++++- lib/generated/assets.dart | 1 + lib/network/api_urls.dart | 4 + lib/network/home_api.dart | 10 + lib/network/home_api.g.dart | 77 +++++ lib/pages/home/friend_footprint.dart | 40 +++ lib/pages/home/recommend_tab.dart | 301 +++++++++++------- lib/pages/home/report_page.dart | 66 +--- lib/pages/message/conversation_tab.dart | 33 +- lib/pages/message/nearby_user.dart | 232 ++++++++++++++ 12 files changed, 712 insertions(+), 173 deletions(-) create mode 100644 assets/images/nearby_user.png create mode 100644 lib/controller/home/friend_footprint_controller.dart create mode 100644 lib/pages/home/friend_footprint.dart create mode 100644 lib/pages/message/nearby_user.dart diff --git a/assets/images/nearby_user.png b/assets/images/nearby_user.png new file mode 100644 index 0000000000000000000000000000000000000000..3ca9fcc3ed8c02c340f347818bedebf5c44f4c09 GIT binary patch literal 23159 zcmV)(K#RYLP)005u}1^@s6i_d2*00001b5ch_0Itp) z=>Px#1am@3R0s$N2z&@+hyVZ}07*naRCt{1y?Kyj_jTX*IltfU-uGVj^eh<60K`Us zn+TASNQsmskrZh=mddgy#bS|moN{8xmL*lGwAiwfc;hf}m8~S+N={s+je`LvF|oxgj^?%OLV{ zpTJ=jGt1ZKLmv`82;6_)>h(JH@Z#UUT>jp7pXL9#AKw2a<3r{C4}Q=PLBPJeXZ^~O z8vr~&?93U*_rC2IXD{50O-?b6cVYT9Y@B!_-Rw?GPoZ`TYD0RErX-LeU3(eqBGbv! ztRH`j%g;Z?VEqxWvm85qk*A+LFF*Rz&CC1|4~^UaAVt&N|DlREeB&w3ZN8J*-h~^! z1vj`A;W$35QI7&3gn=OCy4jLK(*!AkMkBV$YzDJ2#91Q6hS_9Hid%HEM`)YhpxJ$Z z@y_RYZMVY*?$_U|n(_B60PV~f$9)8Dy6X;(oqjX3<~tC1FKfq6Gg$M4$}t!T&ck42 zh!|oC097SL2SDsV6);F0m}MZQ0?4-U#7>w^U^a!x4s^4`bT`sYKS7EgLF6~-rjK&^ z-P?TN1HY%h_aLC%t7;h3m;!Eynx~x!({a|2lO1EWw@EX51mWkHZvAt9 z?eaxFa=(gv^#k$u6aek)C)YXq{5RnSKa8utha<;5!z03Q1fc?vh{Z@Zoe-xTdzX#w z!Y1R1Fr7G>nKGC(%%&bwkD1QjJQ;`zxTR}q4sj5NHH3hxY6gRZTN{F}h^)~yE(3BG zrk9Oo_8eR1|994o{9A7Mw$IB4K9F99OZ63#uX+F)2p{>0A+P_gx8m!+!sy8FW8>Hc z!y}%mHk?z?j_LM<$>xM+Ymf1zM7I}drinC;bX_2+1BHYjrhs5-*$)IX1`tIxEFIh- zA|PNUC>0U}#34?QBLP1gLKvdvP_1aD!epmoHu(aoKSyl-Hn*HRCm*=~s}hJ`wE#3T z;lm#ey#3DCvv&Lk@WUVC=*gPVI)vH~65Xt&y}ZM0bC=!o9Xro7#AzZn2{SMiRIR)r z#N+|c*kLlDYn7^Yw2k3hD32u+M+gDK!8~9I2q4Zd0!X>v1RV{)d8##s)oaAIqG(MqMQ9K#D1QOr?%wG8NK4ezJlee5`bnVeCik0kiieKe*A~oIR0wZk2%DH6Q!HY zm|WOo>-os^>21<<%5*oNY8iOeVp@^3qiZ}0h-DylT?Sf8t{iUl8ma;2ND*+u#Z$~> z%oU9&B8b%Z3RDFVQ1O^}Y_N{hfn*hJtaAk2{%2DBYy8T&PjLTNPt^QX2SEGCM?9~+ z=}nxzRKL zHG@e7vl=rYMTdw()ez_A&k-sA?`oVtGLLsax!?J^JkifzqZTl8*kC{k1I!O-8fcr# zSofD$KlKs$zBAijzN`3^ldl>8ma2v0>EHQI!jb=#wd40NT=zJEIGr%t+F|#ZO(th| z*}V|4l<3+-N`@6P8)u1_1d5ewx`3&nwg8l>5u1dXBUy!MM72U32o5#FiC_pZRSfEU zjoZ%SLxp#VWIgDhu6rQ*1sDM}c|`-TXudmHU;8Jl-STegkq1+{@g93$xWMJd z&M~{VOWX93tTNqmn6{;S0kdhqtVPXe8$XYHH9_q1b5im7XN{vC1llG775V!4^4
  • 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∓t4zwlDO z`#1P+W@$6^fOO5RSHjJk29HLPZN zvVn+_QkCnzRERooEruhdA;C{cU5z04s?CihCM9smWz5vcy!td4T1lh_n(?j7W_O|S z0UrAgKg;+1vzNTg@a5Hj_uub$*V}%On>T)pwWC#b^5cwdGN#!a(``@a8qj9O)YfTc zhRl>0VYXXgNf}Q@v`yybwX?joq3bG==GCu?kQ$G(3Y7)vKni$?c9y&n$o_KpPbhUf-e z^EzUC5$paYf9jt$@_qm8x>7I8^0EQYKJpRIw|@P5Sl{?-RHG3hMx0I<|NrfsYpi71 zb=QAu?{li^cHe%?^gKKs$FUt8BPNb)Ok!eN!34!35g;U(2ndoD5g8OYpdf@1uZ)Gn z2Z%sHkPiW&7=vWb2wM=#CgX`Sc8r4)#$!)BW6!f^#y$P+zJ2@NsydIo_u|9a=hUg{ zdwaU4Z_mW|F6rppsycP*RGt4_dp-Z_844vO(f|E#Sz*DO*L-f@Z++#&z3+~WDS_0)h=bmkg#P*`}Ji|12DvZ z{@!OrZhNzE>@H=xDO4S249?EgomT~s9EA3eL~t?l$Ye>HKTu6ON{UxBH94Oa*&0oV zq^x0$w4B7El0`kDmJ&?bV8oRL;&O`4xA^foj%`#+Mw3WN6Gm%mXpFQqOy_T)s@Eyp zuW|d)|8t}BgkQ5b;C=t$U){-p!@tJ(z?%^f>Pwfn^z13Nj-R95xJ

    Ah9I`1r5}5 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[].obs; // 同城列表数据 final nearbyFeed = [].obs; - + final nearbyDisFeed = [].obs; + // 推荐列表的加载状态和分页信息 final recommendIsLoading = false.obs; final recommendPage = 1.obs; @@ -24,6 +25,7 @@ class HomeController extends GetxController { // 同城列表的加载状态和分页信息 final nearbyIsLoading = false.obs; final nearbyPage = 1.obs; + final nearbyDisPage = 1.obs; final nearbyHasMore = true.obs; // 当前标签页索引 @@ -38,6 +40,7 @@ class HomeController extends GetxController { final timelineTab = 0.obs; final RxnInt cityCode = RxnInt(); + final RxnInt districtCode = RxnInt(); final _locationPlugin = LocationPlugin(); String _status = 'Idle'; @@ -105,6 +108,10 @@ class HomeController extends GetxController { cityCode.value = ((_cityInfo?.code ?? 0) ~/ 100) * 100; + districtCode.value = _cityInfo?.code ?? 0; + + print(_cityInfo!.code); + print("_cityInfo.code"); } on CityInfoLookupException catch (e) { @@ -199,6 +206,42 @@ class HomeController extends GetxController { } } + /// 加载同城列表初始数据 + Future loadNearbyDisInitialData() async { + if(cityCode.value == null || cityCode.value == 0){ + await _fetchLocation(); + + } + if (nearbyIsLoading.value) return; + + try { + nearbyIsLoading.value = true; + nearbyPage.value = 1; + nearbyHasMore.value = true; + + // 获取同城数据 (type=1) + final result = await _fetchMarriageData( + pageNum: 1, + type: 2, + code: districtCode.value + ); + + // 重置并更新同城列表 + nearbyDisFeed.clear(); + nearbyDisFeed.addAll(result['records']); + + // 根据返回的分页信息判断是否还有更多数据 + final int currentPage = result['current'] ?? 1; + // final int totalPages = result['pages'] ?? 1; + final int totalPages = 100; + nearbyHasMore.value = currentPage < totalPages; + } catch (e) { + _handleError('获取同城列表异常', e, '同城列表加载失败,请稍后重试'); + } finally { + nearbyIsLoading.value = false; + } + } + /// 加载更多数据 Future loadMoreData([int? tabIndex]) async { final targetTab = tabIndex ?? selectedTabIndex.value; @@ -282,6 +325,41 @@ class HomeController extends GetxController { } } + /// 加载同城列表更多数据 + Future loadNearbyDisMoreData() async { + if (nearbyIsLoading.value || !nearbyHasMore.value) return; + + try { + nearbyIsLoading.value = true; + // final nextPage = nearbyPage.value + 1; + final nextPage = nearbyPage.value; + print('同城列表加载更多 - 当前页: ${nearbyPage.value}, 下一页: $nextPage'); + // 获取同城数据 (type=1) + final result = await _fetchMarriageData( + pageNum: nextPage, + type: 2, + code: districtCode.value + ); + + // 更新页码 + nearbyDisPage.value = nextPage; + + // 更新同城列表 + nearbyDisFeed.addAll(result['records']); + + // 根据返回的分页信息判断是否还有更多数据 + final int currentPage = result['current'] as int; + // final int totalPages = result['pages'] as int; + final int totalPages = 100; + nearbyHasMore.value = currentPage < totalPages; + print('同城列表加载更多完成 - 当前页: $currentPage, 总页数: $totalPages, 还有更多: ${nearbyHasMore.value}'); + } catch (e) { + _handleError('加载同城更多异常', e, '加载更多失败'); + } finally { + nearbyIsLoading.value = false; + } + } + /// 刷新数据 Future refreshData([int? tabIndex]) async { final targetTab = tabIndex ?? selectedTabIndex.value; @@ -358,6 +436,38 @@ class HomeController extends GetxController { } } + /// 刷新同城列表数据 + Future refreshNearbyDisData() async { + if (nearbyIsLoading.value) return; + + try { + nearbyIsLoading.value = true; + nearbyPage.value = 1; + nearbyHasMore.value = true; + + // 获取同城数据 (type=1) + final result = await _fetchMarriageData( + pageNum: 1, + type: 2, + code: districtCode.value + ); + + // 更新同城列表 + nearbyDisFeed.clear(); + nearbyDisFeed.addAll(result['records']); + + // 根据返回的分页信息判断是否还有更多数据 + final int currentPage = result['current'] ?? 1; + // final int totalPages = result['pages'] ?? 1; + final int totalPages = 100; + nearbyHasMore.value = currentPage < totalPages; + } catch (e) { + _handleError('刷新同城数据异常', e, '刷新失败,请稍后重试'); + } finally { + nearbyIsLoading.value = false; + } + } + /// 设置当前标签页 void setSelectedTabIndex(int index) async { print('Setting selected tab index to: $index'); @@ -391,7 +501,8 @@ class HomeController extends GetxController { pageNum: pageNum, pageSize: pageSize, type: type, - cityCode: type == 1 ? code : null + cityCode: type == 1 ? code : null, + districtCode: type == 2 ? code : null, ); if (response.data.isSuccess) { diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 625b9d0..030d8da 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -173,6 +173,7 @@ class Assets { static const String imagesMore = 'assets/images/more.png'; static const String imagesMoreIcon = 'assets/images/more_icon.png'; static const String imagesMyWalletBg = 'assets/images/my_wallet_bg.png'; + static const String imagesNearbyUser = 'assets/images/nearby_user.png'; static const String imagesNoMoreTimeline = 'assets/images/no_more_timeline.png'; static const String imagesOnlineIcon = 'assets/images/online_icon.png'; static const String imagesOnlineMsgIcon = 'assets/images/online_msg_icon.png'; diff --git a/lib/network/api_urls.dart b/lib/network/api_urls.dart index 90d9d20..58931f7 100644 --- a/lib/network/api_urls.dart +++ b/lib/network/api_urls.dart @@ -106,6 +106,10 @@ class ApiUrls { 'dating-agency-service/user/del/photos'; static const String userPageLatestDatingRecord = 'dating-agency-chat-audio/user/page/latest/dating-record'; + static const String userGetFriendFootprintInfo = + 'dating-agency-chat-audio/user/get/friend-footprint-info'; + static const String userPageFriendFootprint = + '/dating-agency-chat-audio/user/page/friend-footprint'; //首页相关接口 static const String getMarriageList = diff --git a/lib/network/home_api.dart b/lib/network/home_api.dart index 1878158..18ee3b0 100644 --- a/lib/network/home_api.dart +++ b/lib/network/home_api.dart @@ -24,6 +24,7 @@ abstract class HomeApi { @Query('pageSize') required int pageSize, @Query('type') required int type, @Query('cityCode') int? cityCode, + @Query('districtCode') int? districtCode, }); @GET(ApiUrls.listMatchmakerTask) @@ -89,4 +90,13 @@ abstract class HomeApi { ); + @GET(ApiUrls.userGetFriendFootprintInfo) + Future>>> userGetFriendFootprintInfo(); + + @GET(ApiUrls.userPageFriendFootprint) + Future>>> userPageFriendFootprint({ + @Query('pageNum') required int pageNum, + @Query('pageSize') required int pageSize, + }); + } \ No newline at end of file diff --git a/lib/network/home_api.g.dart b/lib/network/home_api.g.dart index c66ab30..f7250ae 100644 --- a/lib/network/home_api.g.dart +++ b/lib/network/home_api.g.dart @@ -26,6 +26,7 @@ class _HomeApi implements HomeApi { required int pageSize, required int type, int? cityCode, + int? districtCode, }) async { final _extra = {}; final queryParameters = { @@ -33,6 +34,7 @@ class _HomeApi implements HomeApi { r'pageSize': pageSize, r'type': type, r'cityCode': cityCode, + r'districtCode': districtCode, }; queryParameters.removeWhere((k, v) => v == null); final _headers = {}; @@ -454,6 +456,81 @@ class _HomeApi implements HomeApi { return httpResponse; } + @override + Future>>> + userGetFriendFootprintInfo() async { + final _extra = {}; + final queryParameters = {}; + 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/friend-footprint-info', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late BaseResponse> _value; + try { + _value = BaseResponse>.fromJson( + _result.data!, + (json) => json is List + ? json.map((i) => i as String).toList() + : List.empty(), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + @override + Future>>> + userPageFriendFootprint({required int pageNum, required int pageSize}) async { + final _extra = {}; + final queryParameters = { + r'pageNum': pageNum, + r'pageSize': pageSize, + }; + 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/friend-footprint', + 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) => 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/pages/home/friend_footprint.dart b/lib/pages/home/friend_footprint.dart new file mode 100644 index 0000000..3412947 --- /dev/null +++ b/lib/pages/home/friend_footprint.dart @@ -0,0 +1,40 @@ +import 'package:dating_touchme_app/components/page_appbar.dart'; +import 'package:dating_touchme_app/controller/home/friend_footprint_controller.dart'; +import 'package:easy_refresh/easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class FriendFootprint extends StatelessWidget { + const FriendFootprint({super.key}); + + @override + Widget build(BuildContext context) { + return GetX( + init: FriendFootprintController(), + builder: (controller){ + return Scaffold( + appBar: PageAppbar(title: "好友脚印"), + body: Container( + padding: EdgeInsets.all(12.w), + child: EasyRefresh( + child: ListView.separated( + itemBuilder: (context, index){ + return Container(); + }, + separatorBuilder: (context, index) { + // 空状态或加载状态时不显示分隔符 + if (true) { + return const SizedBox.shrink(); + } + return const SizedBox(height: 12); + }, + itemCount: 10, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/home/recommend_tab.dart b/lib/pages/home/recommend_tab.dart index 3f617a1..d6af006 100644 --- a/lib/pages/home/recommend_tab.dart +++ b/lib/pages/home/recommend_tab.dart @@ -1,5 +1,7 @@ +import 'package:dating_touchme_app/generated/assets.dart'; import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:dating_touchme_app/controller/home/home_controller.dart'; import 'package:dating_touchme_app/pages/home/content_card.dart'; @@ -17,10 +19,17 @@ class _RecommendTabState extends State final HomeController controller = Get.find(); late final EasyRefreshController _refreshController; + final ScrollController _scrollController = ScrollController(); + + bool _shrink = false; + @override void initState() { super.initState(); _refreshController = EasyRefreshController(controlFinishRefresh: true, controlFinishLoad: true); + + + _scrollController.addListener(_onScroll); } @override @@ -29,6 +38,19 @@ class _RecommendTabState extends State super.dispose(); } + + void _onScroll() { + final offset = _scrollController.offset; + + final shouldShrink = offset > 200; + + if (shouldShrink != _shrink) { + setState(() { + _shrink = shouldShrink; + }); + } + } + @override Widget build(BuildContext context) { super.build(context); @@ -36,118 +58,177 @@ class _RecommendTabState extends State final bottomPadding = MediaQuery.of(context).padding.bottom; 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( - dragText: '下拉刷新', - armedText: '释放刷新', - readyText: '刷新中...', - processingText: '刷新中...', - processedText: '刷新完成', - failedText: '刷新失败', - noMoreText: '没有更多数据', - showMessage: false - ), - footer: ClassicFooter( - dragText: '上拉加载', - armedText: '释放加载', - readyText: '加载中...', - processingText: '加载中...', - processedText: '加载完成', - failedText: '加载失败', - noMoreText: '没有更多数据', - showMessage: false - ), - // 下拉刷新 - onRefresh: () async { - print('推荐列表下拉刷新被触发'); - try { - await controller.refreshRecommendData(); - print( '推荐列表刷新完成, hasMore: $controller.recommendHasMore.value'); - _refreshController.finishRefresh(); - _refreshController.resetFooter(); - } catch (e) { - print('推荐列表刷新失败: $e'); - _refreshController.finishRefresh(IndicatorResult.fail); + return Stack( + children: [ + Obx(() { + if (controller.recommendIsLoading.value && controller.recommendFeed.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载数据中...'), + ], + ), + ); } - }, - // 上拉加载更多 - onLoad: () async { - print('推荐列表上拉加载被触发, hasMore: $controller.recommendHasMore.value'); - try { - await controller.loadRecommendMoreData(); - // 完成加载,根据是否有更多数据决定 - if (controller.recommendHasMore.value) { - _refreshController.finishLoad(IndicatorResult.success); - print('推荐列表加载更多成功'); - } else { - _refreshController.finishLoad(IndicatorResult.noMore); - print('推荐列表没有更多数据了'); - } - } catch (e) { - print('推荐列表加载更多失败: $e'); - _refreshController.finishLoad(IndicatorResult.fail); - } - }, - child: ListView.separated( - // 关键:始终允许滚动,即使内容不足 - // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 - padding: EdgeInsets.only(left: 12, right: 12), - itemBuilder: (context, index) { - // 空数据状态 - if (controller.recommendFeed.isEmpty && index == 0) { - // 使用足够的高度确保可以滚动 - if (controller.recommendIsLoading.value) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text('加载数据中...'), - ], - ), - ); - } else { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('暂无数据'), - ], - ), - ); + return EasyRefresh( + controller: _refreshController, + header: const ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '刷新中...', + processingText: '刷新中...', + processedText: '刷新完成', + failedText: '刷新失败', + noMoreText: '没有更多数据', + showMessage: false + ), + footer: ClassicFooter( + dragText: '上拉加载', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败', + noMoreText: '没有更多数据', + showMessage: false + ), + // 下拉刷新 + onRefresh: () async { + print('推荐列表下拉刷新被触发'); + try { + await controller.refreshRecommendData(); + print( '推荐列表刷新完成, hasMore: $controller.recommendHasMore.value'); + _refreshController.finishRefresh(); + _refreshController.resetFooter(); + } catch (e) { + print('推荐列表刷新失败: $e'); + _refreshController.finishRefresh(IndicatorResult.fail); + } + }, + // 上拉加载更多 + onLoad: () async { + print('推荐列表上拉加载被触发, hasMore: $controller.recommendHasMore.value'); + try { + await controller.loadRecommendMoreData(); + // 完成加载,根据是否有更多数据决定 + if (controller.recommendHasMore.value) { + _refreshController.finishLoad(IndicatorResult.success); + print('推荐列表加载更多成功'); + } else { + _refreshController.finishLoad(IndicatorResult.noMore); + print('推荐列表没有更多数据了'); + } + } catch (e) { + print('推荐列表加载更多失败: $e'); + _refreshController.finishLoad(IndicatorResult.fail); } - } - // 数据项 - final item = controller.recommendFeed[index]; - return ContentCard(item: item); - }, - separatorBuilder: (context, index) { - // 空状态或加载状态时不显示分隔符 - if (controller.recommendFeed.isEmpty) { - return const SizedBox.shrink(); - } - return const SizedBox(height: 12); - }, - // 至少显示一个 item(用于显示加载或空状态) - itemCount: controller.recommendFeed.isEmpty ? 1 : controller.recommendFeed.length, - ) - ); - }); + }, + child: ListView.separated( + controller: _scrollController, + // 关键:始终允许滚动,即使内容不足 + // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 + padding: EdgeInsets.only(left: 12, right: 12), + itemBuilder: (context, index) { + // 空数据状态 + if (controller.recommendFeed.isEmpty && index == 0) { + // 使用足够的高度确保可以滚动 + if (controller.recommendIsLoading.value) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载数据中...'), + ], + ), + ); + } else { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('暂无数据'), + ], + ), + ); + } + } + // 数据项 + final item = controller.recommendFeed[index]; + return ContentCard(item: item); + }, + separatorBuilder: (context, index) { + // 空状态或加载状态时不显示分隔符 + if (controller.recommendFeed.isEmpty) { + return const SizedBox.shrink(); + } + return const SizedBox(height: 12); + }, + // 至少显示一个 item(用于显示加载或空状态) + itemCount: controller.recommendFeed.isEmpty ? 1 : controller.recommendFeed.length, + ) + ); + }), + Positioned( + right: 0, + bottom: 42, + child: Obx(() { + return AnimatedContainer( + duration: const Duration(milliseconds: 250), + width: _shrink ? 50 : 142, + height: 50, + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(50)), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color(0xFF8359FF), + Color(0xFF3D8AE0), + ], + ), + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(40)), + child: Image.asset( + Assets.imagesUserAvatar, + width: 40, + height: 40, + ), + ), + if(!_shrink) SizedBox(width: 4,), + if(!_shrink) Column( + children: [ + Text( + "与你匹配的", + style: TextStyle( + fontSize: 15, + color: Colors.white + ), + ), + Text( + "${controller.cityCode.value ?? 0}人正在连麦...", + style: TextStyle( + fontSize: 11, + color: Colors.white + ), + ) + ], + ) + ], + ), + ); + }), + ) + ], + ); } @override diff --git a/lib/pages/home/report_page.dart b/lib/pages/home/report_page.dart index 56cc797..f174a30 100644 --- a/lib/pages/home/report_page.dart +++ b/lib/pages/home/report_page.dart @@ -142,7 +142,7 @@ class ReportPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "资料作假", + "骚扰谩骂低俗", style: TextStyle( fontSize: 12.w, color: const Color.fromRGBO(51, 51, 51, 1), @@ -169,7 +169,7 @@ class ReportPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "色情低俗", + "垃圾广告", style: TextStyle( fontSize: 13.w, color: const Color.fromRGBO(51, 51, 51, 1), @@ -196,7 +196,7 @@ class ReportPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "涉政/涉独", + "涉政涉恐", style: TextStyle( fontSize: 13.w, color: const Color.fromRGBO(51, 51, 51, 1), @@ -223,7 +223,7 @@ class ReportPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "违法违禁", + "欺诈骗钱", style: TextStyle( fontSize: 13.w, color: const Color.fromRGBO(51, 51, 51, 1), @@ -250,7 +250,7 @@ class ReportPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "未成年相关", + "影响相亲体验", style: TextStyle( fontSize: 13.w, color: const Color.fromRGBO(51, 51, 51, 1), @@ -277,7 +277,7 @@ class ReportPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "欺诈/广告/引导第三方交易", + "涉黄", style: TextStyle( fontSize: 13.w, color: const Color.fromRGBO(51, 51, 51, 1), @@ -298,60 +298,6 @@ class ReportPage extends StatelessWidget { ], ), ), - SizedBox( - height: 32.w, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "恶意骚扰/侮辱谩骂", - style: TextStyle( - fontSize: 13.w, - color: const Color.fromRGBO(51, 51, 51, 1), - fontWeight: FontWeight.w500 - ), - ), - Checkbox( - value: controller.checked.value == 7, - onChanged: (value) { - controller.checked.value = 7; - - }, - activeColor: const Color.fromRGBO(117, 98, 249, 1), - side: const BorderSide(color: Colors.grey), - shape: const CircleBorder(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ], - ), - ), - SizedBox( - height: 32.w, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "其他", - style: TextStyle( - fontSize: 13.w, - color: const Color.fromRGBO(51, 51, 51, 1), - fontWeight: FontWeight.w500 - ), - ), - Checkbox( - value: controller.checked.value == 8, - onChanged: (value) { - controller.checked.value = 8; - - }, - activeColor: const Color.fromRGBO(117, 98, 249, 1), - side: const BorderSide(color: Colors.grey), - shape: const CircleBorder(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ], - ), - ), SizedBox(height: 29.w ,), Row( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/pages/message/conversation_tab.dart b/lib/pages/message/conversation_tab.dart index 04e80b4..1e67151 100644 --- a/lib/pages/message/conversation_tab.dart +++ b/lib/pages/message/conversation_tab.dart @@ -2,6 +2,7 @@ import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/pages/message/chat_page.dart'; import 'package:dating_touchme_app/pages/message/connect_history_page.dart'; +import 'package:dating_touchme_app/pages/message/nearby_user.dart'; import 'package:flutter/material.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:get/get.dart'; @@ -37,6 +38,36 @@ class _ConversationTabState extends State ), child: Row( children: [ + Column( + children: [ + Container( + width: 90.w, + height: 50.w, + margin: EdgeInsets.only(bottom: 5.w), + decoration: BoxDecoration( + color: const Color.fromRGBO(255, 228, 222, 1), + borderRadius: BorderRadius.all(Radius.circular(50.w)) + ), + child: Center( + child: Image.asset( + gaplessPlayback: true, + Assets.imagesNearbyUser, + width: 40.w, + height: 40.w, + ), + ), + ), + Text( + "附近的人", + style: TextStyle( + fontSize: 11.w + ), + ) + ], + ).onTap((){ + Get.to(() => NearbyUser()); + }), + SizedBox(width: 30.w), Column( children: [ Container( @@ -65,7 +96,7 @@ class _ConversationTabState extends State ], ).onTap((){ Get.to(() => ConnectHistoryPage()); - }) + }), ], ), ); diff --git a/lib/pages/message/nearby_user.dart b/lib/pages/message/nearby_user.dart new file mode 100644 index 0000000..af63fb7 --- /dev/null +++ b/lib/pages/message/nearby_user.dart @@ -0,0 +1,232 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dating_touchme_app/components/page_appbar.dart'; +import 'package:dating_touchme_app/controller/home/home_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/home/marriage_data.dart'; +import 'package:dating_touchme_app/pages/home/user_information_page.dart'; +import 'package:easy_refresh/easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class NearbyUser extends StatefulWidget { + const NearbyUser({super.key}); + + @override + State createState() => _NearbyUserState(); +} + +class _NearbyUserState extends State { + + + + final HomeController controller = Get.put(HomeController()); + late final EasyRefreshController _refreshController; + + @override + void initState() { + super.initState(); + _refreshController = EasyRefreshController(controlFinishRefresh: true, controlFinishLoad: true); + + controller.loadNearbyDisInitialData(); + + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PageAppbar(title: "附近的异性"), + body: Obx(() { + if (controller.nearbyIsLoading.value && controller.nearbyDisFeed.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载附近的人数据中...'), + ], + ), + ); + } + + if (controller.nearbyDisFeed.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.people_outline, size: 80, color: Colors.grey[300]), + SizedBox(height: 16), + Text( + '暂无附近的人,点击刷新', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + SizedBox(height: 8), + ], + ), + ).onTap((){ + controller.loadNearbyDisInitialData(); + }); + } + + return EasyRefresh( + controller: _refreshController, + header: ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '刷新中...', + processingText: '刷新中...', + processedText: '刷新完成', + failedText: '刷新失败', + noMoreText: '没有更多数据', + messageText: '更新时间 %T', + showMessage: false + ), + footer: ClassicFooter( + dragText: '上拉加载', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败', + noMoreText: '没有更多数据', + showMessage: false, + ), + + // 下拉刷新 + onRefresh: () async { + print('推荐列表下拉刷新被触发'); + try { + await controller.refreshNearbyDisData(); + print( '推荐列表刷新完成, hasMore: $controller.recommendHasMore.value'); + _refreshController.finishRefresh(); + _refreshController.resetFooter(); + } catch (e) { + print('推荐列表刷新失败: $e'); + _refreshController.finishRefresh(IndicatorResult.fail); + } + }, + // 上拉加载更多 + onLoad: () async { + print('推荐列表上拉加载被触发, hasMore: $controller.recommendHasMore.value'); + try { + await controller.loadNearbyDisMoreData(); + // 完成加载,根据是否有更多数据决定 + if (controller.recommendHasMore.value) { + _refreshController.finishLoad(IndicatorResult.success); + print('推荐列表加载更多成功'); + } else { + _refreshController.finishLoad(IndicatorResult.noMore); + print('推荐列表没有更多数据了'); + } + } catch (e) { + print('推荐列表加载更多失败: $e'); + _refreshController.finishLoad(IndicatorResult.fail); + } + }, + child: GridView.builder( + padding: EdgeInsets.only(top: 8.w, left: 10.w, right: 8.w, bottom: 0.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 每行2个 + crossAxisSpacing: 4.w, // 左右间距 + mainAxisSpacing: 4.w, // 上下间距 + childAspectRatio: 1, // 宽高比 + ), + itemCount: controller.nearbyDisFeed.isEmpty ? 1 : controller.nearbyDisFeed.length, + itemBuilder: (context, index) { + final visitor = controller.nearbyDisFeed[index]; + return VisitorItem(visitor: visitor); + }, + ), + ); + }), + ); + } +} + + +class VisitorItem extends StatelessWidget { + final MarriageData visitor; + + const VisitorItem({Key? key, required this.visitor}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(10.w)), + child: CachedNetworkImage( + imageUrl: "${visitor.profilePhoto}?x-oss-process=image/format,webp/resize,w_240", + width: 113.w, + height: 113.w, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + color: Colors.white38, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 1.w, + color: Colors.grey, + ), + ), + ), + errorWidget: (context, url, error) => + Image.asset( + Assets.imagesUserAvatar, + width: 113.w, + height: 113.w, + fit: BoxFit.cover, + ), + ), + ), + Positioned( + left: 8.w, + bottom: 12.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 113.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${visitor.age}岁 | ${visitor.districtName}", + style: TextStyle( + fontSize: 8.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ) + ], + ), + ) + ], + ).onTap((){ + Get.to(() => UserInformationPage(miId: visitor.miId)); + }); + } + +// String _formatTime(String timestamp) { +// var time = DateTime.parse(timestamp); +// final now = DateTime.now(); +// final difference = now.difference(time); +// +// if (difference.inMinutes < 1) { +// return '刚刚'; +// } else if (difference.inHours < 1) { +// return '${difference.inMinutes}分钟前'; +// } else if (difference.inDays < 1) { +// return '${difference.inHours}小时前'; +// } else if (difference.inDays < 7) { +// return '${difference.inDays}天前'; +// } else { +// return '${time.month}/${time.day}'; +// } +// } + +} \ No newline at end of file