Browse Source

合并代码

dev-2.0
王子贤 3 weeks ago
parent
commit
7beabed98c
29 changed files with 4566 additions and 352 deletions
  1. BIN
      assets/images/comment_icon.png
  2. BIN
      assets/images/like_active.png
  3. BIN
      assets/images/like_icon.png
  4. BIN
      assets/images/publish.png
  5. 2
      lib/controller/home/home_controller.dart
  6. 227
      lib/controller/home/report_controller.dart
  7. 107
      lib/controller/home/send_timeline_controller.dart
  8. 176
      lib/controller/home/timeline_controller.dart
  9. 236
      lib/controller/home/timeline_info_controller.dart
  10. 67
      lib/controller/home/timeline_trend_controller.dart
  11. 2
      lib/controller/message/chat_settings_controller.dart
  12. 4
      lib/generated/assets.dart
  13. 149
      lib/model/home/post_comment_data.dart
  14. 103
      lib/model/home/post_data.dart
  15. 91
      lib/model/home/trend_data.dart
  16. 46
      lib/network/api_urls.dart
  17. 60
      lib/network/home_api.dart
  18. 353
      lib/network/home_api.g.dart
  19. 159
      lib/pages/home/all_timeline.dart
  20. 5
      lib/pages/home/home_page.dart
  21. 92
      lib/pages/home/recommend_window.dart
  22. 830
      lib/pages/home/report_page.dart
  23. 734
      lib/pages/home/send_timeline.dart
  24. 636
      lib/pages/home/timeline_info.dart
  25. 365
      lib/pages/home/timeline_item.dart
  26. 170
      lib/pages/home/timeline_page.dart
  27. 248
      lib/pages/home/timeline_trend.dart
  28. 54
      lib/pages/home/timeline_window.dart
  29. 2
      lib/pages/home/user_information_page.dart

BIN
assets/images/comment_icon.png

Before After
Width: 62  |  Height: 62  |  Size: 1000 B

BIN
assets/images/like_active.png

Before After
Width: 55  |  Height: 50  |  Size: 613 B

BIN
assets/images/like_icon.png

Before After
Width: 55  |  Height: 50  |  Size: 824 B

BIN
assets/images/publish.png

Before After
Width: 238  |  Height: 238  |  Size: 12 KiB

2
lib/controller/home/home_controller.dart

@ -33,6 +33,8 @@ class HomeController extends GetxController {
// GetX依赖注入中获取HomeApi实例 // GetX依赖注入中获取HomeApi实例
late final HomeApi _homeApi; late final HomeApi _homeApi;
final timelineTab = 0.obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();

227
lib/controller/home/report_controller.dart

@ -0,0 +1,227 @@
import 'dart:io';
import 'package:dating_touchme_app/network/home_api.dart';
import 'package:dating_touchme_app/oss/oss_manager.dart';
import 'package:flustars/flustars.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class ReportController extends GetxController {
final String id;
ReportController({required this.id});
final checked = 1.obs;
final message = ''.obs;
final blockUser = false.obs;
final messageController = TextEditingController().obs;
final imgList = <String>[].obs;
late final HomeApi _homeApi;
final isClick = false.obs;
@override
void onInit() {
super.onInit();
_homeApi = Get.find<HomeApi>();
}
// -
Future<void> handleCameraCapture() async {
try {
//
final ok = await _ensurePermission(
Permission.camera,
denyToast: '相机权限被拒绝,请在设置中允许访问相机',
);
if (!ok) return;
// /
await _ensurePermission(Permission.microphone, denyToast: '麦克风权限被拒绝');
//
final ImagePicker picker = ImagePicker();
final XFile? photo = await picker.pickImage(source: ImageSource.camera);
if (photo != null) {
await processSelectedImage(File(photo.path));
}
} catch (e) {
print('拍照失败: $e');
//
if (e.toString().contains('permission') || e.toString().contains('权限')) {
SmartDialog.showToast('相机权限被拒绝,请在设置中允许访问相机');
} else if (e.toString().contains('camera') ||
e.toString().contains('相机')) {
SmartDialog.showToast('设备没有可用的相机');
} else {
SmartDialog.showToast('拍照失败,请重试');
}
}
}
Future<void> handleGallerySelection() async {
try {
// /
// final ok = await _ensurePermission(
// Permission.photos,
// // Android photos storage/mediaLibrarypermission_handler
// denyToast: '相册权限被拒绝,请在设置中允许访问相册',
// );
// if (!ok) return;
//
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
await processSelectedImage(File(image.path));
}
} catch (e) {
print('选择图片失败: $e');
//
if (e.toString().contains('permission') || e.toString().contains('权限')) {
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册');
} else {
SmartDialog.showToast('选择图片失败,请重试');
}
}
}
Future<void> handleMultiGallerySelection() async {
try {
// /
// final ok = await _ensurePermission(
// Permission.photos,
// // Android photos storage/mediaLibrarypermission_handler
// denyToast: '相册权限被拒绝,请在设置中允许访问相册',
// );
// if (!ok) return;
//
final ImagePicker picker = ImagePicker();
final List<XFile>? image = await picker.pickMultiImage(limit: 9 - imgList.length);
if (image != null) {
final futures = image.map((e){
return processSelectedMoreImage(File(e.path));
});
final list = await Future.wait(futures);
imgList.addAll(list);
print(imgList);
SmartDialog.dismiss();
SmartDialog.showToast('上传相册成功');
}
} catch (e) {
print('选择图片失败: $e');
//
if (e.toString().contains('permission') || e.toString().contains('权限')) {
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册');
} else {
SmartDialog.showToast('选择图片失败,请重试');
}
}
}
//
Future<bool> _ensurePermission(Permission permission, {String? denyToast}) async {
var status = await permission.status;
if (status.isGranted) return true;
if (status.isDenied || status.isRestricted || status.isLimited) {
status = await permission.request();
if (status.isGranted) return true;
if (denyToast != null) SmartDialog.showToast(denyToast);
return false;
}
if (status.isPermanentlyDenied) {
if (denyToast != null) SmartDialog.showToast('$denyToast,前往系统设置开启');
//
Future.delayed(const Duration(milliseconds: 300), openAppSettings);
return false;
}
return false;
}
//
Future<void> processSelectedImage(File imageFile) async {
try {
//
SmartDialog.showLoading(msg: '上传相册中...');
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}';
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName);
print('上传成功,图片URL: $imageUrl');
imgList.add(imageUrl);
SmartDialog.dismiss();
SmartDialog.showToast('相册上传成功');
} catch (e) {
SmartDialog.dismiss();
print('处理图片失败: $e');
SmartDialog.showToast('上传相册失败,请重试');
}
}
//
Future<String> processSelectedMoreImage(File imageFile) async {
try {
//
SmartDialog.showLoading(msg: '上传相册中...');
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}';
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName);
print('上传成功,图片URL: $imageUrl');
return imageUrl;
} catch (e) {
SmartDialog.dismiss();
print('处理图片失败: $e');
SmartDialog.showToast('上传相册失败,请重试');
return "";
}
}
sendReport() async {
try {
if(isClick.value) return;
isClick.value = true;
final response = await _homeApi.userReportPost({
"id": id,
"reportPicUrls": imgList.isNotEmpty ? imgList.join(",") : "",
"reportContent": message.value,
"reportReason": checked.value
});
if (response.data.isSuccess) {
SmartDialog.showToast('举报已提交成功');
Get.back();
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e){
print('举报提交失败: $e');
SmartDialog.showToast('举报提交失败');
rethrow;
} finally {
isClick.value = false;
}
}
}

107
lib/controller/home/send_timeline_controller.dart

@ -0,0 +1,107 @@
import 'package:dating_touchme_app/config/emoji_config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
class SendTimelineController extends GetxController {
final title = "".obs;
final message = ''.obs;
final TextEditingController messageController = TextEditingController();
final focusNode = FocusNode().obs;
final isEmojiVisible = false.obs;
@override
void onInit() {
super.onInit();
focusNode.value.addListener(() {
if (focusNode.value.hasFocus) {
//
isEmojiVisible.value = false;
}
});
}
@override
void onClose() {
super.onClose();
focusNode.value.dispose();
}
void toggleEmojiPanel() {
isEmojiVisible.value = !isEmojiVisible.value;
FocusManager.instance.primaryFocus?.unfocus();
}
void handleEmojiSelected(EmojiItem emoji) {
//
final currentText = messageController.text;
final emojiText = '[emoji:${emoji.id}]';
messageController.text = currentText + emojiText;
//
messageController.selection = TextSelection.fromPosition(
TextPosition(offset: messageController.text.length),
);
}
/// +
List<Widget> buildInputContentWidgets() {
final List<Widget> widgets = [];
final text = messageController.value.text;
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: 2.w),
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;
}
}

176
lib/controller/home/timeline_controller.dart

