Browse Source

对接广场接口

ios
王子贤 3 months ago
parent
commit
6bda01513a
16 changed files with 1843 additions and 272 deletions
  1. BIN
      assets/images/like_active.png
  2. 176
      lib/controller/home/timeline_controller.dart
  3. 224
      lib/controller/home/timeline_info_controller.dart
  4. 1
      lib/generated/assets.dart
  5. 149
      lib/model/home/post_comment_data.dart
  6. 103
      lib/model/home/post_data.dart
  7. 18
      lib/network/api_urls.dart
  8. 35
      lib/network/home_api.dart
  9. 214
      lib/network/home_api.g.dart
  10. 24
      lib/pages/home/all_timeline.dart
  11. 340
      lib/pages/home/send_timeline.dart
  12. 654
      lib/pages/home/timeline_info.dart
  13. 156
      lib/pages/home/timeline_item.dart
  14. 11
      lib/pages/home/timeline_page.dart
  15. 4
      lib/pages/main/main_page.dart
  16. 6
      pubspec.yaml

BIN
assets/images/like_active.png

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

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

224
lib/controller/home/timeline_info_controller.dart

@ -0,0 +1,224 @@
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;
@override
void onInit() {
super.onInit();
listRefreshController = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
// HomeApi
_homeApi = Get.find<HomeApi>();
getPostData();
getCommentData();
}
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;
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: 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;
}
}

1
lib/generated/assets.dart

@ -131,6 +131,7 @@ class Assets {
static const String imagesImg = 'assets/images/img.png';
static const String imagesInformationBg = 'assets/images/information_bg.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 imagesLiveIcon = 'assets/images/live_icon.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;
}
}

18
lib/network/api_urls.dart

@ -107,6 +107,24 @@ class ApiUrls {
static const String getMarriageList =
'dating-agency-service/user/page/dongwo/marriage-information';
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';
// API端点
static const String listMatchmakerProduct =
'dating-agency-mall/user/page/product/by/matchmaker';

35
lib/network/home_api.dart

@ -1,3 +1,5 @@
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/network/api_urls.dart';
import 'package:dating_touchme_app/network/response_model.dart';
import 'package:retrofit/retrofit.dart';
@ -20,4 +22,37 @@ abstract class HomeApi {
@Query('pageSize') required int pageSize,
@Query('type') required int type,
});
@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,
);
}

214
lib/network/home_api.g.dart

