11 changed files with 780 additions and 262 deletions
Split View
Diff Options
-
67lib/controller/home/timeline_trend_controller.dart
-
91lib/model/home/trend_data.dart
-
3lib/network/api_urls.dart
-
7lib/network/home_api.dart
-
37lib/network/home_api.g.dart
-
24lib/pages/home/event_info.dart
-
12lib/pages/home/timeline_page.dart
-
243lib/pages/home/timeline_trend.dart
-
523lib/pages/home/user_information_page.dart
-
16lib/pages/mine/edit_info_page.dart
-
19lib/pages/mine/mine_page.dart
@ -0,0 +1,67 @@ |
|||
import 'package:dating_touchme_app/network/home_api.dart'; |
|||
import 'package:easy_refresh/easy_refresh.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get/get.dart'; |
|||
|
|||
import '../../model/home/trend_data.dart'; |
|||
|
|||
class TimelineTrendController extends GetxController { |
|||
|
|||
final trendList = <Records>[].obs; |
|||
|
|||
final page = 1.obs; |
|||
final size = 10.obs; |
|||
|
|||
late final EasyRefreshController listRefreshController; |
|||
|
|||
late final HomeApi _homeApi; |
|||
|
|||
@override |
|||
void onInit() { |
|||
super.onInit(); |
|||
listRefreshController = EasyRefreshController( |
|||
controlFinishRefresh: true, |
|||
controlFinishLoad: true, |
|||
); |
|||
// 从全局依赖中获取HomeApi |
|||
_homeApi = Get.find<HomeApi>(); |
|||
|
|||
getTrendData(); |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
getTrendData() async { |
|||
try { |
|||
final response = await _homeApi.userPageOwnPostDynamic( |
|||
pageNum: page.value, |
|||
pageSize: size.value |
|||
); |
|||
if (response.data.isSuccess && response.data.data != null) { |
|||
final data = response.data.data?.records ?? []; |
|||
|
|||
trendList.addAll(data); |
|||
|
|||
if((data.length ?? 0) == size.value){ |
|||
|
|||
listRefreshController.finishLoad(IndicatorResult.success); |
|||
} else { |
|||
listRefreshController.finishLoad(IndicatorResult.noMore); |
|||
} |
|||
|
|||
} else { |
|||
|
|||
// 响应失败,抛出异常 |
|||
throw Exception(response.data.message ?? '获取数据失败'); |
|||
} |
|||
} catch(e){ |
|||
print('详情获取失败: $e'); |
|||
SmartDialog.showToast('动态失败'); |
|||
rethrow; |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
class TrendData { |
|||
List<Records>? records; |
|||
int? total; |
|||
int? size; |
|||
int? current; |
|||
int? pages; |
|||
|
|||
TrendData({this.records, this.total, this.size, this.current, this.pages}); |
|||
|
|||
TrendData.fromJson(Map<String, dynamic> json) { |
|||
if (json['records'] != null) { |
|||
records = <Records>[]; |
|||
json['records'].forEach((v) { |
|||
records!.add(new Records.fromJson(v)); |
|||
}); |
|||
} |
|||
total = json['total']; |
|||
size = json['size']; |
|||
current = json['current']; |
|||
pages = json['pages']; |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
final Map<String, dynamic> data = new Map<String, dynamic>(); |
|||
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? postId; |
|||
int? operationType; |
|||
String? postCommentContent; |
|||
String? userId; |
|||
String? miId; |
|||
String? nickName; |
|||
String? profilePhoto; |
|||
String? content; |
|||
String? mediaUrls; |
|||
String? topicTags; |
|||
String? createTime; |
|||
|
|||
Records( |
|||
{this.postId, |
|||
this.operationType, |
|||
this.postCommentContent, |
|||
this.userId, |
|||
this.miId, |
|||
this.nickName, |
|||
this.profilePhoto, |
|||
this.content, |
|||
this.mediaUrls, |
|||
this.topicTags, |
|||
this.createTime}); |
|||
|
|||
Records.fromJson(Map<String, dynamic> json) { |
|||
postId = json['postId']; |
|||
operationType = json['operationType']; |
|||
postCommentContent = json['postCommentContent']; |
|||
userId = json['userId']; |
|||
miId = json['miId']; |
|||
nickName = json['nickName']; |
|||
profilePhoto = json['profilePhoto']; |
|||
content = json['content']; |
|||
mediaUrls = json['mediaUrls']; |
|||
topicTags = json['topicTags']; |
|||
createTime = json['createTime']; |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
final Map<String, dynamic> data = new Map<String, dynamic>(); |
|||
data['postId'] = this.postId; |
|||
data['operationType'] = this.operationType; |
|||
data['postCommentContent'] = this.postCommentContent; |
|||
data['userId'] = this.userId; |
|||
data['miId'] = this.miId; |
|||
data['nickName'] = this.nickName; |
|||
data['profilePhoto'] = this.profilePhoto; |
|||
data['content'] = this.content; |
|||
data['mediaUrls'] = this.mediaUrls; |
|||
data['topicTags'] = this.topicTags; |
|||
data['createTime'] = this.createTime; |
|||
return data; |
|||
} |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:dating_touchme_app/components/page_appbar.dart'; |
|||
import 'package:dating_touchme_app/config/emoji_config.dart'; |
|||
import 'package:dating_touchme_app/controller/home/timeline_trend_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/trend_data.dart'; |
|||
import 'package:dating_touchme_app/pages/home/timeline_info.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 TimelineTrend extends StatelessWidget { |
|||
const TimelineTrend({super.key}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return GetX<TimelineTrendController>( |
|||
init: TimelineTrendController(), |
|||
builder: (controller){ |
|||
return Scaffold( |
|||
appBar: PageAppbar(title: "互动通知"), |
|||
body: EasyRefresh( |
|||
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('推荐列表下拉刷新被触发'); |
|||
controller.page.value = 1; |
|||
controller.trendList.clear(); |
|||
await controller.getTrendData(); |
|||
controller.listRefreshController.finishRefresh(IndicatorResult.success); |
|||
controller.listRefreshController.finishLoad(IndicatorResult.none); |
|||
}, |
|||
// 上拉加载更多 |
|||
onLoad: () async { |
|||
print('推荐列表上拉加载被触发, hasMore: '); |
|||
controller.page.value += 1; |
|||
controller.getTrendData(); |
|||
}, |
|||
child: ListView.separated( |
|||
// 关键:始终允许滚动,即使内容不足 |
|||
// 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 |
|||
padding: EdgeInsets.only(left: 12, right: 12), |
|||
itemBuilder: (context, index){ |
|||
|
|||
if (controller.trendList.isEmpty) { |
|||
return Center( |
|||
child: Column( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
Text('暂无数据'), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
return TrendItem(item: controller.trendList[index]); |
|||
}, |
|||
|
|||
separatorBuilder: (context, index) { |
|||
// 空状态或加载状态时不显示分隔符 |
|||
// if (controller.postList.isEmpty) { |
|||
// return const SizedBox.shrink(); |
|||
// } |
|||
return const SizedBox(height: 12); |
|||
}, |
|||
itemCount: controller.trendList.isEmpty ? 1 : controller.trendList.length, |
|||
), |
|||
), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
|
|||
class TrendItem extends StatefulWidget { |
|||
final Records item; |
|||
const TrendItem({super.key, required this.item}); |
|||
|
|||
@override |
|||
State<TrendItem> createState() => _TrendItemState(); |
|||
} |
|||
|
|||
class _TrendItemState extends State<TrendItem> { |
|||
|
|||
/// 构建输入框内容(文本+表情) |
|||
List<Widget> buildInputContentWidgets() { |
|||
final List<Widget> widgets = []; |
|||
final text = widget.item.content ?? ""; |
|||
final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]'); |
|||
|
|||
int lastMatchEnd = 0; |
|||
final matches = emojiRegex.allMatches(text); |
|||
|
|||
for (final match in matches) { |
|||
// 添加表情之前的文本 |
|||
if (match.start > lastMatchEnd) { |
|||
final textPart = text.substring(lastMatchEnd, match.start); |
|||
widgets.add( |
|||
Text( |
|||
textPart, |
|||
style: TextStyle(fontSize: 14.sp, color: Colors.black), |
|||
), |
|||
); |
|||
} |
|||
|
|||
// 添加表情图片 |
|||
final emojiId = match.group(1); |
|||
if (emojiId != null) { |
|||
final emoji = EmojiConfig.getEmojiById(emojiId); |
|||
if (emoji != null) { |
|||
widgets.add( |
|||
Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 0), |
|||
child: Image.asset( |
|||
emoji.path, |
|||
width: 24.w, |
|||
height: 24.w, |
|||
fit: BoxFit.contain, |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
|
|||
lastMatchEnd = match.end; |
|||
} |
|||
|
|||
// 添加剩余的文本 |
|||
if (lastMatchEnd < text.length) { |
|||
final textPart = text.substring(lastMatchEnd); |
|||
widgets.add( |
|||
Text( |
|||
textPart, |
|||
style: TextStyle(fontSize: 14.sp, color: Colors.black), |
|||
), |
|||
); |
|||
} |
|||
|
|||
return widgets; |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Row( |
|||
children: [ |
|||
ClipRRect( |
|||
borderRadius: BorderRadius.all(Radius.circular(40.w)), |
|||
child: CachedNetworkImage( |
|||
imageUrl: widget.item.profilePhoto ?? "", |
|||
width: 40.w, |
|||
height: 40.w, |
|||
fit: BoxFit.cover, |
|||
), |
|||
), |
|||
SizedBox(width: 15.w,), |
|||
Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
widget.item.nickName ?? "", |
|||
style: TextStyle( |
|||
fontSize: 13.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
), |
|||
if(widget.item.operationType == 1)Text( |
|||
"赞了你的动态", |
|||
style: TextStyle( |
|||
fontSize: 11.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
), |
|||
if(widget.item.operationType == 2)Text( |
|||
widget.item.postCommentContent ?? "", |
|||
maxLines: 1, |
|||
overflow: TextOverflow.ellipsis, |
|||
style: TextStyle( |
|||
fontSize: 11.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
), |
|||
Text( |
|||
widget.item.createTime ?? "", |
|||
style: TextStyle( |
|||
fontSize: 11.w, |
|||
color: const Color.fromRGBO(51, 51, 51, .6), |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
) |
|||
], |
|||
) |
|||
], |
|||
), |
|||
Container( |
|||
width: 80.w, |
|||
height: 80.w, |
|||
padding: EdgeInsets.all(10.w), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
|||
color: const Color.fromRGBO(0, 0, 0, .2) |
|||
), |
|||
child: !widget.item.content!.contains('[emoji:') ? Text( |
|||
widget.item.content ?? "", |
|||
overflow: TextOverflow.ellipsis, |
|||
maxLines: 3, |
|||
) : ClipRect( |
|||
child: Wrap( |
|||
crossAxisAlignment: WrapCrossAlignment.center, |
|||
children: buildInputContentWidgets(), |
|||
), |
|||
), |
|||
) |
|||
], |
|||
).onTap((){ |
|||
Get.to(() => TimelineInfo(id: widget.item.postId ?? "")); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
Write
Preview
Loading…
Cancel
Save