@ -0,0 +1,176 @@
import 'package:dating_touchme_app/model/home/marriage_data.dart';
import 'package:dating_touchme_app/network/home_api.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../model/home/post_data.dart';
class TimelineController extends GetxController {
//
final recommendFeed = <MarriageData>[].obs;
//
final nearbyFeed = <MarriageData>[].obs;
//
final recommendIsLoading = false.obs;
final recommendPage = 1.obs;
final recommendHasMore = true.obs;
//
final nearbyIsLoading = false.obs;
final nearbyPage = 1.obs;
final nearbyHasMore = true.obs;
//
final selectedTabIndex = 0.obs;
final topTab = 0.obs;
final timelineTab = 0.obs;
//
final pageSize = 10;
// GetX依赖注入中获取HomeApi实例
late final HomeApi _homeApi;
final page = 1.obs;
final size = 10.obs;
final hasMore = true.obs;
final postList = <Records>[].obs;
@override
void onInit() {
super.onInit();
// HomeApi
_homeApi = Get.find<HomeApi>();
loadPostList();
}
loadPostList() async {
if (recommendIsLoading.value || !recommendHasMore.value) return;
try{
recommendIsLoading.value = true;
final response = await _homeApi.userPagePost(
pageNum: page.value,
pageSize: size.value,
);
if (response.data.isSuccess && response.data.data != null) {
final data = response.data.data?.records ?? [];
postList.addAll(data.toList());
if((data.length ?? 0) == size.value){
hasMore.value = true;
} else {
hasMore.value = false;
}
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e) {
print('帖子列表获取失败: $e');
SmartDialog.showToast('帖子列表获取失败');
rethrow;
} finally {
recommendIsLoading.value = false;
}
}
///
void setSelectedTabIndex(int index) {
print('Setting selected tab index to: $index');
selectedTabIndex.value = index;
// UI能够更新
update();
}
void setTopTab(int index) {
print('Setting selected tab index to: $index');
topTab.value = index;
// UI能够更新
update();
}
void setTimelineTab(int index) {
print('Setting selected tab index to: $index');
timelineTab.value = index;
// UI能够更新
update();
}
///
List<MarriageData> getFeedListByTab(int tabIndex) {
return tabIndex == 0 ? List.from(recommendFeed) : List.from(nearbyFeed);
}
///
/// recordscurrentpagesMap
Future<Map<String, dynamic>> _fetchMarriageData({
required int pageNum,
required int type,
}) async {
try {
print('_fetchMarriageData - pageNum: $pageNum, pageSize: $pageSize, type: $type');
// API获取数据
var response = await _homeApi.getMarriageList(
pageNum: pageNum,
pageSize: pageSize,
type: type,
);
if (response.data.isSuccess) {
final paginatedData = response.data.data;
// data是否为空
if (paginatedData == null) {
return {
'records': <MarriageData>[],
'current': pageNum,
'pages': 1,
'total': 0,
'size': pageSize,
};
}
// data PaginatedResponse<dynamic>使
// records dynamic MarriageData
final allRecords = paginatedData.records
.map((item) => MarriageData.fromJson(item as Map<String, dynamic>))
.toList();
//
final records = allRecords.where((item) => !item.isLive).toList();
print('_fetchMarriageData 返回 - 请求页码: $pageNum, 返回当前页: ${paginatedData.current}, 总页数: ${paginatedData.pages}, 原始记录数: ${allRecords.length}, 过滤后记录数: ${records.length}');
return {
'records': records,
'current': paginatedData.current,
'pages': paginatedData.pages,
'total': paginatedData.total,
'size': paginatedData.size,
};
} else {
//
throw Exception(response.data.message);
}
} catch (e) {
//
rethrow;
}
}
///
void _handleError(String logMessage, dynamic error, String toastMessage) {
//
print('$logMessage: $error');
//
SmartDialog.showToast(toastMessage);
}
}

236
lib/controller/home/timeline_info_controller.dart

@ -0,0 +1,236 @@
import 'package:dating_touchme_app/config/emoji_config.dart';
import 'package:dating_touchme_app/model/home/post_data.dart';
import 'package:dating_touchme_app/network/home_api.dart';
import 'package:easy_refresh/easy_refresh.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';
import 'package:dating_touchme_app/model/home/post_comment_data.dart' as pcd;
class TimelineInfoController extends GetxController {
final String id;
TimelineInfoController({required this.id});
final showInput = false.obs;
late final HomeApi _homeApi;
final item = Records().obs;
final imgList = <String>[].obs;
final commentList = <pcd.Records>[].obs;
final page = 1.obs;
final size = 10.obs;
final parentId = "0".obs;
late final EasyRefreshController listRefreshController;
final message = ''.obs;
final messageController = TextEditingController().obs;
final focusNode = FocusNode().obs;
@override
void onInit() {
super.onInit();
listRefreshController = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
// HomeApi
_homeApi = Get.find<HomeApi>();
getPostData();
getCommentData();
}
@override
void onClose() {
super.onClose();
focusNode.value.dispose();
}
getPostData() async {
try {
final response = await _homeApi.userPagePostDetail(id: id);
if (response.data.isSuccess && response.data.data != null) {
item.value = response.data.data ?? Records();
if(item.value.mediaUrls != null && item.value.mediaUrls != ""){
imgList.value = item.value.mediaUrls!.split(",");
}
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e){
print('详情获取失败: $e');
SmartDialog.showToast('详情获取失败');
rethrow;
}
}
getCommentData() async {
try {
final response = await _homeApi.userPagePostComment(
postId: id,
pageNum: page.value,
pageSize: size.value
);
if (response.data.isSuccess && response.data.data != null) {
final data = response.data.data?.records ?? [];
commentList.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;
}
}
likePost() async {
try {
final response = await _homeApi.userLikePost({
"id": id,
"isLiked": !(item.value.isLiked ?? false),
});
if (response.data.isSuccess) {
if(item.value.isLiked ?? false){
SmartDialog.showToast('取消点赞成功');
} else {
SmartDialog.showToast('点赞成功');
}
getPostData();
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e){
print('帖子发布失败: $e');
SmartDialog.showToast('帖子发布失败');
rethrow;
}
}
sendComment() async {
try {
if(message.value == ""){
SmartDialog.showToast('请输入评论');
return;
}
final response = await _homeApi.userCreatePostComment({
"postId": id,
"parentId": parentId.value,
"content": message.value,
});
if (response.data.isSuccess) {
page.value = 1;
commentList.clear();
getPostData();
getCommentData();
showInput.value = false;
FocusScope.of(Get.context!).unfocus();
parentId.value = "0";
message.value = "";
messageController.value.value = TextEditingValue(
text: message.value,
selection: TextSelection.fromPosition(TextPosition(offset: message.value.length)),
);
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e){
print('帖子发布失败: $e');
SmartDialog.showToast('帖子发布失败');
rethrow;
}
}
/// +
List<Widget> buildInputContentWidgets() {
final List<Widget> widgets = [];
final text = item.value.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;
}
}

67
lib/controller/home/timeline_trend_controller.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;
}
}
}

2
lib/controller/message/chat_settings_controller.dart

@ -494,7 +494,7 @@ class ChatSettingsController extends GetxController {
/// ///
void reportUser() { void reportUser() {
// //
Get.to(() => ReportPage());
Get.to(() => ReportPage(id: "",));
} }
/// ///

4
lib/generated/assets.dart

@ -98,6 +98,7 @@ class Assets {
static const String imagesCheck = 'assets/images/check.png'; static const String imagesCheck = 'assets/images/check.png';
static const String imagesCloseArrow = 'assets/images/close_arrow.png'; static const String imagesCloseArrow = 'assets/images/close_arrow.png';
static const String imagesCloseLive = 'assets/images/close_live.png'; static const String imagesCloseLive = 'assets/images/close_live.png';
static const String imagesCommentIcon = 'assets/images/comment_icon.png';
static const String imagesConnectHistoryIcon = 'assets/images/connect_history_icon.png'; static const String imagesConnectHistoryIcon = 'assets/images/connect_history_icon.png';
static const String imagesCustomer = 'assets/images/customer.png'; static const String imagesCustomer = 'assets/images/customer.png';
static const String imagesDailyTasks = 'assets/images/daily_tasks.png'; static const String imagesDailyTasks = 'assets/images/daily_tasks.png';
@ -138,6 +139,8 @@ class Assets {
static const String imagesJoinRoomIcon = 'assets/images/join_room_icon.png'; static const String imagesJoinRoomIcon = 'assets/images/join_room_icon.png';
static const String imagesKickUser = 'assets/images/kick_user.png'; static const String imagesKickUser = 'assets/images/kick_user.png';
static const String imagesLastMsgIcon = 'assets/images/last_msg_icon.png'; static const String imagesLastMsgIcon = 'assets/images/last_msg_icon.png';
static const String imagesLikeActive = 'assets/images/like_active.png';
static const String imagesLikeIcon = 'assets/images/like_icon.png';
static const String imagesLimitTime = 'assets/images/limit_time.png'; static const String imagesLimitTime = 'assets/images/limit_time.png';
static const String imagesLiveIcon = 'assets/images/live_icon.png'; static const String imagesLiveIcon = 'assets/images/live_icon.png';
static const String imagesLocationIcon = 'assets/images/location_icon.png'; static const String imagesLocationIcon = 'assets/images/location_icon.png';
@ -179,6 +182,7 @@ class Assets {
static const String imagesPlatVoiceMessageSelf = 'assets/images/plat_voice_message_self.png'; static const String imagesPlatVoiceMessageSelf = 'assets/images/plat_voice_message_self.png';
static const String imagesPlayIcon = 'assets/images/play_icon.png'; static const String imagesPlayIcon = 'assets/images/play_icon.png';
static const String imagesPlayer = 'assets/images/player.png'; static const String imagesPlayer = 'assets/images/player.png';
static const String imagesPublish = 'assets/images/publish.png';
static const String imagesQuestionIcon = 'assets/images/question_icon.png'; static const String imagesQuestionIcon = 'assets/images/question_icon.png';
static const String imagesRealChecked = 'assets/images/real_checked.png'; static const String imagesRealChecked = 'assets/images/real_checked.png';
static const String imagesRealName = 'assets/images/real_name.png'; static const String imagesRealName = 'assets/images/real_name.png';

149
lib/model/home/post_comment_data.dart

@ -0,0 +1,149 @@
class PostCommentData {
List<Records>? records;
int? total;
int? size;
int? current;
int? pages;
PostCommentData(
{this.records, this.total, this.size, this.current, this.pages});
PostCommentData.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? id;
String? userId;
String? miId;
String? nickName;
int? genderCode;
String? profilePhoto;
String? parentId;
String? content;
String? createTime;
List<ChildPostCommentList>? childPostCommentList;
Records(
{this.id,
this.userId,
this.miId,
this.nickName,
this.genderCode,
this.profilePhoto,
this.parentId,
this.content,
this.createTime,
this.childPostCommentList});
Records.fromJson(Map<String, dynamic> json) {
id = json['id'];
userId = json['userId'];
miId = json['miId'];
nickName = json['nickName'];
genderCode = json['genderCode'];
profilePhoto = json['profilePhoto'];
parentId = json['parentId'];
content = json['content'];
createTime = json['createTime'];
if (json['childPostCommentList'] != null) {
childPostCommentList = <ChildPostCommentList>[];
json['childPostCommentList'].forEach((v) {
childPostCommentList!.add(new ChildPostCommentList.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['userId'] = this.userId;
data['miId'] = this.miId;
data['nickName'] = this.nickName;
data['genderCode'] = this.genderCode;
data['profilePhoto'] = this.profilePhoto;
data['parentId'] = this.parentId;
data['content'] = this.content;
data['createTime'] = this.createTime;
if (this.childPostCommentList != null) {
data['childPostCommentList'] =
this.childPostCommentList!.map((v) => v.toJson()).toList();
}
return data;
}
}
class ChildPostCommentList {
String? id;
String? userId;
String? miId;
String? nickName;
int? genderCode;
String? profilePhoto;
String? parentId;
String? content;
String? createTime;
Null? childPostCommentList;
ChildPostCommentList(
{this.id,
this.userId,
this.miId,
this.nickName,
this.genderCode,
this.profilePhoto,
this.parentId,
this.content,
this.createTime,
this.childPostCommentList});
ChildPostCommentList.fromJson(Map<String, dynamic> json) {
id = json['id'];
userId = json['userId'];
miId = json['miId'];
nickName = json['nickName'];
genderCode = json['genderCode'];
profilePhoto = json['profilePhoto'];
parentId = json['parentId'];
content = json['content'];
createTime = json['createTime'];
childPostCommentList = json['childPostCommentList'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['userId'] = this.userId;
data['miId'] = this.miId;
data['nickName'] = this.nickName;
data['genderCode'] = this.genderCode;
data['profilePhoto'] = this.profilePhoto;
data['parentId'] = this.parentId;
data['content'] = this.content;
data['createTime'] = this.createTime;
data['childPostCommentList'] = this.childPostCommentList;
return data;
}
}

103
lib/model/home/post_data.dart

@ -0,0 +1,103 @@
class PostData {
List<Records>? records;
int? total;
int? size;
int? current;
int? pages;
PostData({this.records, this.total, this.size, this.current, this.pages});
PostData.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? id;
String? userId;
String? miId;
String? nickName;
int? genderCode;
String? profilePhoto;
String? content;
String? mediaUrls;
String? topicTags;
int? status;
bool? isLiked;
int? likeCount;
int? commentCount;
String? createTime;
Records(
{this.id,
this.userId,
this.miId,
this.nickName,
this.genderCode,
this.profilePhoto,
this.content,
this.mediaUrls,
this.topicTags,
this.status,
this.isLiked,
this.likeCount,
this.commentCount,
this.createTime});
Records.fromJson(Map<String, dynamic> json) {
id = json['id'];
userId = json['userId'];
miId = json['miId'];
nickName = json['nickName'];
genderCode = json['genderCode'];
profilePhoto = json['profilePhoto'];
content = json['content'];
mediaUrls = json['mediaUrls'];
topicTags = json['topicTags'];
status = json['status'];
isLiked = json['isLiked'];
likeCount = json['likeCount'];
commentCount = json['commentCount'];
createTime = json['createTime'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['userId'] = this.userId;
data['miId'] = this.miId;
data['nickName'] = this.nickName;
data['genderCode'] = this.genderCode;
data['profilePhoto'] = this.profilePhoto;
data['content'] = this.content;
data['mediaUrls'] = this.mediaUrls;
data['topicTags'] = this.topicTags;
data['status'] = this.status;
data['isLiked'] = this.isLiked;
data['likeCount'] = this.likeCount;
data['commentCount'] = this.commentCount;
data['createTime'] = this.createTime;
return data;
}
}

91
lib/model/home/trend_data.dart

@ -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;
}
}

46
lib/network/api_urls.dart

@ -183,4 +183,50 @@ class ApiUrls {
static const String userGetSysInfo = static const String userGetSysInfo =
'dating-agency-uec/user/get/sys-info'; 'dating-agency-uec/user/get/sys-info';
static const String userPagePost =
'dating-agency-service/user/page/post';
static const String userPagePostDetail =
'dating-agency-service/user/page/post/detail';
static const String userCreatePost =
'dating-agency-service/user/create/post';
static const String userPagePostComment =
'dating-agency-service/user/page/post-comment';
static const String userLikePost =
'dating-agency-service/user/like/post';
static const String userCreatePostComment =
'dating-agency-service/user/create/post-comment';
static const String userReportPost =
'dating-agency-service/user/report/post';
static const String userGetSiteActivityPage =
'dating-agency-service/user/get/site/activity/page';
static const String userGetSiteActivityDetails =
'dating-agency-service/user/get/site/activity/details';
static const String userPageAuthorPost =
'dating-agency-service/user/page/author-post';
static const String userPageOwnPostDynamic =
'dating-agency-service/user/page/own-post-dynamic';
static const String userParticipateInSiteActivity =
'dating-agency-service/user/participate/in/site/qulianlian-activity';
static const String userQuitSiteActivity =
'dating-agency-service/user/quit/site/activity';
static const String userPageBannerByCustomer =
'dating-agency-service/user/page/banner/by/customer';
static const String userPageDongwoMatchmakerMarriageInformation =
'dating-agency-service/user/page/dongwo/matchmaker-marriage-information';
} }

60
lib/network/home_api.dart

@ -1,3 +1,6 @@
import 'package:dating_touchme_app/model/home/post_comment_data.dart' hide Records;
import 'package:dating_touchme_app/model/home/post_data.dart';
import 'package:dating_touchme_app/model/home/trend_data.dart' hide Records;
import 'package:dating_touchme_app/model/live/matchmaker_task.dart'; import 'package:dating_touchme_app/model/live/matchmaker_task.dart';
import 'package:dating_touchme_app/network/api_urls.dart'; import 'package:dating_touchme_app/network/api_urls.dart';
import 'package:dating_touchme_app/network/response_model.dart'; import 'package:dating_touchme_app/network/response_model.dart';
@ -28,4 +31,61 @@ abstract class HomeApi {
}); });
@GET(ApiUrls.userPagePost)
Future<HttpResponse<BaseResponse<PostData>>> userPagePost({
@Query('pageNum') required int pageNum,
@Query('pageSize') required int pageSize,
});
@GET(ApiUrls.userPagePostDetail)
Future<HttpResponse<BaseResponse<Records>>> userPagePostDetail({
@Query('id') required String id,
});
@POST(ApiUrls.userCreatePost)
Future<HttpResponse<BaseResponse<dynamic>>> userCreatePost(
@Body() Map<String, dynamic> data,
);
@GET(ApiUrls.userPagePostComment)
Future<HttpResponse<BaseResponse<PostCommentData>>> userPagePostComment({
@Query('pageNum') required int pageNum,
@Query('pageSize') required int pageSize,
@Query('postId') required String postId,
});
@POST(ApiUrls.userLikePost)
Future<HttpResponse<BaseResponse<dynamic>>> userLikePost(
@Body() Map<String, dynamic> data,
);
@POST(ApiUrls.userCreatePostComment)
Future<HttpResponse<BaseResponse<dynamic>>> userCreatePostComment(
@Body() Map<String, dynamic> data,
);
@POST(ApiUrls.userReportPost)
Future<HttpResponse<BaseResponse<dynamic>>> userReportPost(
@Body() Map<String, dynamic> data,
);
@GET(ApiUrls.userPageOwnPostDynamic)
Future<HttpResponse<BaseResponse<TrendData>>> userPageOwnPostDynamic({
@Query('pageNum') required int pageNum,
@Query('pageSize') required int pageSize,
});
@POST(ApiUrls.userParticipateInSiteActivity)
Future<HttpResponse<BaseResponse<dynamic>>> userParticipateInSiteActivity(
@Body() Map<String, dynamic> data,
);
@POST(ApiUrls.userQuitSiteActivity)
Future<HttpResponse<BaseResponse<dynamic>>> userQuitSiteActivity(
@Body() Map<String, dynamic> data,
);
} }

353
lib/network/home_api.g.dart

@ -98,6 +98,359 @@ class _HomeApi implements HomeApi {
return httpResponse; return httpResponse;
} }
@override
Future<HttpResponse<BaseResponse<PostData>>> userPagePost({
required int pageNum,
required int pageSize,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{
r'pageNum': pageNum,
r'pageSize': pageSize,
};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<HttpResponse<BaseResponse<PostData>>>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/page/post',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<PostData> _value;
try {
_value = BaseResponse<PostData>.fromJson(
_result.data!,
(json) => PostData.fromJson(json as Map<String, dynamic>),
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<Records>>> userPagePostDetail({
required String id,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{r'id': id};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<HttpResponse<BaseResponse<Records>>>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/page/post/detail',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<Records> _value;
try {
_value = BaseResponse<Records>.fromJson(
_result.data!,
(json) => Records.fromJson(json as Map<String, dynamic>),
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> userCreatePost(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/create/post',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<PostCommentData>>> userPagePostComment({
required int pageNum,
required int pageSize,
required String postId,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{
r'pageNum': pageNum,
r'pageSize': pageSize,
r'postId': postId,
};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options =
_setStreamType<HttpResponse<BaseResponse<PostCommentData>>>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/page/post-comment',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl),
),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<PostCommentData> _value;
try {
_value = BaseResponse<PostCommentData>.fromJson(
_result.data!,
(json) => PostCommentData.fromJson(json as Map<String, dynamic>),
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> userLikePost(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/like/post',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> userCreatePostComment(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/create/post-comment',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> userReportPost(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/report/post',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<TrendData>>> userPageOwnPostDynamic({
required int pageNum,
required int pageSize,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{
r'pageNum': pageNum,
r'pageSize': pageSize,
};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<HttpResponse<BaseResponse<TrendData>>>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/page/own-post-dynamic',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<TrendData> _value;
try {
_value = BaseResponse<TrendData>.fromJson(
_result.data!,
(json) => TrendData.fromJson(json as Map<String, dynamic>),
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> userParticipateInSiteActivity(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/participate/in/site/qulianlian-activity',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<dynamic>>> userQuitSiteActivity(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<BaseResponse<dynamic>>>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-service/user/quit/site/activity',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<dynamic> _value;
try {
_value = BaseResponse<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) { RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic && if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes || !(requestOptions.responseType == ResponseType.bytes ||

159
lib/pages/home/all_timeline.dart

@ -0,0 +1,159 @@
import 'package:dating_touchme_app/controller/home/timeline_controller.dart';
import 'package:dating_touchme_app/pages/home/timeline_item.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
/// Tab
class AllTimeline extends StatefulWidget {
const AllTimeline({super.key});
@override
State<AllTimeline> createState() => _AllTimelineState();
}
class _AllTimelineState extends State<AllTimeline>
with AutomaticKeepAliveClientMixin {
late final TimelineController controller;
late final EasyRefreshController _refreshController;
@override
void initState() {
super.initState();
// TimelineController
if (!Get.isRegistered<TimelineController>()) {
Get.put(TimelineController());
}
controller = Get.find<TimelineController>();
_refreshController = EasyRefreshController(controlFinishRefresh: true, controlFinishLoad: true);
}
@override
void dispose() {
_refreshController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Obx(() {
if (controller.recommendIsLoading.value && controller.postList.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 {
controller.page.value = 1;
controller.postList.clear();
await controller.loadPostList();
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 {
controller.page.value += 1;
await controller.loadPostList();
//
if (controller.hasMore.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.postList.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.postList[index];
return TimelineItem(item: item,);
},
separatorBuilder: (context, index) {
//
if (controller.postList.isEmpty) {
return const SizedBox.shrink();
}
return const SizedBox(height: 12);
},
// item
itemCount: controller.postList.isEmpty ? 1 : controller.postList.length,
)
);
});
}
@override
bool get wantKeepAlive => true;
}

5
lib/pages/home/home_page.dart

@ -1,4 +1,5 @@
import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/generated/assets.dart';
import 'package:dating_touchme_app/pages/home/all_timeline.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:dating_touchme_app/controller/home/home_controller.dart'; import 'package:dating_touchme_app/controller/home/home_controller.dart';
@ -63,7 +64,7 @@ class _HomePageState extends State<HomePage>
// //
RecommendTab(), RecommendTab(),
// //
NearbyTab(),
AllTimeline(),
], ],
); );
}), }),
@ -87,7 +88,7 @@ class _HomePageState extends State<HomePage>
children: [ children: [
_buildTabButton(title: '推荐', index: 0, controller: controller), _buildTabButton(title: '推荐', index: 0, controller: controller),
const SizedBox(width: 28), const SizedBox(width: 28),
_buildTabButton(title: '同城', index: 1, controller: controller),
_buildTabButton(title: '广场', index: 1, controller: controller),
], ],
), ),
bottom: const PreferredSize( bottom: const PreferredSize(

92
lib/pages/home/recommend_window.dart

@ -0,0 +1,92 @@
import 'package:dating_touchme_app/controller/home/home_controller.dart';
import 'package:dating_touchme_app/pages/home/nearby_tab.dart';
import 'package:dating_touchme_app/pages/home/recommend_tab.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class RecommendWindow extends StatefulWidget {
const RecommendWindow({super.key});
@override
State<RecommendWindow> createState() => _RecommendWindowState();
}
class _RecommendWindowState extends State<RecommendWindow> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
late TabController tabController;
final HomeController controller = Get.find<HomeController>();
@override
void initState() {
super.initState();
tabController = TabController(length: 2, vsync: this);
}
@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('同城'),
// ),
// ),
// ],
// 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) async {
// print('相亲页面 Tab: $index');
// if (controller.selectedTabIndex.value != index) {
// controller.setSelectedTabIndex(index);
// // UI
// controller.update();
// }
// },
// ),
Expanded(
child: Obx(() {
// 使 IndexedStack
return IndexedStack(
index: controller.selectedTabIndex.value,
children: const [
//
RecommendTab(),
//
NearbyTab(),
],
);
}),
),
],
);
}
@override
bool get wantKeepAlive => true;
}

830
lib/pages/home/report_page.dart

@ -1,394 +1,528 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dating_touchme_app/components/page_appbar.dart'; import 'package:dating_touchme_app/components/page_appbar.dart';
import 'package:dating_touchme_app/controller/home/report_controller.dart';
import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart';
class ReportPage extends StatefulWidget {
const ReportPage({super.key});
class ReportPage extends StatelessWidget {
final String id;
const ReportPage({super.key, required this.id});
@override
State<ReportPage> createState() => _ReportPageState();
}
class _ReportPageState extends State<ReportPage> {
int checked = 0;
void _showAvatarPopup(ReportController controller){
Navigator.of(Get.context!).push(
TDSlidePopupRoute(
slideTransitionFrom: SlideTransitionFrom.bottom,
builder: (context) {
return Container(
height: 176,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
),
),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
),
child: TDCell(
arrow: false,
titleWidget: Center(
child: Text('拍照', style: TextStyle(fontSize: 16.w, color: const Color.fromRGBO(51, 51, 51, 1))),
),
style: TDCellStyle(
padding: EdgeInsets.all(TDTheme.of(context).spacer16),
clickBackgroundColor: TDTheme.of(context).bgColorContainerHover,
cardBorderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
)
),
onClick: (cell) async{
Navigator.pop(context);
String message = '';
if(9 - controller.imgList.length == 1){
bool blockUser = false;
await controller.handleCameraCapture();
} else {
if(controller.imgList.length >= 9){
final TextEditingController _messageController = TextEditingController();
SmartDialog.showToast('超出数量限制,请先删除再尝试上传');
return;
}
await controller.handleCameraCapture();
}
},
),
),
const TDDivider(),
TDCell(
arrow: false,
titleWidget: Center(
child: Text('从相册选择'),
),
onClick: (cell) async{
Navigator.pop(context);
if(9 - controller.imgList.length == 1){
await controller.handleGallerySelection();
} else {
if(controller.imgList.length >= 9){
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PageAppbar(title: "举报中心"),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
top: 6.w,
right: 10.w,
left: 20.w
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"请选择举报的原因",
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(144, 144, 144, 1)
SmartDialog.showToast('超出数量限制,请先删除再尝试上传');
return;
}
await controller.handleMultiGallerySelection();
}
},
),
Expanded(
child: Container(
color: Color(0xFFF3F3F3),
),
),
TDCell(
arrow: false,
titleWidget: Center(
child: Text('取消'),
), ),
onClick: (cell){
Navigator.pop(context);
},
), ),
], ],
), ),
SizedBox(height: 9.w ,),
SizedBox(
height: 32.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"资料作假",
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(51, 51, 51, 1),
fontWeight: FontWeight.w500
),
),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
activeColor: const Color.fromRGBO(117, 98, 249, 1),
side: const BorderSide(color: Colors.grey),
shape: const CircleBorder(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
],
),
);
}),
);
}
@override
Widget build(BuildContext context) {
return GetX<ReportController>(
init: ReportController(id: id),
builder: (controller){
return Scaffold(
appBar: PageAppbar(title: "举报中心"),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
top: 6.w,
right: 10.w,
left: 20.w
), ),
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
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"请选择举报的原因",
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(144, 144, 144, 1)
),
), ),
],
),
SizedBox(height: 9.w ,),
SizedBox(
height: 32.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"资料作假",
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(51, 51, 51, 1),
fontWeight: FontWeight.w500
),
),
Checkbox(
value: controller.checked.value == 1,
onChanged: (value) {
controller.checked.value = 1;
},
activeColor: const Color.fromRGBO(117, 98, 249, 1),
side: const BorderSide(color: Colors.grey),
shape: const CircleBorder(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
],
), ),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
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 == 2,
onChanged: (value) {
controller.checked.value = 2;
},
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
),
),
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 == 3,
onChanged: (value) {
controller.checked.value = 3;
},
activeColor: const Color.fromRGBO(117, 98, 249, 1),
side: const BorderSide(color: Colors.grey),
shape: const CircleBorder(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
],
), ),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
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 == 4,
onChanged: (value) {
controller.checked.value = 4;
},
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
),
),
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 == 5,
onChanged: (value) {
controller.checked.value = 5;
},
activeColor: const Color.fromRGBO(117, 98, 249, 1),
side: const BorderSide(color: Colors.grey),
shape: const CircleBorder(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
],
), ),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
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 == 6,
onChanged: (value) {
controller.checked.value = 6;
},
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
),
),
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,
),
],
), ),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
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: 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
),
SizedBox(height: 29.w ,),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"图片证据(选填)",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1)
),
), ),
),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
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
],
),
SizedBox(height: 13.w ,),
Wrap(
spacing: 10.w,
runSpacing: 10.w,
children: [
...controller.imgList.map((e){
return Stack(
children: [
CachedNetworkImage(
imageUrl: e,
width: 80.w,
height: 80.w,
fit: BoxFit.cover,
),
Positioned(
left: 5.w,
top: 5.w,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: const Color.fromRGBO(0, 0, 0, .3)
),
child: Icon(
Icons.close,
size: 20.w,
),
).onTap((){
controller.imgList.remove(e);
}),
)
],
);
}),
Container(
width: 80.w,
height: 80.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.w)),
border: Border.all(width: 1, color: const Color.fromRGBO(224, 224, 224, 1))
),
child: Center(
child: Icon(
Icons.add,
size: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1),
),
),
).onTap((){
_showAvatarPopup(controller);
})
],
),
SizedBox(height: 15.w ,),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"投诉内容(选填)",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1)
),
), ),
],
),
Container(
padding: EdgeInsets.all(17.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.w)),
border: Border.all(width: 1, color: const Color.fromRGBO(224, 224, 224, 1))
), ),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
},
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(
"其他",
child: TextField(
controller: controller.messageController.value,
minLines: 3, //
maxLines: 3, //
style: TextStyle( style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(51, 51, 51, 1),
fontWeight: FontWeight.w500
fontSize: ScreenUtil().setWidth(12),
height: 1
), ),
),
Checkbox(
value: checked == 1,
onChanged: (value) {
checked = 1;
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 0,
horizontal: 0
),
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: (value){
controller.message.value = value;
}, },
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,
children: [
Text(
"图片证据(选填)",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1)
), ),
), ),
],
),
SizedBox(height: 13.w ,),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: 16.w ,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"同时加入黑名单",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(51, 51, 51, 1),
fontWeight: FontWeight.w500
),
),
TDSwitch(
isOn: controller.blockUser.value,
trackOnColor: const Color.fromRGBO(117, 98, 249, 1),
onChanged: (bool e){
print(e);
controller.blockUser.value = e;
return e;
},
),
],
),
SizedBox(height: 53.w ,),
Container( Container(
width: 80.w,
height: 80.w,
width: 325.w,
height: 45.w,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.w)),
border: Border.all(width: 1, color: const Color.fromRGBO(224, 224, 224, 1))
borderRadius: BorderRadius.all(Radius.circular(45.w)),
color: Color.fromRGBO(117, 98, 249, controller.checked.value != 0 ? 1 : .6)
), ),
child: Center( child: Center(
child: Icon(
Icons.add,
size: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1),
child: Text(
"提交",
style: TextStyle(
fontSize: 18.w,
color: Colors.white,
fontWeight: FontWeight.w500
),
), ),
), ),
)
],
),
SizedBox(height: 15.w ,),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"投诉内容(选填)",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1)
),
),
).onTap((){
controller.sendReport();
})
], ],
), ),
Container(
padding: EdgeInsets.all(17.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.w)),
border: Border.all(width: 1, color: const Color.fromRGBO(224, 224, 224, 1))
),
child: TextField(
controller: _messageController,
minLines: 3, //
maxLines: 3, //
style: TextStyle(
fontSize: ScreenUtil().setWidth(12),
height: 1
),
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 0,
horizontal: 0
),
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: (value){
message = value;
},
),
),
SizedBox(height: 16.w ,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"同时加入黑名单",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(51, 51, 51, 1),
fontWeight: FontWeight.w500
),
),
TDSwitch(
isOn: blockUser,
trackOnColor: const Color.fromRGBO(117, 98, 249, 1),
onChanged: (bool e){
print(e);
blockUser = e;
setState(() {
});
return e;
},
),
],
),
SizedBox(height: 53.w ,),
Container(
width: 325.w,
height: 45.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(45.w)),
color: Color.fromRGBO(117, 98, 249, checked != 0 ? 1 : .6)
),
child: Center(
child: Text(
"提交",
style: TextStyle(
fontSize: 18.w,
color: Colors.white,
fontWeight: FontWeight.w500
),
),
),
)
],
),
), ),
),
),
);
},
); );
} }
} }