@ -65,6 +65,220 @@ class _HomeApi implements HomeApi {
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;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

24
lib/pages/home/all_timeline.dart

@ -1,3 +1,4 @@
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';
@ -15,7 +16,7 @@ class AllTimeline extends StatefulWidget {
class _AllTimelineState extends State<AllTimeline>
with AutomaticKeepAliveClientMixin {
final HomeController controller = Get.find<HomeController>();
final TimelineController controller = Get.find<TimelineController>();
late final EasyRefreshController _refreshController;
@override
@ -38,7 +39,7 @@ class _AllTimelineState extends State<AllTimeline>
final tabBarHeight = 64.0;
final totalBottomPadding = bottomPadding + tabBarHeight;
return Obx(() {
if (controller.recommendIsLoading.value && controller.recommendFeed.isEmpty) {
if (controller.recommendIsLoading.value && controller.postList.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -76,7 +77,9 @@ class _AllTimelineState extends State<AllTimeline>
onRefresh: () async {
print('推荐列表下拉刷新被触发');
try {
await controller.refreshRecommendData();
controller.page.value = 1;
controller.postList.clear();
await controller.loadPostList();
print( '推荐列表刷新完成, hasMore: $controller.recommendHasMore.value');
_refreshController.finishRefresh();
_refreshController.resetFooter();
@ -89,9 +92,10 @@ class _AllTimelineState extends State<AllTimeline>
onLoad: () async {
print('推荐列表上拉加载被触发, hasMore: $controller.recommendHasMore.value');
try {
await controller.loadRecommendMoreData();
controller.page.value += 1;
await controller.loadPostList();
//
if (controller.recommendHasMore.value) {
if (controller.hasMore.value) {
_refreshController.finishLoad(IndicatorResult.success);
print('推荐列表加载更多成功');
} else {
@ -109,7 +113,7 @@ class _AllTimelineState extends State<AllTimeline>
padding: EdgeInsets.only(left: 12, right: 12),
itemBuilder: (context, index) {
//
if (controller.recommendFeed.isEmpty && index == 0) {
if (controller.postList.isEmpty && index == 0) {
// 使
if (controller.recommendIsLoading.value) {
return Center(
@ -134,18 +138,18 @@ class _AllTimelineState extends State<AllTimeline>
}
}
//
// final item = controller.recommendFeed[index];
return TimelineItem();
final item = controller.postList[index];
return TimelineItem(item: item,);
},
separatorBuilder: (context, index) {
//
if (controller.recommendFeed.isEmpty) {
if (controller.postList.isEmpty) {
return const SizedBox.shrink();
}
return const SizedBox(height: 12);
},
// item
itemCount: 10,
itemCount: controller.postList.length,
)
);
});

340
lib/pages/home/send_timeline.dart

@ -1,12 +1,22 @@
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: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});
@ -25,9 +35,16 @@ class _SendTimelineState extends State<SendTimeline> {
bool isEmojiVisible = false;
List imgList = [];
late final HomeApi _homeApi;
@override
void initState() {
super.initState();
_homeApi = Get.find<HomeApi>();
focusNode.addListener(() {
if (focusNode.hasFocus) {
//
@ -47,6 +64,265 @@ class _SendTimelineState extends State<SendTimeline> {
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) {
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();
@ -125,6 +401,30 @@ class _SendTimelineState extends State<SendTimeline> {
return widgets;
}
sendTimeLine() async {
try {
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;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -145,7 +445,9 @@ class _SendTimelineState extends State<SendTimeline> {
),
),
),
),),
).onTap((){
sendTimeLine();
}),),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 17.w, vertical: 10.w),
child: Column(
@ -208,12 +510,46 @@ class _SendTimelineState extends State<SendTimeline> {
],
),
),
if(imgList.length == 1) CachedNetworkImage(
imageUrl: imgList[0],
width: 341.w,
height: 341.w,
),
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,
);
}),
],
),
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,
);
}),
],
),
Row(
children: [
Image.asset(
Assets.imagesImg,
width: 20.w,
),
).onTap(() {
_showAvatarPopup();
}),
SizedBox(width: 25.w,),
Image.asset(
Assets.imagesEmoji,

654
lib/pages/home/timeline_info.dart

@ -1,258 +1,440 @@
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/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class TimelineInfo extends StatefulWidget {
const TimelineInfo({super.key});
class TimelineInfo extends StatelessWidget {
final String id;
const TimelineInfo({super.key, required this.id});
@override
State<TimelineInfo> createState() => _TimelineInfoState();
}
class _TimelineInfoState extends State<TimelineInfo> {
Widget build(BuildContext context) {
return GetX<TimelineInfoController>(
init: TimelineInfoController(id: id),
builder: (controller) {
return Scaffold(
appBar: PageAppbar(title: "详情"),
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
),
bool showInput = false;
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 ?? "", userId: controller.item.value.userId ?? "",));
}),
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
),
)
],
)
],
),
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());
}
},
child: Icon(
Icons.keyboard_control,
size: 15.w,
color: const Color.fromRGBO(51, 51, 51, 1),
), //
),
],
),
Container(
margin: EdgeInsets.symmetric(vertical: 11.w),
child: !controller.item.value.content!.contains('[emoji:') ? Text(
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PageAppbar(title: "详情"),
body: Stack(
children: [
SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 10.w
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Image.asset(
Assets.imagesUserAvatar,
width: 40.w,
height: 40.w,
controller.item.value.content ?? "",
) : Wrap(
children: controller.buildInputContentWidgets(),
),
SizedBox(width: 8.w,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"刘美玲",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
if(controller.imgList.length == 1) CachedNetworkImage(
imageUrl: controller.imgList[0],
width: 341.w,
height: 341.w,
),
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,
);
}),
],
),
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,
);
}),
],
),
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,
),
),
Text(
"15:16",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
fontWeight: FontWeight.w500
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,
),
)
],
)
],
),
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("举报");
}
},
child: Icon(
Icons.keyboard_control,
size: 15.w,
color: const Color.fromRGBO(51, 51, 51, 1),
), //
),
],
),
Container(
margin: EdgeInsets.symmetric(vertical: 11.w),
child: Text(
"你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。"
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.showInput.value = true;
}),
],
),
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,);
}),
],
),
),
Image.asset(
Assets.imagesRoseBanner,
width: 343.w,
),
SizedBox(height: 15.w,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
),
),
if(controller.showInput.value) Positioned.fill(
child: Container(
color: const Color.fromRGBO(0, 0, 0, .4),
).onTap((){
controller.showInput.value = false;
}),
),
if(controller.showInput.value) Positioned(
left: 0,
bottom: 0,
child: Container(
width: 375.w,
height: 60.w,
color: Colors.white,
padding: EdgeInsets.all(10.w),
child: Row(
children: [
Row(
children: [
Image.asset(
Assets.imagesLikeIcon,
width: 14.w,
height: 12.w,
Expanded(
child: Container(
decoration: BoxDecoration(
color: const Color.fromRGBO(247, 247, 247, 1),
borderRadius: BorderRadius.all(Radius.circular(40.w))
),
SizedBox(width: 6.w,),
Text(
"47",
child: TextField(
controller: controller.messageController.value,
autofocus: true,
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
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;
},
),
),
),
SizedBox(width: 33.w,),
Row(
children: [
Image.asset(
Assets.imagesCommentIcon,
width: 15.w,
height: 15.w,
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%
),
SizedBox(width: 6.w,),
Text(
"23",
),
child: Center(
child: Text(
"发送",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
fontSize: 12.w,
color: Colors.white
),
)
],
),
),
).onTap((){
showInput = true;
setState(() {
});
}),
controller.sendComment();
})
],
),
SizedBox(height: 18.w,),
Text(
"全部评论(23)",
style: TextStyle(
fontSize: 13.w,
color: const Color.fromRGBO(144, 144, 144, 1),
fontWeight: FontWeight.w500
),
),
SizedBox(height: 20.w,),
CommentItem(),
CommentItem(),
CommentItem(),
CommentItem(),
CommentItem(),
CommentItem(),
CommentItem(),
],
),
),
),
)
],
),
if(showInput) Positioned.fill(
child: Container(
color: const Color.fromRGBO(0, 0, 0, .4),
).onTap((){
showInput = false;
setState(() {
);
},
);
}
}
});
}),
),
if(showInput) 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(
// controller: _codeController,
autofocus: true,
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){
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,
),
),
),
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), //
).onTap((){
Get.to(() => UserInformationPage(miId: widget.item.miId ?? "", userId: widget.item.userId ?? "",));
}),
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.showInput.value = true;
}),
],
stops: [0.0, 0.7753, 1.0], // CSS 0%77.53%100%
),
),
child: Center(
child: Text(
"发送",
style: TextStyle(
fontSize: 12.w,
color: Colors.white
),
),
),
)
],
)
],
),
),
// 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,);
}) ?? [],
],
),
)
],
@ -261,14 +443,15 @@ class _TimelineInfoState extends State<TimelineInfo> {
}
}
class CommentItem extends StatefulWidget {
const CommentItem({super.key});
class SecondCommentItem extends StatefulWidget {
final ChildPostCommentList item;
const SecondCommentItem({super.key, required this.item});
@override
State<CommentItem> createState() => _CommentItemState();
State<SecondCommentItem> createState() => _SecondCommentItemState();
}
class _CommentItemState extends State<CommentItem> {
class _SecondCommentItemState extends State<SecondCommentItem> {
@override
Widget build(BuildContext context) {
return Container(
@ -280,18 +463,24 @@ class _CommentItemState extends State<CommentItem> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
Assets.imagesUserAvatar,
width: 40.w,
height: 40.w,
),
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 ?? "", userId: widget.item.userId ?? "",));
}),
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),
@ -300,7 +489,7 @@ class _CommentItemState extends State<CommentItem> {
SizedBox(height: 5.w,),
SizedBox(
child: Text(
"看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!",
widget.item.content ?? "",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
@ -309,7 +498,7 @@ class _CommentItemState extends State<CommentItem> {
),
SizedBox(height: 5.w,),
Text(
"15:16·回复",
"${widget.item.createTime}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
@ -330,4 +519,3 @@ class _CommentItemState extends State<CommentItem> {
);
}
}

156
lib/pages/home/timeline_item.dart

@ -1,47 +1,137 @@
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/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:get/get.dart';
class TimelineItem extends StatefulWidget {
const TimelineItem({super.key});
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: 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;
}
List<String> imgList = [];
@override
void initState() {
super.initState();
getImgList();
}
getImgList(){
if(widget.item.mediaUrls != null && widget.item.mediaUrls != ""){
imgList = widget.item.mediaUrls!.split(",");
setState(() {
});
}
}
@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: [
Image.asset(
Assets.imagesUserAvatar,
width: 40.w,
height: 40.w,
),
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 ?? "", userId: widget.item.userId ?? "",));
}),
SizedBox(width: 8.w,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"刘美玲",
widget.item.nickName ?? "",
style: TextStyle(
fontSize: 13.w,
fontWeight: FontWeight.w500
),
),
Text(
"15:16",
widget.item.createTime ?? "",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, .6),
@ -66,6 +156,7 @@ class _TimelineItemState extends State<TimelineItem> {
onSelected: (v) {
if (v == 'report') {
print("举报");
Get.to(() => ReportPage());
}
},
child: Icon(
@ -79,13 +170,44 @@ class _TimelineItemState extends State<TimelineItem> {
),
Container(
margin: EdgeInsets.symmetric(vertical: 11.w),
child: Text(
"你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。"
child: !widget.item.content!.contains('[emoji:') ? Text(
widget.item.content ?? ""
) : Wrap(
children: buildInputContentWidgets(),
),
),
Image.asset(
Assets.imagesRoseBanner,
width: 375.w - 24,
if(imgList.length == 1) CachedNetworkImage(
imageUrl: imgList[0],
width: 341.w,
height: 341.w,
),
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,
);
}),
],
),
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,
);
}),
],
),
SizedBox(height: 14.w,),
Row(
@ -94,13 +216,13 @@ class _TimelineItemState extends State<TimelineItem> {
Row(
children: [
Image.asset(
Assets.imagesLikeIcon,
(widget.item.isLiked ?? false) ? Assets.imagesLikeActive : Assets.imagesLikeIcon,
width: 14.w,
height: 12.w,
),
SizedBox(width: 6.w,),
Text(
"47",
"${widget.item.likeCount ?? 0}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
@ -118,7 +240,7 @@ class _TimelineItemState extends State<TimelineItem> {
),
SizedBox(width: 6.w,),
Text(
"23",
"${widget.item.commentCount ?? 0}",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(144, 144, 144, .6)
@ -131,7 +253,7 @@ class _TimelineItemState extends State<TimelineItem> {
],
),
).onTap((){
Get.to(() => TimelineInfo());
Get.to(() => TimelineInfo(id: widget.item.id ?? "",));
});
}
}

11
lib/pages/home/timeline_page.dart

@ -1,3 +1,4 @@
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';
@ -23,15 +24,15 @@ class _TimelinePageState extends State<TimelinePage>
void initState() {
super.initState();
// HomeController
if (!Get.isRegistered<HomeController>()) {
Get.put(HomeController());
if (!Get.isRegistered<TimelineController>()) {
Get.put(TimelineController());
}
}
@override
Widget build(BuildContext context) {
super.build(context);
return GetBuilder<HomeController>(
return GetBuilder<TimelineController>(
builder: (controller) {
return Stack(
children: [
@ -78,7 +79,7 @@ class _TimelinePageState extends State<TimelinePage>
);
}
PreferredSizeWidget _buildAppBar(HomeController controller) {
PreferredSizeWidget _buildAppBar(TimelineController controller) {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
@ -104,7 +105,7 @@ class _TimelinePageState extends State<TimelinePage>
Widget _buildTabButton({
required String title,
required int index,
required HomeController controller,
required TimelineController controller,
}) {
final bool selected = controller.topTab.value == index;
return GestureDetector(

4
lib/pages/main/main_page.dart

@ -103,8 +103,8 @@ class _MainPageState extends State<MainPage> {
navigationTabs: [
tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0),
tabItem('广场', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1),
tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 1),
tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 2),
tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 2),
tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 3),
]
),

6
pubspec.yaml

@ -65,7 +65,7 @@ dependencies:
audioplayers: ^6.5.1
video_thumbnail: ^0.5.3 # 视频缩略图生成
fluwx: ^5.7.5
# # tobias: ^5.3.1
# # tobias: ^5.3.1
agora_rtc_engine: ^6.5.3
agora_rtm: ^2.2.5
agora_token_generator: ^1.0.0
@ -78,7 +78,7 @@ dependencies:
pinput: ^6.0.1
im_flutter_sdk: 4.15.2
webview_flutter: ^4.13.0
dev_dependencies:
flutter_test:
sdk: flutter
@ -143,4 +143,4 @@ flutter_launcher_icons:
ios: true
image_path: "assets/images/app_logo.jpg"
min_sdk_android: 21
remove_alpha_ios: true
remove_alpha_ios: true
Loading…
Cancel
Save