diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4..f3307eb 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..bf31e45 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-ldpi/ic_launcher.png b/android/app/src/main/res/mipmap-ldpi/ic_launcher.png new file mode 100644 index 0000000..c1f161b Binary files /dev/null and b/android/app/src/main/res/mipmap-ldpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png new file mode 100644 index 0000000..9712873 Binary files /dev/null and b/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b7..f6d0690 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..f86798a Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391..e673b91 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b9477d9 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d..bfe7705 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..c2df945 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372e..f6ead7e 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d5e3831 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/lib/api/moment.dart b/lib/api/moment.dart new file mode 100644 index 0000000..d8d2127 --- /dev/null +++ b/lib/api/moment.dart @@ -0,0 +1,34 @@ +import 'package:food_hub_app/models/moment.dart'; +import 'package:food_hub_app/utils/http_util.dart'; +import 'package:food_hub_app/utils/index.dart'; + +Future addMomentApi(Moment moment) { + return HttpUtil().post("/moment", data: moment); +} + +Future updateMomentApi(int id, Moment moment) { + return HttpUtil().put("/moment/$id", data: moment); +} + +Future deleteMomentApi(int id) { + return HttpUtil().delete("/moment/$id"); +} + +Future> queryMomentListApi() { + return HttpUtil().get>( + "/moment", + converter: (data) => convertListResponse(data, Moment.fromJson), + ); +} + +Future addMomentCommentApi(int id, String content) { + return HttpUtil().post("/moment/$id/comment", data: content); +} + +Future addMomentLikeApi(int id) { + return HttpUtil().post("/moment/$id/like"); +} + +Future deleteMomentLikeApi(int id) { + return HttpUtil().delete("/moment/$id/like"); +} diff --git a/lib/models/moment.dart b/lib/models/moment.dart new file mode 100644 index 0000000..427e386 --- /dev/null +++ b/lib/models/moment.dart @@ -0,0 +1,53 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'moment.g.dart'; + +@JsonSerializable() +class Comment { + final int id; + final String username; + final String avatar; + final String content; + final String date; + + const Comment({ + required this.id, + required this.username, + required this.avatar, + required this.content, + required this.date, + }); + + factory Comment.fromJson(Map json) => _$CommentFromJson(json); + + Map toJson() => _$CommentToJson(this); +} + +@JsonSerializable() +class Moment { + final int? id; + final int? userId; + final String? username; + final String? avatar; + final String content; + final List imageList; + final String? date; + final List? likeList; + final List? commentList; + + Moment({ + this.id, + this.userId, + this.username, + this.avatar, + required this.content, + required this.imageList, + this.date, + this.likeList, + this.commentList, + }); + + factory Moment.fromJson(Map json) => _$MomentFromJson(json); + + Map toJson() => _$MomentToJson(this); +} diff --git a/lib/models/moment.g.dart b/lib/models/moment.g.dart new file mode 100644 index 0000000..cd6554c --- /dev/null +++ b/lib/models/moment.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'moment.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Comment _$CommentFromJson(Map json) => Comment( + id: (json['id'] as num).toInt(), + username: json['username'] as String, + avatar: json['avatar'] as String, + content: json['content'] as String, + date: json['date'] as String, +); + +Map _$CommentToJson(Comment instance) => { + 'id': instance.id, + 'username': instance.username, + 'avatar': instance.avatar, + 'content': instance.content, + 'date': instance.date, +}; + +Moment _$MomentFromJson(Map json) => Moment( + id: (json['id'] as num?)?.toInt(), + userId: (json['userId'] as num?)?.toInt(), + username: json['username'] as String?, + avatar: json['avatar'] as String?, + content: json['content'] as String, + imageList: + (json['imageList'] as List).map((e) => e as String).toList(), + date: json['date'] as String?, + likeList: + (json['likeList'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), + commentList: + (json['commentList'] as List?) + ?.map((e) => Comment.fromJson(e as Map)) + .toList(), +); + +Map _$MomentToJson(Moment instance) => { + 'id': instance.id, + 'userId': instance.userId, + 'username': instance.username, + 'avatar': instance.avatar, + 'content': instance.content, + 'imageList': instance.imageList, + 'date': instance.date, + 'likeList': instance.likeList, + 'commentList': instance.commentList, +}; diff --git a/lib/views/moment.dart b/lib/views/moment.dart index 4d3086c..f55145f 100644 --- a/lib/views/moment.dart +++ b/lib/views/moment.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:food_hub_app/api/moment.dart'; +import 'package:food_hub_app/models/moment.dart'; +import 'package:food_hub_app/widgets/moment/card.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; class MomentPage extends StatefulWidget { const MomentPage({super.key}); @@ -7,9 +11,35 @@ class MomentPage extends StatefulWidget { State createState() => _MomentPage(); } -class _MomentPage extends State{ +class _MomentPage extends State { + List momentList = []; + + @override + void initState() { + super.initState(); + refreshRecipeList(); + } + + Future refreshRecipeList() async { + final result = await queryMomentListApi(); + + setState(() { + momentList = result; + }); + } + + @override Widget build(BuildContext context) { - return const Center(child: Text("朋友圈")); + if (momentList.isEmpty) { + return const TDEmpty(type: TDEmptyType.plain, emptyText: '暂无数据'); + } else { + return ListView.builder( + itemCount: momentList.length, + itemBuilder: (context, index) { + return MomentCard(moment: momentList[index]); + } + ); + } } } diff --git a/lib/views/record.dart b/lib/views/record.dart index 055d55a..108e739 100644 --- a/lib/views/record.dart +++ b/lib/views/record.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:food_hub_app/widgets/recipe/recipe_calendar.dart'; -import 'package:food_hub_app/widgets/recipe/recipe_list.dart'; -import 'package:food_hub_app/widgets/recipe/recipe_timeline.dart'; +import 'package:food_hub_app/widgets/recipe/calendar.dart'; +import 'package:food_hub_app/widgets/recipe/list.dart'; +import 'package:food_hub_app/widgets/recipe/timeline.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; diff --git a/lib/widgets/moment/card.dart b/lib/widgets/moment/card.dart new file mode 100644 index 0000000..aff6975 --- /dev/null +++ b/lib/widgets/moment/card.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:food_hub_app/config/app_config.dart'; +import 'package:food_hub_app/models/moment.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +class MomentCard extends StatelessWidget { + final Moment moment; + + const MomentCard({super.key, required this.moment}); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: Padding( + padding: const EdgeInsets.all(10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildAvatar(), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildNickname(), + const SizedBox(height: 5), + _buildContent(), + if (moment.imageList.isNotEmpty) ...[ + const SizedBox(height: 8), + _buildPostImages(), + ], + const SizedBox(height: 10), + _buildTime(), + const SizedBox(height: 5), + _buildActionButtons(), + ], + ), + ), + ], + ), + ), + ); + } + + // 构建头像组件 + Widget _buildAvatar() { + return TDAvatar( + size: TDAvatarSize.medium, + type: TDAvatarType.normal, + fit: BoxFit.contain, + avatarUrl: '${AppConfig.baseApiUrl}/${moment.avatar}'); + // return SizedBox( + // width: 40, + // height: 40, + // child: ClipRRect( + // borderRadius: BorderRadius.circular(25), + // child: Image.network( + // '${AppConfig.baseApiUrl}/${moment.avatar}', + // fit: BoxFit.contain, + // errorBuilder: (context, error, stackTrace) { + // return Container( + // color: Colors.grey[200], + // child: const Icon(Icons.person, color: Colors.grey), + // ); + // }, + // ), + // ), + // ); + } + + // 构建昵称组件 + Widget _buildNickname() { + return Text( + moment.username ?? "", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ); + } + + // 构建内容组件 + Widget _buildContent() { + return Text( + moment.content, + style: const TextStyle(fontSize: 15, color: Colors.black87, height: 1.3), + ); + } + + // 构建朋友圈图片列表(网格布局) + Widget _buildPostImages() { + // 图片数量 + final imageCount = moment.imageList.length; + + // 计算网格布局 + int crossAxisCount; + if (imageCount == 1) { + crossAxisCount = 1; // 单张图片 + } else if (imageCount == 2 || imageCount == 4) { + crossAxisCount = 2; // 2张或4张图片用2列 + } else { + crossAxisCount = 3; // 其他数量用3列 + } + + // 计算图片尺寸 + double itemAspectRatio = 1.0; // 默认正方形 + if (imageCount == 1) { + // 单张图片使用宽屏比例 + itemAspectRatio = 1.5; + } + + return GridView.count( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisCount: crossAxisCount, + crossAxisSpacing: 4, + mainAxisSpacing: 4, + childAspectRatio: itemAspectRatio, + children: List.generate(imageCount, (index) { + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + '${AppConfig.baseApiUrl}/${moment.imageList[index]}', + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey[200], + child: const Icon(Icons.image, color: Colors.grey, size: 30), + ); + }, + ), + ); + }), + ); + } + + // 构建时间组件 + Widget _buildTime() { + return Text( + moment.date ?? "", + style: TextStyle(fontSize: 12, color: Colors.grey[500]), + ); + } + + // 构建点赞和评论按钮 + Widget _buildActionButtons() { + return Row( + children: [ + SizedBox( + width: 42, + height: 36, + child: Stack( + alignment: Alignment.bottomLeft, + children: [ + Icon(Icons.thumb_up_alt_outlined), + Positioned( + left: 20, + bottom: 15, + child: TDBadge( + TDBadgeType.message, + count: (moment.likeList?.length ?? 0).toString(), + ), + ), + ], + ), + ), + SizedBox( + width: 42, + height: 36, + child: Stack( + alignment: Alignment.bottomLeft, + children: [ + Icon(Icons.comment_outlined), + Positioned( + left: 20, + bottom: 15, + child: TDBadge( + TDBadgeType.message, + count: (moment.commentList?.length ?? 0).toString(), + ), + ), + ], + ), + ), + // _buildActionButton( + // icon: Icons.thumb_up_alt_outlined, + // count: moment.likeList?.length ?? 0, + // ), + // const SizedBox(width: 20), + // _buildActionButton( + // icon: Icons.comment_outlined, + // count: moment.commentList?.length ?? 0, + // ), + ], + ); + } + + // 构建带数字标记的动作按钮 + Widget _buildActionButton({required IconData icon, required int count}) { + return Row( + children: [ + Icon(icon, color: Colors.grey[500], size: 18), + const SizedBox(width: 4), + if (count > 0) + Text( + count.toString(), + style: TextStyle(fontSize: 12, color: Colors.grey[500]), + ), + ], + ); + } +} diff --git a/lib/widgets/recipe/recipe_calendar.dart b/lib/widgets/recipe/calendar.dart similarity index 100% rename from lib/widgets/recipe/recipe_calendar.dart rename to lib/widgets/recipe/calendar.dart diff --git a/lib/widgets/recipe/recipe_card.dart b/lib/widgets/recipe/card.dart similarity index 100% rename from lib/widgets/recipe/recipe_card.dart rename to lib/widgets/recipe/card.dart diff --git a/lib/widgets/recipe/recipe_list.dart b/lib/widgets/recipe/list.dart similarity index 94% rename from lib/widgets/recipe/recipe_list.dart rename to lib/widgets/recipe/list.dart index d0f06ef..79bc995 100644 --- a/lib/widgets/recipe/recipe_list.dart +++ b/lib/widgets/recipe/list.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:food_hub_app/api/recipe.dart'; import 'package:food_hub_app/models/recipe.dart'; -import 'package:food_hub_app/widgets/recipe/recipe_card.dart'; +import 'package:food_hub_app/widgets/recipe/card.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; class RecipeList extends StatefulWidget { diff --git a/lib/widgets/recipe/recipe_timeline.dart b/lib/widgets/recipe/timeline.dart similarity index 100% rename from lib/widgets/recipe/recipe_timeline.dart rename to lib/widgets/recipe/timeline.dart