734
lib/pages/home/send_timeline.dart

@ -0,0 +1,734 @@
import 'dart:io';
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/send_timeline_controller.dart';
import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:dating_touchme_app/generated/assets.dart';
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:flustars/flustars.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';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class SendTimeline extends StatefulWidget {
const SendTimeline({super.key});
@override
State<SendTimeline> createState() => _SendTimelineState();
}
class _SendTimelineState extends State<SendTimeline> {
String title = "";
String message = '';
final TextEditingController messageController = TextEditingController();
final FocusNode focusNode = FocusNode();
bool isEmojiVisible = false;
List imgList = [];
late final HomeApi _homeApi;
bool isClick = false;
@override
void initState() {
super.initState();
_homeApi = Get.find<HomeApi>();
focusNode.addListener(() {
if (focusNode.hasFocus) {
//
isEmojiVisible = false;
setState(() {
});
}
});
}
@override
void dispose() {
super.dispose();
focusNode.dispose();
}
void _showAvatarPopup(){
Navigator.of(Get.context!).push(
TDSlidePopupRoute(
slideTransitionFrom: SlideTransitionFrom.bottom,
builder: (context) {
return Container(
height: 176,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
),
),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
),
child: TDCell(
arrow: false,
titleWidget: Center(
child: Text('拍照', style: TextStyle(fontSize: 16.w, color: const Color.fromRGBO(51, 51, 51, 1))),
),
style: TDCellStyle(
padding: EdgeInsets.all(TDTheme.of(context).spacer16),
clickBackgroundColor: TDTheme.of(context).bgColorContainerHover,
cardBorderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
)
),
onClick: (cell) async{
Navigator.pop(context);
if(9 - imgList.length == 1){
await handleCameraCapture();
} else {
if(imgList.length >= 9){
SmartDialog.showToast('超出数量限制,请先删除再尝试上传');
return;
}
await handleCameraCapture();
}
},
),
),
const TDDivider(),
TDCell(
arrow: false,
titleWidget: Center(
child: Text('从相册选择'),
),
onClick: (cell) async{
Navigator.pop(context);
if(9 - imgList.length == 1){
await handleGallerySelection();
} else {
if(imgList.length >= 9){
SmartDialog.showToast('超出数量限制,请先删除再尝试上传');
return;
}
await handleMultiGallerySelection();
}
},
),
Expanded(
child: Container(
color: Color(0xFFF3F3F3),
),
),
TDCell(
arrow: false,
titleWidget: Center(
child: Text('取消'),
),
onClick: (cell){
Navigator.pop(context);
},
),
],
),
);
}),
);
}
// -
Future<void> handleCameraCapture() async {
try {
//
final ok = await _ensurePermission(
Permission.camera,
denyToast: '相机权限被拒绝,请在设置中允许访问相机',
);
if (!ok) return;
// /
await _ensurePermission(Permission.microphone, denyToast: '麦克风权限被拒绝');
//
final ImagePicker picker = ImagePicker();
final XFile? photo = await picker.pickImage(source: ImageSource.camera);
if (photo != null) {
await processSelectedImage(File(photo.path));
}
} catch (e) {
print('拍照失败: $e');
//
if (e.toString().contains('permission') || e.toString().contains('权限')) {
SmartDialog.showToast('相机权限被拒绝,请在设置中允许访问相机');
} else if (e.toString().contains('camera') ||
e.toString().contains('相机')) {
SmartDialog.showToast('设备没有可用的相机');
} else {
SmartDialog.showToast('拍照失败,请重试');
}
}
}
Future<void> handleGallerySelection() async {
try {
// /
// final ok = await _ensurePermission(
// Permission.photos,
// // Android photos storage/mediaLibrarypermission_handler
// denyToast: '相册权限被拒绝,请在设置中允许访问相册',
// );
// if (!ok) return;
//
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
await processSelectedImage(File(image.path));
}
} catch (e) {
print('选择图片失败: $e');
//
if (e.toString().contains('permission') || e.toString().contains('权限')) {
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册');
} else {
SmartDialog.showToast('选择图片失败,请重试');
}
}
}
Future<void> handleMultiGallerySelection() async {
try {
// /
// final ok = await _ensurePermission(
// Permission.photos,
// // Android photos storage/mediaLibrarypermission_handler
// denyToast: '相册权限被拒绝,请在设置中允许访问相册',
// );
// if (!ok) return;
//
final ImagePicker picker = ImagePicker();
final List<XFile>? image = await picker.pickMultiImage(limit: 9 - imgList.length);
if (image != null && image.isNotEmpty) {
final futures = image.map((e){
return processSelectedMoreImage(File(e.path));
});
final list = await Future.wait(futures);
imgList.addAll(list);
print(imgList);
SmartDialog.dismiss();
SmartDialog.showToast('上传照片成功');
setState(() {
});
}
} catch (e) {
print('选择图片失败: $e');
//
if (e.toString().contains('permission') || e.toString().contains('权限')) {
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册');
} else {
SmartDialog.showToast('选择图片失败,请重试');
}
}
}
//
Future<bool> _ensurePermission(Permission permission, {String? denyToast}) async {
var status = await permission.status;
if (status.isGranted) return true;
if (status.isDenied || status.isRestricted || status.isLimited) {
status = await permission.request();
if (status.isGranted) return true;
if (denyToast != null) SmartDialog.showToast(denyToast);
return false;
}
if (status.isPermanentlyDenied) {
if (denyToast != null) SmartDialog.showToast('$denyToast,前往系统设置开启');
//
Future.delayed(const Duration(milliseconds: 300), openAppSettings);
return false;
}
return false;
}
//
Future<void> processSelectedImage(File imageFile) async {
try {
//
SmartDialog.showLoading(msg: '上传相册中...');
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}';
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName);
print('上传成功,图片URL: $imageUrl');
imgList.add(imageUrl);
SmartDialog.dismiss();
SmartDialog.showToast('相册上传成功');
setState(() {
});
} catch (e) {
SmartDialog.dismiss();
print('处理图片失败: $e');
SmartDialog.showToast('上传相册失败,请重试');
}
}
//
Future<String> processSelectedMoreImage(File imageFile) async {
try {
//
SmartDialog.showLoading(msg: '上传相册中...');
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}';
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName);
print('上传成功,图片URL: $imageUrl');
return imageUrl;
} catch (e) {
SmartDialog.dismiss();
print('处理图片失败: $e');
SmartDialog.showToast('上传相册失败,请重试');
return "";
}
}
void toggleEmojiPanel() {
isEmojiVisible = !isEmojiVisible;
FocusManager.instance.primaryFocus?.unfocus();
setState(() {
});
}
void handleEmojiSelected(EmojiItem emoji) {
//
final currentText = messageController.text;
final emojiText = '[emoji:${emoji.id}]';
messageController.text = currentText + emojiText;
//
messageController.selection = TextSelection.fromPosition(
TextPosition(offset: messageController.text.length),
);
setState(() {}); //
}
/// +
List<Widget> buildInputContentWidgets() {
final List<Widget> widgets = [];
final text = messageController.value.text;
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;
}
sendTimeLine() async {
try {
if(isClick) return;
isClick = true;
if(messageController.value.text == ""){
SmartDialog.showToast('请填写帖子内容');
return;
}
final response = await _homeApi.userCreatePost({
"content": messageController.value.text,
"mediaUrls": imgList.isNotEmpty ? imgList.join(",") : "",
"topicTags": ""
});
if (response.data.isSuccess) {
SmartDialog.showToast('帖子已发布成功,待审核通过后可见');
Get.back();
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e){
print('帖子发布失败: $e');
SmartDialog.showToast('帖子发布失败');
rethrow;
} finally {
isClick = false;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PageAppbar(title: "", right: Container(
width: 53.w,
height: 26.w,
margin: EdgeInsets.only(right: 17.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.w)),
color: const Color.fromRGBO(117, 98, 249, 1)
),
child: Center(
child: Text(
"发布",
style: TextStyle(
fontSize: 13.w,
color: Colors.white
),
),
),
).onTap((){
sendTimeLine();
}),),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 70.w,
height: 70.w,
margin: EdgeInsets.only(
left: 15.w,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(9.w)),
border: Border.all(width: 1.w, color: const Color.fromRGBO(224, 224, 224, 1))
),
child: Center(
child: Icon(
Icons.add,
size: 35.w,
color: const Color.fromRGBO(224, 224, 224, 1)
),
),
).onTap(() {
_showAvatarPopup();
}),
SizedBox(height: 25.w,),
Container(
margin: EdgeInsets.symmetric(horizontal: 15.w,),
width: 345.w,
height: 1.w,
color: const Color.fromRGBO(224, 224, 224, 1),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 17.w, vertical: 10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Stack(
children: [
TextField(
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: "勇敢表达吧,你的有趣,总有人懂。",
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)),
),
),
onChanged: (value){
setState(() {
});
},
),
if (messageController.text.contains('[emoji:'))
Positioned.fill(
child: IgnorePointer(
child: SingleChildScrollView(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: buildInputContentWidgets(),
),
),
),
),
],
),
),
if(imgList.length == 1) Stack(
children: [
CachedNetworkImage(
imageUrl: imgList[0],
width: 341.w,
height: 341.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 341.w,
height: 341.w,
fit: BoxFit.cover,
),
),
Positioned(
left: 5.w,
top: 5.w,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: const Color.fromRGBO(0, 0, 0, .3)
),
child: Icon(
Icons.close,
size: 20.w,
color: Colors.white,
),
).onTap((){
imgList.clear();
setState(() {
});
}),
)
],
),
if(imgList.length == 2) Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
...imgList.map((e){
return Stack(
children: [
CachedNetworkImage(
imageUrl: e,
width: 165.w,
height: 165.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 165.w,
height: 165.w,
fit: BoxFit.cover,
),
),
Positioned(
left: 5.w,
top: 5.w,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: const Color.fromRGBO(0, 0, 0, .3)
),
child: Icon(
Icons.close,
size: 20.w,
),
).onTap((){
imgList.remove(e);
setState(() {
});
}),
)
],
);
}),
],
),
if(imgList.length > 2) Wrap(
spacing: 13.w,
runSpacing: 13.w,
children: [
...imgList.map((e){
return Stack(
children: [
CachedNetworkImage(
imageUrl: e,
width: 105.w,
height: 105.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 105.w,
height: 105.w,
fit: BoxFit.cover,
),
),
Positioned(
left: 5.w,
top: 5.w,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: const Color.fromRGBO(0, 0, 0, .3)
),
child: Icon(
Icons.close,
size: 20.w,
),
).onTap((){
imgList.remove(e);
setState(() {
});
}),
)
],
);
}),
],
),
],
),
),
),
//
EmojiPanel(
isVisible: isEmojiVisible,
onEmojiSelected: handleEmojiSelected,
),
],
),
);
}
}

