diff --git a/lib/pages/my/vloger.dart b/lib/pages/my/vloger.dart index 450a55a..6f42d44 100644 --- a/lib/pages/my/vloger.dart +++ b/lib/pages/my/vloger.dart @@ -195,7 +195,14 @@ class MyPageState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - return Scaffold( + return PopScope( + canPop: true, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (didPop) { + print('User navigated back'); + } + }, + child:Scaffold( backgroundColor: const Color(0xFFFAF6F9), body: Obx(() { return NestedScrollViewPlus( @@ -211,6 +218,16 @@ class MyPageState extends State with SingleTickerProviderStateMixin { collapsedHeight: 120.0, pinned: true, stretch: true, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Get.back(result: { + 'returnTo': '/', + 'vlogerId': args['memberId'], + 'followStatus': (followed.value == 1 || followed.value == 3)?true:false, + }); + }, + ), onStretchTrigger: () async { logger.i('触发 stretch 拉伸'); // 加载刷新逻辑 @@ -283,6 +300,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { ), ); }), + ), ); } diff --git a/lib/pages/video/module/recommend.dart b/lib/pages/video/module/recommend.dart index c89200f..096968a 100644 --- a/lib/pages/video/module/recommend.dart +++ b/lib/pages/video/module/recommend.dart @@ -10,6 +10,7 @@ import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_core.dart'; import 'package:loopin/IM/im_message.dart'; +import 'package:loopin/IM/im_service.dart' hide logger; import 'package:loopin/api/video_api.dart'; import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/network_or_asset_image.dart'; @@ -46,9 +47,14 @@ class RecommendModule extends StatefulWidget { State createState() => _RecommendModuleState(); } class CommentBottomSheet extends StatefulWidget { - final int videoId; + final String videoId; + final Function(int) onCommentCountChanged; // 新增回调函数 - const CommentBottomSheet({super.key, required this.videoId}); + const CommentBottomSheet({ + super.key, + required this.videoId, + required this.onCommentCountChanged, // 接收回调 + }); @override _CommentBottomSheetState createState() => _CommentBottomSheetState(); @@ -61,9 +67,15 @@ class _CommentBottomSheetState extends State { bool isLoadingMoreComments = false; bool hasMoreComments = true; List> commentList = []; - int replyingCommentId = 0; + String replyingCommentId = ''; String replyingCommentUser = ''; + // 新增:子评论相关状态 + Map expandedReplies = {}; // 存储已展开的回复 {commentId: true/false} + Map>> replyData = {}; // 存储子评论数据 {commentId: [replies]} + Map replyPage = {}; // 子评论分页 + Map isLoadingReplies = {}; // 子评论加载状态 + @override void initState() { super.initState(); @@ -83,7 +95,7 @@ class _CommentBottomSheetState extends State { hasMoreComments = true; commentList.clear(); } - + logger.d('入参vlogId-------------------->: ${widget.videoId}'); try { final res = await Http.post(VideoApi.videoCommentList, data: { 'vlogId': widget.videoId, @@ -91,7 +103,7 @@ class _CommentBottomSheetState extends State { 'size': commentPageSize, }); - logger.d('评论接口返回: ${json.encode(res)}'); + logger.d('评论接口列表返回: ${json.encode(res)}'); if (res['code'] == 200 && res['data'] != null) { final data = res['data']; @@ -108,7 +120,7 @@ class _CommentBottomSheetState extends State { hasMoreComments = commentList.length < total; commentPage++; }); - + widget.onCommentCountChanged(total); logger.d('成功加载 ${newComments.length} 条评论,总共 $total 条'); } } catch (e) { @@ -120,32 +132,91 @@ class _CommentBottomSheetState extends State { } } - Future postComment(String content, {int parentCommentId = 0}) async { - try { - final res = await Http.post(VideoApi.doVideoComment, data: { - 'vlogId': widget.videoId, - 'content': content, - 'fatherCommentId': parentCommentId, - }); +Future postComment(String content, {String parentCommentId = ''}) async { + try { + final res = await Http.post(VideoApi.doVideoComment, data: { + 'vlogId': widget.videoId, + 'content': content, + 'fatherCommentId': parentCommentId.isNotEmpty ? parentCommentId : null, + }); - if (res['code'] == 0) { - // 评论成功,重新加载评论列表 + if (res['code'] == 200) { + // 如果是回复,刷新对应的子评论 + if (parentCommentId.isNotEmpty) { + fetchReplies(parentCommentId, false); + // 更新主评论的子评论数量 + setState(() { + final comment = commentList.firstWhere( + (c) => c['id'] == parentCommentId, + orElse: () => {} + ); + if (comment != null && comment.isNotEmpty) { + comment['childCount'] = (comment['childCount'] ?? 0) + 1; + } + }); + } else { + // 如果是新评论,刷新整个列表 fetchComments(false); - MyToast().tip( - title: '评论成功', - position: 'center', - type: 'success', - ); } - } catch (e) { - logger.i('发布评论失败: $e'); + widget.onCommentCountChanged(commentList.length + 1); MyToast().tip( - title: '评论失败', + title: '评论成功', position: 'center', - type: 'error', + type: 'success', ); } + } catch (e) { + logger.i('发布评论失败: $e'); + MyToast().tip( + title: '评论失败', + position: 'center', + type: 'error', + ); } +} + + // 获取二级评论,复用一级评论的接口,修改传参 + Future fetchReplies(String commentId, bool loadMore) async { + if (isLoadingReplies[commentId] == true && !loadMore) return; + + setState(() { + isLoadingReplies[commentId] = true; + }); + + if (!loadMore) { + replyPage[commentId] = 1; + replyData[commentId] = []; + } + + try { + final res = await Http.post(VideoApi.videoCommentList, data: { + 'fatherCommentId': commentId, + 'current': replyPage[commentId], + 'size': commentPageSize, + }); + + if (res['code'] == 200 && res['data'] != null) { + final data = res['data']; + final List> newReplies = + List>.from(data['records'] ?? []); + + setState(() { + if (loadMore) { + replyData[commentId]!.addAll(newReplies); + } else { + replyData[commentId] = newReplies; + } + replyPage[commentId] = replyPage[commentId]! + 1; + }); + } + } catch (e) { + logger.e('获取子评论异常: $e'); + } finally { + setState(() { + isLoadingReplies[commentId] = false; + }); + } +} @override Widget build(BuildContext context) { @@ -227,109 +298,200 @@ class _CommentBottomSheetState extends State { } final comment = commentList[index]; - return ListTile( - isThreeLine: true, - leading: ClipRRect( - borderRadius: BorderRadius.circular(50.0), - child: NetworkOrAssetImage( - imageUrl: comment['commentUserFace'] ?? 'assets/images/avatar/default.png', - width: 30.0, - height: 30.0, - fit: BoxFit.cover, - ), - ), - title: Row( - children: [ - Expanded( - child: Text( - comment['commentUserNickname'] ?? '未知用户', - style: TextStyle( - color: Colors.grey, - fontSize: 12.0, - ), + final hasReplies = comment['childCount'] > 0; + final isExpanded = expandedReplies[comment['id']] == true; + final replies = replyData[comment['id']] ?? []; + + return Column( + children: [ + // 主评论 + ListTile( + isThreeLine: true, + leading: ClipRRect( + borderRadius: BorderRadius.circular(50.0), + child: NetworkOrAssetImage( + imageUrl: comment['commentUserFace'] ?? 'assets/images/avatar/default.png', + width: 30.0, + height: 30.0, + fit: BoxFit.cover, ), ), - SizedBox(width: 20.0), - GestureDetector( - onTap: () { - setState(() { - replyingCommentId = comment['id']; - replyingCommentUser = comment['commentUserNickname'] ?? '未知用户'; - }); - }, - child: Icon( - Icons.reply, - color: Colors.black54, - size: 16.0, - ), - ), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.symmetric(vertical: 5.0), - child: Text( - comment['content'] ?? '', - style: TextStyle( - fontSize: 14.0, - ), - ), - ), - if (comment['childCount'] > 0) - GestureDetector( - onTap: () { - setState(() { - replyingCommentId = comment['id']; - replyingCommentUser = comment['commentUserNickname'] ?? '未知用户'; - }); - }, - child: Container( - margin: EdgeInsets.only(right: 15.0), - padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 3.0), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(20.0), - ), - child: Row(children: [ - Text( - '${comment['childCount']}回复', - style: TextStyle(fontSize: 12.0), + title: Row( + children: [ + Expanded( + child: Text( + comment['commentUserNickname'] ?? '未知用户', + style: TextStyle( + color: Colors.grey, + fontSize: 12.0, ), - Icon( - Icons.arrow_forward_ios, - size: 10.0, - ) - ]), + ), ), - ), - Text( - '${comment['createTime']?.toString().substring(0, 10) ?? ''}', - style: TextStyle(color: Colors.grey, fontSize: 12.0), + SizedBox(width: 20.0), + GestureDetector( + onTap: () { + setState(() { + replyingCommentId = comment['id']; + replyingCommentUser = comment['commentUserNickname'] ?? '未知用户'; + }); + }, + child: Icon( + Icons.reply, + color: Colors.black54, + size: 16.0, + ), + ), + ], ), - ], - ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.symmetric(vertical: 5.0), + child: Text( + comment['content'] ?? '', + style: TextStyle( + fontSize: 14.0, + ), + ), + ), + if (hasReplies) + GestureDetector( + onTap: () { + setState(() { + expandedReplies[comment['id']] = !isExpanded; + if (expandedReplies[comment['id']] == true && + (replyData[comment['id']] == null || replyData[comment['id']]!.isEmpty)) { + fetchReplies(comment['id'], false); + } + }); + }, + child: Container( + margin: EdgeInsets.only(right: 15.0), + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 3.0), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(20.0), + ), + child: Row(children: [ + Text( + '${comment['childCount']}回复', + style: TextStyle(fontSize: 12.0), + ), + Icon( + isExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down, + size: 16.0, + ) + ]), + ), + ), + Text( + '${comment['createTime']?.toString().substring(0, 10) ?? ''}', + style: TextStyle(color: Colors.grey, fontSize: 12.0), + ), + ], + ), + ), + + // 子评论列表 + if (isExpanded && replies.isNotEmpty) + Padding( + padding: EdgeInsets.only(left: 40.0), + child: Column( + children: [ + ...replies.map((reply) => ListTile( + leading: ClipRRect( + borderRadius: BorderRadius.circular(25.0), + child: NetworkOrAssetImage( + imageUrl: reply['commentUserFace'] ?? 'assets/images/avatar/default.png', + width: 25.0, + height: 25.0, + fit: BoxFit.cover, + ), + ), + title: Row( + children: [ + Expanded( + child: Text( + reply['commentUserNickname'] ?? '未知用户', + style: TextStyle( + color: Colors.grey, + fontSize: 11.0, + ), + ), + ), + GestureDetector( + onTap: () { + setState(() { + replyingCommentId = comment['id']; + replyingCommentUser = reply['commentUserNickname'] ?? '未知用户'; + }); + }, + child: Icon( + Icons.reply, + color: Colors.black54, + size: 14.0, + ), + ), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.symmetric(vertical: 3.0), + child: Text( + reply['content'] ?? '', + style: TextStyle(fontSize: 13.0), + ), + ), + Text( + '${reply['createTime']?.toString().substring(0, 10) ?? ''}', + style: TextStyle(color: Colors.grey, fontSize: 11.0), + ), + ], + ), + )).toList(), + + // 加载更多子评论按钮 + if (replies.length < comment['childCount']) + Center( + child: TextButton( + onPressed: () => fetchReplies(comment['id'], true), + child: isLoadingReplies[comment['id']] == true + ? CircularProgressIndicator() + : Text('加载更多回复'), + ), + ), + ], + ), + ), + + Divider(height: 1, color: Colors.grey[200]), + ], ); }, ), + ), ), - ), - if (replyingCommentId > 0) + // 在回复输入区域显示更详细的信息 + if (replyingCommentId.isNotEmpty) Container( padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), color: Colors.grey[100], child: Row( children: [ - Text( - '回复 @$replyingCommentUser', - style: TextStyle(fontSize: 12.0, color: Colors.blue), + Expanded( + child: Text( + '回复 @$replyingCommentUser', + style: TextStyle(fontSize: 12.0, color: Colors.blue), + overflow: TextOverflow.ellipsis, + ), ), - Spacer(), GestureDetector( onTap: () { setState(() { - replyingCommentId = 0; + replyingCommentId = ''; replyingCommentUser = ''; }); }, @@ -338,58 +500,58 @@ class _CommentBottomSheetState extends State { ], ), ), - GestureDetector( - child: Container( - margin: EdgeInsets.all(10.0), - height: 40.0, - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(30.0), - ), - child: Row( - children: [ - SizedBox(width: 15.0), - Icon( - Icons.edit_note, - color: Colors.black54, - size: 16.0, - ), - SizedBox(width: 5.0), - Text( - replyingCommentId > 0 ? '回复 @$replyingCommentUser' : '说点什么...', - style: TextStyle(color: Colors.black54, fontSize: 14.0), - ), - ], + GestureDetector( + child: Container( + margin: EdgeInsets.all(10.0), + height: 40.0, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(30.0), + ), + child: Row( + children: [ + SizedBox(width: 15.0), + Icon( + Icons.edit_note, + color: Colors.black54, + size: 16.0, + ), + SizedBox(width: 5.0), + Text( + replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...', + style: TextStyle(color: Colors.black54, fontSize: 14.0), + ), + ], + ), ), + onTap: () { + Navigator.push(context, FadeRoute(child: PopupReply( + hintText: replyingCommentId.isNotEmpty ? '回复 @$replyingCommentUser' : '说点什么...', + onChanged: (value) { + debugPrint('评论内容: $value'); + }, + onSubmitted: (value) { + if (value.isNotEmpty) { + postComment(value, parentCommentId: replyingCommentId); + setState(() { + replyingCommentId = ''; + replyingCommentUser = ''; + }); + Navigator.pop(context); + } + }, + ))); + }, ), - onTap: () { - Navigator.push(context, FadeRoute(child: PopupReply( - hintText: replyingCommentId > 0 ? '回复 @$replyingCommentUser' : '说点什么...', - onChanged: (value) { - debugPrint('评论内容: $value'); - }, - onSubmitted: (value) { - if (value.isNotEmpty) { - postComment(value, parentCommentId: replyingCommentId); - setState(() { - replyingCommentId = 0; - replyingCommentUser = ''; - }); - Navigator.pop(context); - } - }, - ))); - }, - ), - ], - ), - ); + ], + ), + ); + } } -} class _RecommendModuleState extends State { VideoModuleController videoModuleController = Get.put(VideoModuleController()); final ChatController chatController = Get.find(); - + final RxMap lastReturnData = RxMap({}); // 分页内容 int page = 1; final int pageSize = 10; @@ -421,21 +583,15 @@ class _RecommendModuleState extends State { bool hasMoreComments = true; List> commentList = []; int currentVideoId = 0; - int replyingCommentId = 0; // 正在回复的评论ID + String replyingCommentId = ''; // 正在回复的评论ID String replyingCommentUser = ''; // 正在回复的用户名 // 分享列表 List shareList = [ {'icon': 'assets/images/share-wx.png', 'label': '好友'}, - {'icon': 'assets/images/share-wx.png', 'label': '微信'}, {'icon': 'assets/images/share-pyq.png', 'label': '朋友圈'}, {'icon': 'assets/images/share-link.png', 'label': '复制链接'}, {'icon': 'assets/images/share-download.png', 'label': '下载'}, - {'icon': 'assets/images/share-download.png', 'label': '下载'}, - {'icon': 'assets/images/share-download.png', 'label': '下载'}, - {'icon': 'assets/images/share-download.png', 'label': '下载'}, - {'icon': 'assets/images/share-download.png', 'label': '下载下载下载下载下载下载下载下载下载下载下载下载'}, - {'icon': 'assets/images/share-download.png', 'label': '下载下载下载下载下载下载下载下载下载下载下载下载'}, ]; @override @@ -545,7 +701,7 @@ class _RecommendModuleState extends State { if (videos.isNotEmpty) { page++; } - logger.i('获取新的视频数据了'); + logger.i('视频数据列表------------------->$videoList'); // 初始化播放器 player.open( @@ -569,91 +725,6 @@ class _RecommendModuleState extends State { } } - // 修改 fetchComments 方法 -Future fetchComments(int videoId, {bool loadMore = false}) async { - // 防止重复加载 - if (isLoadingMoreComments && !loadMore) return; - - setState(() { - isLoadingMoreComments = true; - }); - - try { - final res = await Http.post(VideoApi.videoCommentList, data: { - 'vlogId': videoId, - 'current': loadMore ? commentPage : 1, - 'size': commentPageSize, - }); - - logger.d('评论接口返回: ${json.encode(res)}'); - - if (res['code'] == 200 && res['data'] != null) { - final data = res['data']; - final List> newComments = - List>.from(data['records'] ?? []); - final int total = data['total'] ?? 0; - - setState(() { - if (loadMore) { - commentList.addAll(newComments); - } else { - commentList = newComments; - } - hasMoreComments = commentList.length < total; - commentPage = loadMore ? commentPage + 1 : 2; // 下一页页码 - }); - - logger.d('成功加载 ${newComments.length} 条评论,总共 $total 条'); - } else { - logger.e('获取评论失败: ${res['msg']}'); - MyToast().tip( - title: '获取评论失败', - position: 'center', - type: 'error', - ); - } - } catch (e) { - logger.e('获取评论异常: $e'); - MyToast().tip( - title: '网络异常', - position: 'center', - type: 'error', - ); - } finally { - setState(() { - isLoadingMoreComments = false; - }); - } -} - - // 发布评论 - Future postComment(String content, {int parentCommentId = 0}) async { - try { - final res = await Http.post(VideoApi.doVideoComment, data: { - 'vlogId': currentVideoId, - 'content': content, - 'fatherCommentId': parentCommentId, - }); - - if (res['code'] == 0) { - // 评论成功,重新加载评论列表 - fetchComments(currentVideoId, loadMore: false); - MyToast().tip( - title: '评论成功', - position: 'center', - type: 'success', - ); - } - } catch (e) { - logger.i('发布评论失败: $e'); - MyToast().tip( - title: '评论失败', - position: 'center', - type: 'error', - ); - } - } - // 取消喜欢视频 Future doUnLikeVideo(item) async { logger.d('点击了点赞按钮$item'); @@ -688,8 +759,7 @@ Future fetchComments(int videoId, {bool loadMore = false}) async { // 评论弹框 void handleComment(index) { - final videoIdStr = videoList[index]['id'].toString(); - final videoId = int.tryParse(videoIdStr) ?? 0; + final videoId = videoList[index]['id']; logger.i('点击了评论按钮$videoId'); showModalBottomSheet( @@ -703,7 +773,17 @@ void handleComment(index) { ), context: context, builder: (context) { - return CommentBottomSheet(videoId: videoId); + return CommentBottomSheet( + videoId: videoId, + onCommentCountChanged: (newCount) { + // 更新对应视频的评论数量 + setState(() { + if (index < videoList.length) { + videoList[index]['commentsCounts'] = newCount; + } + }); + } + ); }, ); } @@ -974,9 +1054,20 @@ void handleComment(index) { height: 55.0, width: 48.0, child: GestureDetector( - onTap: () { + onTap: ()async { player.pause(); - Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]); + // 跳转到 Vloger 页面并等待返回结果 + final result = await Get.toNamed( + '/vloger', + arguments: videoList[videoModuleController + .videoPlayIndex.value]); + if (result != null) { + // 处理返回的参数 + print('返回的数据: ${result['followStatus']}'); + player.play(); + videoList[index]['doIFollowVloger'] = result['followStatus']; + + }; }, child: UnconstrainedBox( alignment: Alignment.topCenter, @@ -1013,10 +1104,18 @@ void handleComment(index) { size: 14.0, ), ), - onTap: () { - setState(() { - videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger']; - }); + onTap: () async { + final vlogerId = videoList[index]['memberId']; + final doIFollowVloger = videoList[index]['doIFollowVloger']; + // 未关注点击才去关注 + if(doIFollowVloger == false){ + final res = await ImService.instance.followUser(userIDList: [vlogerId]); + if (res.success) { + setState(() { + videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger']; + }); + } + } }, ), ),