636
lib/pages/home/timeline_info.dart

@ -0,0 +1,636 @@
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/timeline_info_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/post_comment_data.dart';
import 'package:dating_touchme_app/pages/home/report_page.dart';
import 'package:dating_touchme_app/pages/home/user_information_page.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/foundation.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 TimelineInfo extends StatelessWidget {
final String id;
const TimelineInfo({super.key, required this.id});
@override
Widget build(BuildContext context) {
return GetX<TimelineInfoController>(
init: TimelineInfoController(id: id),
builder: (controller) {
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) return;
Get.back(result: controller.item.value);
},
child: Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
Get.back(result: controller.item.value);
},
),
backgroundColor: const Color.fromRGBO(255, 255, 255, 1),
surfaceTintColor: const Color.fromRGBO(255, 255, 255, 1),
centerTitle: true,
title: Text(
"详情",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(51, 51, 51, 1)
),
),
actions: [
Container(
margin: EdgeInsets.only(right: 14.w),
child: PopupMenuButton<String>(
tooltip: "",
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
color: Colors.white,
elevation: 8,
offset: Offset(0, 32.w), //
itemBuilder: (context) => [
const PopupMenuItem(value: 'report', child: Text('举报')),
],
onSelected: (v) {
if (v == 'report') {
print("举报");
Get.to(() => ReportPage(id: id,));
}
},
child: Icon(
Icons.keyboard_control,
size: 24.w,
color: const Color.fromRGBO(51, 51, 51, 1),
), //
),
)
],
),
body: Stack(
children: [
if(controller.item.value.id != null && controller.item.value.id != "") EasyRefresh(
controller: controller.listRefreshController,
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.commentList.clear();
await controller.getPostData();
await controller.getCommentData();
controller.listRefreshController.finishRefresh(IndicatorResult.success);
controller.listRefreshController.finishLoad(IndicatorResult.none);
},
//
onLoad: () async {
print('推荐列表上拉加载被触发, hasMore: ');
controller.page.value += 1;
controller.getCommentData();
},
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 10.w
),
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(40.w)),
child: CachedNetworkImage(
imageUrl: controller.item.value.profilePhoto ?? "",
width: 40.w,
height: 40.w,
fit: BoxFit.cover,
),
).onTap((){
Get.to(() => UserInformationPage(miId: controller.item.value.miId ?? ""));
}),
SizedBox(width: 8.w,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.item.value.nickName ?? "",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
Text(
controller.item.value.createTime ?? "",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
fontWeight: FontWeight.w500
),
)
],
)
],
),
],
),
Container(
margin: EdgeInsets.symmetric(vertical: 11.w),
child: !controller.item.value.content!.contains('[emoji:') ? Text(
controller.item.value.content ?? "",
) : Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: controller.buildInputContentWidgets(),
),
),
if(controller.imgList.length == 1) CachedNetworkImage(
imageUrl: controller.imgList[0],
width: 341.w,
height: 341.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 341.w,
height: 341.w,
fit: BoxFit.cover,
),
).onTap((){
TDImageViewer.showImageViewer(context: context, images: controller.imgList);
}),
if(controller.imgList.length == 2) Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
...controller.imgList.map((e){
return CachedNetworkImage(
imageUrl: e,
width: 165.w,
height: 165.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 165.w,
height: 165.w,
fit: BoxFit.cover,
),
).onTap((){
TDImageViewer.showImageViewer(context: context, images: controller.imgList);
});
}),
],
),
if(controller.imgList.length > 2) Wrap(
spacing: 13.w,
runSpacing: 13.w,
children: [
...controller.imgList.map((e){
return CachedNetworkImage(
imageUrl: e,
width: 105.w,
height: 105.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 105.w,
height: 105.w,
fit: BoxFit.cover,
),
).onTap((){
TDImageViewer.showImageViewer(context: context, images: controller.imgList);
});
}),
],
),
SizedBox(height: 15.w,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
children: [
Image.asset(
(controller.item.value.isLiked ?? false) ? Assets.imagesLikeActive : Assets.imagesLikeIcon,
width: 14.w,
height: 12.w,
),
SizedBox(width: 6.w,),
Text(
"${controller.item.value.likeCount ?? 0}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
),
)
],
).onTap((){
controller.likePost();
}),
SizedBox(width: 33.w,),
Row(
children: [
Image.asset(
Assets.imagesCommentIcon,
width: 15.w,
height: 15.w,
),
SizedBox(width: 6.w,),
Text(
"${controller.item.value.commentCount ?? 0}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
),
)
],
).onTap((){
controller.focusNode.value.requestFocus();
// controller.update();
}),
],
),
SizedBox(height: 18.w,),
Text(
"全部评论(${controller.item.value.commentCount ?? 0})",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1),
fontWeight: FontWeight.w500
),
),
SizedBox(height: 20.w,),
...controller.commentList.map((e){
return CommentItem(item: e, controller: controller,);
}),
],
),
),
),
),
// if(controller.showInput.value) Positioned.fill(
// child: Container(
// color: const Color.fromRGBO(0, 0, 0, .4),
// ).onTap((){
// controller.showInput.value = false;
// FocusScope.of(context).unfocus();
//
// }),
// ),
ListenableBuilder(
listenable: controller.focusNode.value, //
builder: (context, child) {
// builder
return Visibility(
visible: controller.focusNode.value.hasFocus,
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
controller.parentId.value = "0";
},
child: Container(
color: const Color.fromRGBO(0, 0, 0, .4),
//
),
),
);
},
),
Positioned(
left: 0,
bottom: 0,
child: Container(
width: 375.w,
height: 60.w,
color: Colors.white,
padding: EdgeInsets.all(10.w),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: const Color.fromRGBO(247, 247, 247, 1),
borderRadius: BorderRadius.all(Radius.circular(40.w))
),
child: TextField(
focusNode: controller.focusNode.value,
controller: controller.messageController.value,
style: TextStyle(
fontSize: ScreenUtil().setWidth(14),
height: 1
),
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 0,
horizontal: 17.w
),
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: (value){
controller.message.value = value;
},
),
),
),
Container(
width: 60.w,
height: 30.w,
margin: EdgeInsets.only(left: 15.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30.w)),
gradient: LinearGradient(
begin: Alignment.centerLeft, // 0%
end: Alignment.centerRight, // 100%
colors: [
Color.fromRGBO(131, 89, 255, 1), //
Color.fromRGBO(77, 127, 231, 1), //
Color.fromRGBO(61, 138, 224, 1), //
],
stops: [0.0, 0.7753, 1.0], // CSS 0%77.53%100%
),
),
child: Center(
child: Text(
"发送",
style: TextStyle(
fontSize: 12.w,
color: Colors.white
),
),
),
).onTap((){
controller.sendComment();
})
],
),
),
)
],
),
),
);
},
);
}
}
class CommentItem extends StatefulWidget {
final Records item;
final TimelineInfoController controller;
const CommentItem({super.key, required this.item, required this.controller});
@override
State<CommentItem> createState() => _CommentItemState();
}
class _CommentItemState extends State<CommentItem> {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20.w),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(40.w)),
child: CachedNetworkImage(
imageUrl: widget.item.profilePhoto ?? "",
width: 40.w,
height: 40.w,
fit: BoxFit.cover,
),
).onTap((){
Get.to(() => UserInformationPage(miId: widget.item.miId ?? ""));
}),
SizedBox(width: 8.w,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.item.nickName ?? "",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1),
),
),
SizedBox(height: 5.w,),
SizedBox(
child: Text(
widget.item.content ?? "",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
),
SizedBox(height: 5.w,),
Text(
"${widget.item.createTime}·回复",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
),
).onTap((){
widget.controller.parentId.value = widget.item.id ?? "0";
widget.controller.focusNode.value.requestFocus();
}),
],
),
)
],
),
),
// Image.asset(
// Assets.imagesLikeIcon,
// width: 14.w,
// )
],
),
Container(
padding: EdgeInsets.only(left: 48.w),
margin: EdgeInsets.only(top: 10.w),
child: Column(
children: [
...widget.item.childPostCommentList?.map((e){
return SecondCommentItem(item: e,);
}) ?? [],
],
),
)
],
),
);
}
}
class SecondCommentItem extends StatefulWidget {
final ChildPostCommentList item;
const SecondCommentItem({super.key, required this.item});
@override
State<SecondCommentItem> createState() => _SecondCommentItemState();
}
class _SecondCommentItemState extends State<SecondCommentItem> {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(40.w)),
child: CachedNetworkImage(
imageUrl: widget.item.profilePhoto ?? "",
width: 40.w,
height: 40.w,
fit: BoxFit.cover,
),
).onTap((){
Get.to(() => UserInformationPage(miId: widget.item.miId ?? ""));
}),
SizedBox(width: 8.w,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.item.nickName ?? "",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1),
),
),
SizedBox(height: 5.w,),
SizedBox(
child: Text(
widget.item.content ?? "",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
),
SizedBox(height: 5.w,),
Text(
"${widget.item.createTime}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
),
),
],
),
)
],
),
),
// Image.asset(
// Assets.imagesLikeIcon,
// width: 14.w,
// )
],
),
);
}
}

365
lib/pages/home/timeline_item.dart

@ -0,0 +1,365 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dating_touchme_app/config/emoji_config.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/post_data.dart';
import 'package:dating_touchme_app/network/home_api.dart';
import 'package:dating_touchme_app/pages/home/report_page.dart';
import 'package:dating_touchme_app/pages/home/timeline_info.dart';
import 'package:dating_touchme_app/pages/home/user_information_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';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class TimelineItem extends StatefulWidget {
final Records item;
const TimelineItem({super.key, required this.item});
@override
State<TimelineItem> createState() => _TimelineItemState();
}
class _TimelineItemState extends State<TimelineItem> {
/// +
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;
}
List<String> imgList = [];
late final HomeApi _homeApi;
@override
void initState() {
super.initState();
_homeApi = Get.find<HomeApi>();
getImgList();
}
getImgList(){
if(widget.item.mediaUrls != null && widget.item.mediaUrls != ""){
imgList = widget.item.mediaUrls!.split(",");
setState(() {
});
}
}
likePost() async {
try {
final response = await _homeApi.userLikePost({
"id": widget.item.id,
"isLiked": !(widget.item.isLiked ?? false),
});
if (response.data.isSuccess) {
if(widget.item.isLiked ?? false){
SmartDialog.showToast('取消点赞成功');
widget.item.likeCount = widget.item.likeCount! - 1;
} else {
SmartDialog.showToast('点赞成功');
widget.item.likeCount = widget.item.likeCount! + 1;
}
widget.item.isLiked = !(widget.item.isLiked ?? false);
setState(() {
});
} else {
//
throw Exception(response.data.message ?? '获取数据失败');
}
} catch(e){
print('帖子发布失败: $e');
SmartDialog.showToast('帖子发布失败');
rethrow;
}
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 15.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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,
),
).onTap((){
Get.to(() => UserInformationPage(miId: widget.item.miId ?? ""));
}),
SizedBox(width: 8.w,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.item.nickName ?? "",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
Text(
widget.item.createTime ?? "",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
fontWeight: FontWeight.w500
),
)
],
)
],
),
PopupMenuButton<String>(
tooltip: "",
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
color: Colors.white,
elevation: 8,
offset: Offset(0, 32.w), //
itemBuilder: (context) => [
const PopupMenuItem(value: 'report', child: Text('举报')),
],
onSelected: (v) {
if (v == 'report') {
print("举报");
Get.to(() => ReportPage(id: widget.item.id ?? "",));
}
},
child: Icon(
Icons.keyboard_control,
size: 15.w,
color: const Color.fromRGBO(51, 51, 51, 1),
), //
),
],
),
Container(
margin: EdgeInsets.symmetric(vertical: 11.w),
child: !widget.item.content!.contains('[emoji:') ? Text(
widget.item.content ?? ""
) : Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: buildInputContentWidgets(),
),
),
if(imgList.length == 1) CachedNetworkImage(
imageUrl: imgList[0],
width: 341.w,
height: 341.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 341.w,
height: 341.w,
fit: BoxFit.cover,
),
).onTap((){
TDImageViewer.showImageViewer(context: context, images: imgList);
}),
if(imgList.length == 2) Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
...imgList.map((e){
return CachedNetworkImage(
imageUrl: e,
width: 165.w,
height: 165.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 165.w,
height: 165.w,
fit: BoxFit.cover,
),
).onTap((){
TDImageViewer.showImageViewer(context: context, images: imgList);
});
}),
],
),
if(imgList.length > 2) Wrap(
spacing: 13.w,
runSpacing: 13.w,
children: [
...imgList.map((e){
return CachedNetworkImage(
imageUrl: e,
width: 105.w,
height: 105.w,
fit: BoxFit.cover,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 105.w,
height: 105.w,
fit: BoxFit.cover,
),
).onTap((){
TDImageViewer.showImageViewer(context: context, images: imgList);
});
}),
],
),
SizedBox(height: 14.w,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
children: [
Image.asset(
(widget.item.isLiked ?? false) ? Assets.imagesLikeActive : Assets.imagesLikeIcon,
width: 14.w,
height: 12.w,
),
SizedBox(width: 6.w,),
Text(
"${widget.item.likeCount ?? 0}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
),
)
],
).onTap((){
likePost();
}),
SizedBox(width: 33.w,),
Row(
children: [
Image.asset(
Assets.imagesCommentIcon,
width: 15.w,
height: 15.w,
),
SizedBox(width: 6.w,),
Text(
"${widget.item.commentCount ?? 0}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
),
)
],
),
],
)
],
),
).onTap((){
Get.to(() => TimelineInfo(id: widget.item.id ?? "",))?.then((e){
widget.item.likeCount = e.likeCount;
widget.item.isLiked = e.isLiked;
widget.item.commentCount = e.commentCount;
setState(() {
});
});
});
}
}

170
lib/pages/home/timeline_page.dart

@ -0,0 +1,170 @@
import 'package:dating_touchme_app/controller/home/timeline_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/home/recommend_window.dart';
import 'package:dating_touchme_app/pages/home/send_timeline.dart';
import 'package:dating_touchme_app/pages/home/timeline_trend.dart';
import 'package:dating_touchme_app/pages/home/timeline_window.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/recommend_tab.dart';
import 'package:dating_touchme_app/pages/home/nearby_tab.dart';
class TimelinePage extends StatefulWidget {
final Function goMessage;
const TimelinePage({super.key, required this.goMessage});
@override
State<TimelinePage> createState() => _TimelinePageState();
}
class _TimelinePageState extends State<TimelinePage>
with AutomaticKeepAliveClientMixin {
@override
void initState() {
super.initState();
// HomeController
if (!Get.isRegistered<TimelineController>()) {
Get.put(TimelineController());
}
// HomeController
if (!Get.isRegistered<HomeController>()) {
Get.put(HomeController());
}
}
@override
Widget build(BuildContext context) {
super.build(context);
return GetBuilder<TimelineController>(
builder: (controller) {
return Stack(
children: [
Positioned.fill(
child: Container(
color: Colors.white,
),
),
// -
Image.asset(
Assets.imagesBgInformation,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
Scaffold(
backgroundColor: Colors.transparent,
appBar: _buildAppBar(controller),
body: Stack(
children: [
Obx(() {
// 使 IndexedStack
return IndexedStack(
index: controller.topTab.value,
children: [
// //
// RecommendWindow(),
//
const TimelineWindow(),
Container()
],
);
}),
Positioned(
bottom: 44.w,
right: 3.w,
child: Image.asset(
Assets.imagesPublish,
width: 60.w,
).onTap((){
Get.to(() => SendTimeline());
}),
)
],
),
),
],
);
},
);
}
PreferredSizeWidget _buildAppBar(TimelineController controller) {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
toolbarHeight: 56,
titleSpacing: 0,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
// _buildTabButton(title: '推荐', index: 0, controller: controller),
// const SizedBox(width: 28),
_buildTabButton(title: '广场', index: 0, controller: controller),
],
),
bottom: const PreferredSize(
preferredSize: Size.fromHeight(4),
child: SizedBox(height: 4),
),
actions: [
Container(
margin: EdgeInsets.only(right: 15),
child: Icon(
Icons.email_outlined,
size: 19,
),
).onTap((){
widget.goMessage();
// Get.to(() => TimelineTrend());
})
],
);
}
Widget _buildTabButton({
required String title,
required int index,
required TimelineController controller,
}) {
final bool selected = controller.topTab.value == index;
return GestureDetector(
onTap: () {
print('Tab $index clicked');
if (controller.topTab.value != index) {
controller.setTopTab(index);
// UI
controller.update();
}
},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
style: TextStyle(
fontSize: selected ? 19 : 17,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
color: selected
? const Color(0xFF333333)
: const Color(0xFF999999),
),
),
const SizedBox(height: 6),
selected
? Image.asset(Assets.imagesTabChangeIcon, width: 32, height: 8)
: const SizedBox(height: 8),
],
),
);
}
@override
bool get wantKeepAlive => true;
}

248
lib/pages/home/timeline_trend.dart

@ -0,0 +1,248 @@
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: 11.w, 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: 18.w,
height: 18.w,
fit: BoxFit.contain,
),
),
);
}
}
lastMatchEnd = match.end;
}
//
if (lastMatchEnd < text.length) {
final textPart = text.substring(lastMatchEnd);
widgets.add(
Text(
textPart,
style: TextStyle(fontSize: 11.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: 12.w,
fontWeight: FontWeight.w500,
color: const Color.fromRGBO(144, 144, 144, 1)
),
),
if(widget.item.operationType == 1)Text(
"赞了你的动态",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
if(widget.item.operationType == 2)Text(
widget.item.postCommentContent ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
SizedBox(height: 15.w,),
Text(
widget.item.createTime ?? "",
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(144, 144, 144, .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(240, 240, 240, 1)
),
child: !widget.item.content!.contains('[emoji:') ? Text(
widget.item.content ?? "",
overflow: TextOverflow.ellipsis,
maxLines: 4,
style: TextStyle(
fontSize: 11.w
),
) : ClipRect(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: buildInputContentWidgets(),
),
),
)
],
).onTap((){
Get.to(() => TimelineInfo(id: widget.item.postId ?? ""));
});
}
}

54
lib/pages/home/timeline_window.dart

@ -0,0 +1,54 @@
import 'package:dating_touchme_app/controller/home/home_controller.dart';
import 'package:dating_touchme_app/pages/home/all_timeline.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 TimelineWindow extends StatefulWidget {
const TimelineWindow({super.key});
@override
State<TimelineWindow> createState() => _TimelineWindowState();
}
class _TimelineWindowState extends State<TimelineWindow> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
late TabController tabController;
final HomeController controller = Get.find<HomeController>();
@override
void initState() {
super.initState();
print(111);
tabController = TabController(length: 2, vsync: this);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: [
Expanded(
child: Obx(() {
// 使 IndexedStack
return IndexedStack(
index: controller.timelineTab.value,
children: const [
//
AllTimeline(),
//
SizedBox(),
],
);
}),
),
],
);
}
@override
bool get wantKeepAlive => true;
}

2
lib/pages/home/user_information_page.dart

@ -427,7 +427,7 @@ class UserInformationPage extends StatelessWidget {
onSelected: (v) { onSelected: (v) {
if (v == 'report') { if (v == 'report') {
print("举报"); print("举报");
Get.to(() => ReportPage());
Get.to(() => ReportPage(id: "",));
} else if (v == 'block') { } else if (v == 'block') {
print("拉黑"); print("拉黑");
} }

Loading…
Cancel
Save