diff --git a/lib/api/common_api.dart b/lib/api/common_api.dart index d43eb47..20b5401 100644 --- a/lib/api/common_api.dart +++ b/lib/api/common_api.dart @@ -5,7 +5,7 @@ class CommonApi { ///---------post static const String login = '/auth/login'; // 登录 {'phonenumber': '', 'smsCode': '', 'clientId': '428a8310cd442757ae699df5d894f051', 'grantType': 'sms'}; - static const String checkVersion = '/system/version/list'; // 查询app版本 {'platformType': Platform.isAndroid ? 'android' : 'ios','status': 1} + static const String checkVersion = '/app/version/page'; // 查询app版本 {'platformType': Platform.isAndroid ? 'android' : 'ios','status': 1} static const String uploadFile = '/resource/oss/upload'; ///[source]=wechat_open [clientId]=428a8310cd442757ae699df5d894f051 [grantType]=social [socialState]=1 diff --git a/lib/api/shop_api.dart b/lib/api/shop_api.dart index 0bf1541..7a6c987 100644 --- a/lib/api/shop_api.dart +++ b/lib/api/shop_api.dart @@ -24,4 +24,7 @@ class ShopApi { // 查询订单状态 static const String goodsOrderStatus= '/trans/easypay/paymentQuery'; + // 取消订单 + static const String cancelGoodsOrder= '/app/order/cancel'; + } diff --git a/lib/pages/order/detail.dart b/lib/pages/order/detail.dart index 1a75a84..5c839fc 100644 --- a/lib/pages/order/detail.dart +++ b/lib/pages/order/detail.dart @@ -1,5 +1,9 @@ library; +import 'dart:async'; +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:loopin/service/http.dart'; import 'package:loopin/api/shop_api.dart'; @@ -49,15 +53,16 @@ class _OrderDetailState extends State with SingleTickerProviderStat final res = await Http.get('${ShopApi.goodsOrderStatus}/$orderId'); // transState var orderStatus = res['data']['transState']; + print('状态-------------->${orderStatus}'); + // orderStatus + setState(() { + orderGoodsInfo['orderStatus'] = orderStatus; + }); if (orderStatus == 2) { // 已支付 MyDialog.toast('支付成功'); - } else { - MyDialog.toast('支付尚未完成,请稍后查看'); } - - - } catch (e) { + } catch (e) { print('报错-------------->${e}'); } } @@ -65,16 +70,29 @@ class _OrderDetailState extends State with SingleTickerProviderStat // 获取订单详情信息,包含商品参数 void getOrderDetail({required String orderId}) async { try { - final res = await Http.get('${ShopApi.goodsOrderDetail}/$orderId'); - print('订单详情-------------->${res['data']}'); - setState(() { - orderGoodsInfo = res['data']; // 注意取 data 部分 - }); - } catch (e) { - Get.back(); - } + final res = await Http.get('${ShopApi.goodsOrderDetail}/$orderId'); + print('订单详情-------------->${res['data']}'); + setState(() { + orderGoodsInfo = res['data']; // 注意取 data 部分 + }); + } catch (e) { + Get.back(); + } } + // 取消订单 + void _cancelOrder() async { + print('取消订单: $_orderId'); + try { + final res = await Http.post('${ShopApi.cancelGoodsOrder}/$_orderId'); + print('取消订单成功-------------->${res}'); + getOrderDetail(orderId: _orderId); // 刷新订单详情数据 + } catch (e) { + print('取消订单失败-------------->${e}'); + } + } + + // 显示支付结果弹框 void _showPaymentResultDialog() { @@ -139,6 +157,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat child: OutlinedButton( onPressed: () { Navigator.of(context).pop(); + // getOrderDetail(orderId: _orderId); getOrderRealStatus(orderId: _orderId); // 主动再次拉取订单状态 }, style: OutlinedButton.styleFrom( @@ -336,13 +355,6 @@ class _OrderDetailState extends State with SingleTickerProviderStat } } - // 取消订单 - void _cancelOrder() { - print('取消订单: $_orderId'); - // 实现取消订单的逻辑 - } - - Widget emptyTip() { return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -403,7 +415,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat Icons.info, size: 16.0, )), - TextSpan(text: '待支付, '), + TextSpan(text: getOrderStatusText(orderStatus)), TextSpan(text: _countdownFinished ? '倒计时已结束' : ' 剩余 '), if (!_countdownFinished) WidgetSpan( @@ -423,6 +435,7 @@ class _OrderDetailState extends State with SingleTickerProviderStat interval: const Duration(seconds: 1), onFinished: () { print("倒计时结束"); + _cancelOrder(); setState(() { _countdownFinished = true; // 倒计时结束时更新标志位 }); @@ -533,8 +546,9 @@ class _OrderDetailState extends State with SingleTickerProviderStat color: Colors.grey, size: 14.0, ), - onTap: () { - MyDialog.toast('复制订单信息', icon: Icon(Icons.check_circle)); + onTap: ()async { + await Clipboard.setData(ClipboardData(text: _orderId)); + MyDialog.toast('订单已复制到剪切板', icon: Icon(Icons.check_circle)); }, ) ], diff --git a/lib/pages/video/module/attention.dart b/lib/pages/video/module/attention.dart index ac48c8f..4fdb316 100644 --- a/lib/pages/video/module/attention.dart +++ b/lib/pages/video/module/attention.dart @@ -676,7 +676,11 @@ class _AttentionModuleState extends State { }); final data = res['data']; logger.d('关注用户的视频列表:$data'); - if (data == null || (data is List && data.isEmpty)) { + // 处理空数据情况 + if (data == null || data['records'] == null || (data['records'] is List && data['records'].isEmpty)) { + setState(() { + videoList = []; // 清空视频列表 + }); return; } @@ -1014,6 +1018,20 @@ class _AttentionModuleState extends State { color: Colors.black, child: Column( children: [ + // 添加暂无数据提示 + if (videoList.isEmpty && !isLoadingMore) + Expanded( + child: Center( + child: Text( + '暂无数据', + style: TextStyle( + color: Colors.white, + fontSize: 16.0, + ), + ), + ), + ) + else Expanded( child: Stack( children: [ diff --git a/lib/pages/video/module/friend.dart b/lib/pages/video/module/friend.dart index 4b27927..248cfe6 100644 --- a/lib/pages/video/module/friend.dart +++ b/lib/pages/video/module/friend.dart @@ -1,31 +1,1416 @@ -/// 朋友模块 +/// 朋友视频列表 library; +import 'dart:async'; +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +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'; +import 'package:loopin/models/summary_type.dart'; +import 'package:loopin/service/http.dart'; +import 'package:loopin/utils/download_video.dart'; +import 'package:loopin/utils/permissions.dart'; +import 'package:loopin/utils/wxsdk.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; +import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; + +import '../../../behavior/custom_scroll_behavior.dart'; +import '../../../controller/video_module_controller.dart'; +import '../../../router/fade_route.dart'; +import '../components/popup_reply.dart'; class FriendModule extends StatefulWidget { const FriendModule({super.key}); + static Player? _player; + + static void setPlayer(Player player) { + _player = player; + } + + static void pauseVideo() { + _player?.pause(); + } + + static void playVideo() { + _player?.play(); + } @override State createState() => _FriendModuleState(); } -class _FriendModuleState extends State { +class CommentBottomSheet extends StatefulWidget { + final String videoId; + final Function(int) onCommentCountChanged; // 新增回调函数 + + const CommentBottomSheet({ + super.key, + required this.videoId, + required this.onCommentCountChanged, // 接收回调 + }); + + @override + _CommentBottomSheetState createState() => _CommentBottomSheetState(); +} + +class _CommentBottomSheetState extends State { + // 评论相关状态(移动到内部) + int commentPage = 1; + final int commentPageSize = 10; + bool isLoadingMoreComments = false; + bool hasMoreComments = true; + List> commentList = []; + String replyingCommentId = ''; + String replyingCommentUser = ''; + + // 新增:子评论相关状态 + Map expandedReplies = {}; // 存储已展开的回复 {commentId: true/false} + Map>> replyData = {}; // 存储子评论数据 {commentId: [replies]} + Map replyPage = {}; // 子评论分页 + Map isLoadingReplies = {}; // 子评论加载状态 + + @override + void initState() { + super.initState(); + // 初始化时加载评论 + fetchComments(false); + } + + Future fetchComments(bool loadMore) async { + if (isLoadingMoreComments && !loadMore) return; + + setState(() { + isLoadingMoreComments = true; + }); + + if (!loadMore) { + commentPage = 1; + hasMoreComments = true; + commentList.clear(); + } + logger.d('入参vlogId-------------------->: ${widget.videoId}'); + try { + final res = await Http.post(VideoApi.videoCommentList, data: { + 'vlogId': widget.videoId, + 'current': commentPage, + '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++; + }); + widget.onCommentCountChanged(total); + logger.d('成功加载 ${newComments.length} 条评论,总共 $total 条'); + } + } catch (e) { + logger.e('获取评论异常: $e'); + } finally { + setState(() { + isLoadingMoreComments = false; + }); + } + } + + 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'] == 200) { + // 如果是回复,刷新对应的子评论 + if (parentCommentId.isNotEmpty) { + fetchReplies(parentCommentId, false); + // 更新主评论的子评论数量 + setState(() { + final comment = commentList.firstWhere((c) => c['id'] == parentCommentId, orElse: () => {}); + if (comment.isNotEmpty) { + comment['childCount'] = (comment['childCount'] ?? 0) + 1; + } + }); + } else { + // 如果是新评论,刷新整个列表 + fetchComments(false); + } + widget.onCommentCountChanged(commentList.length + 1); + MyToast().tip( + title: '评论成功', + position: 'center', + 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) { - return Container( - color: Colors.pink[200], + return Material( + color: Colors.white, child: Column( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 5.0, children: [ - Image.asset( - 'assets/images/empty.png', - width: 100.0, + Container( + padding: EdgeInsets.fromLTRB(15.0, 10.0, 10.0, 5.0), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Color(0xFFFAFAFA)))), + child: Column( + spacing: 10.0, + children: [ + Row( + children: [ + /* Expanded( + child: Text.rich(TextSpan(children: [ + TextSpan( + text: '大家都在搜: ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: '黑神话-悟空', + style: TextStyle(color: const Color(0xFF496D80)), + ), + ]))),*/ + GestureDetector( + child: Container( + height: 22.0, + width: 22.0, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(100.0), + ), + child: UnconstrainedBox(child: Icon(Icons.close, color: Colors.black45, size: 14.0))), + onTap: () { + Navigator.pop(context); + }, + ), + ], + ), + Text( + '${commentList.length}条评论', + style: TextStyle(fontSize: 12.0, fontWeight: FontWeight.w600), + ), + ], + ), ), - Text( - '暂无朋友信息~', - style: TextStyle(color: Colors.white54, fontSize: 14.0), + Expanded( + child: NotificationListener( + onNotification: (ScrollNotification scrollInfo) { + if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !isLoadingMoreComments && hasMoreComments) { + fetchComments(true); + } + return false; + }, + child: commentList.isEmpty && !isLoadingMoreComments + ? Center( + child: Text( + '暂无评论', + style: TextStyle(color: Colors.grey), + ), + ) + : ListView.builder( + physics: BouncingScrollPhysics(), + itemCount: commentList.length + (hasMoreComments ? 1 : 0), + itemBuilder: (context, index) { + if (index == commentList.length) { + return Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: isLoadingMoreComments ? CircularProgressIndicator() : Text('没有更多评论了'), + ), + ); + } + + final comment = commentList[index]; + 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, + ), + ), + title: Row( + children: [ + Expanded( + child: Text( + comment['commentUserNickname'] ?? '未知用户', + 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), + ), + ], + ), + )), + + // 加载更多子评论按钮 + 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.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + color: Colors.grey[100], + child: Row( + children: [ + Expanded( + child: Text( + '回复 @$replyingCommentUser', + style: TextStyle(fontSize: 12.0, color: Colors.blue), + overflow: TextOverflow.ellipsis, + ), + ), + GestureDetector( + onTap: () { + setState(() { + replyingCommentId = ''; + replyingCommentUser = ''; + }); + }, + child: Icon(Icons.close, size: 16.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); + } + }, + ))); + }, + ), + ], + ), + ); + } +} + +class _FriendModuleState extends State { + VideoModuleController videoModuleController = Get.put(VideoModuleController()); + final ChatController chatController = Get.find(); + final RxMap lastReturnData = RxMap({}); + // 分页内容 + int page = 1; + final int pageSize = 10; + bool isLoadingMore = false; + + // 页面controller + late PageController pageController = PageController( + initialPage: videoModuleController.videoPlayIndex.value, + viewportFraction: 1.0, + ); + + // 播放器controller + late Player player = Player(); + late VideoController videoController = VideoController(player); + + final List subscriptions = []; + // 进度条slider当前阈值 + double sliderValue = 0.0; + bool sliderDraging = false; + late Duration position = Duration.zero; // 当前时长 + late Duration duration = Duration.zero; // 总时长 + // 视频数据 + List videoList = []; + + // 评论相关状态 + int commentPage = 1; + final int commentPageSize = 10; + bool isLoadingMoreComments = false; + bool hasMoreComments = true; + List> commentList = []; + int currentVideoId = 0; + String replyingCommentId = ''; // 正在回复的评论ID + String replyingCommentUser = ''; // 正在回复的用户名 + + // 分享列表 + List shareList = [ + {'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': '下载'}, + ]; + + @override + void initState() { + super.initState(); + videoModuleController.needRefresh.listen((need) { + if (need) { + reInit(); + videoModuleController.clearNeedRefresh(); + } + }); + FriendModule.setPlayer(player); + // 获取视频数据 + fetchVideoList(); + } + + @override + void setState(VoidCallback fn) { + if (mounted) { + super.setState(fn); + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (subscriptions.isEmpty) { + subscriptions.addAll( + [ + // 监听视频时长 + player.stream.duration.listen((event) { + setState(() { + duration = event; + }); + }), + // 监听视频播放进度 + player.stream.position.listen((event) { + setState(() { + position = event; + if (position > Duration.zero && !sliderDraging) { + // 设置视频播放位置 + sliderValue = (position.inMilliseconds / duration.inMilliseconds).clamp(0.0, 1.0); + } + }); + }), + ], + ); + } + } + + @override + void dispose() { + player.dispose(); + pageController.dispose(); + for (final subscription in subscriptions) { + subscription.cancel(); + } + super.dispose(); + } + + void reInit() async { + await player.stop(); + // 重置状态 + page = 1; + isLoadingMore = false; + videoList.clear(); + videoModuleController.updateVideoPlayIndex(0); + sliderValue = 0.0; + sliderDraging = false; + position = Duration.zero; + duration = Duration.zero; + pageController.jumpToPage(0); + + // 拉新数据 + fetchVideoList(); + } + + Future fetchVideoList() async { + if (isLoadingMore) return; + isLoadingMore = true; + try { + final res = await Http.post(VideoApi.friendList, data: { + 'current': page, + 'size': pageSize, + }); + final data = res['data']; + logger.d('好友的视频列表:$data'); + // 处理空数据情况 + if (data == null || data['records'] == null || (data['records'] is List && data['records'].isEmpty)) { + setState(() { + videoList = []; // 清空视频列表 + }); + return; + } + + if (data['records'] is List) { + List videos = data['records']; + for (var item in videos) { + item['expanded'] = false; + } + + setState(() { + if (page == 1) { + // 初始化 + videoList = videos; + } else { + videoList.addAll(videos); + } + }); + // 处理完成后 + if (videos.isNotEmpty) { + page++; + } + logger.i('视频数据列表------------------->$videoList'); + + // 初始化播放器 + player.open( + Media( + videoList[videoModuleController.videoPlayIndex.value]['url'], + ), + play: false); + player.setPlaylistMode(PlaylistMode.loop); // 循环播放; + + // 第一次加载后播放第一个视频 + if (page == 2 && videoModuleController.videoTabIndex.value == 2 && Get.currentRoute == '/' && videoModuleController.layoutPageCurrent.value == 0) { + player.play(); // 播放第一个 + } else { + logger.i('没播放视频'); + } + } + } catch (e) { + logger.i('获取视频失败: $e'); + } finally { + isLoadingMore = false; // 加载完成,标记为 false + } + } + + // 取消喜欢视频 + Future doUnLikeVideo(item) async { + logger.d('点击了点赞按钮$item'); + try { + final res = await Http.post(VideoApi.unlike, data: {'id': item['id']}); + final resCode = res['code']; + if (resCode == 200) { + item['doILikeThisVlog'] = !item['doILikeThisVlog']; + } + } catch (e) { + logger.i('点击取消喜欢按钮报错: $e'); + } + } + + // 点击喜欢视频 + Future doLikeVideo(item) async { + try { + final res = await Http.post(VideoApi.like, data: {'id': item['id']}); + final resCode = res['code']; + if (resCode == 200) { + item['doILikeThisVlog'] = !item['doILikeThisVlog']; + } + logger.i('点赞返回信息----------->: $res'); + } catch (e) { + logger.i('点击喜欢按钮报错: $e'); + } + } + + // 评论弹框 + void handleComment(index) { + final videoId = videoList[index]['id']; + logger.i('点击了评论按钮$videoId'); + + showModalBottomSheet( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(15.0))), + showDragHandle: false, + clipBehavior: Clip.antiAlias, + isScrollControlled: true, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 3 / 4, + ), + context: context, + builder: (context) { + return CommentBottomSheet( + videoId: videoId, + onCommentCountChanged: (newCount) { + // 更新对应视频的评论数量 + setState(() { + if (index < videoList.length) { + videoList[index]['commentsCounts'] = newCount; + } + }); + }); + }, + ); + } + + // 分享弹框 + void handleShare(index) { + if (chatController.chatList.isNotEmpty) { + chatController.getConversationList(); + } + showModalBottomSheet( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)), + ), + clipBehavior: Clip.antiAlias, + context: context, + isScrollControlled: true, + builder: (context) { + return Material( + color: Colors.white, + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 分享列表 + SizedBox( + height: 110, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: shareList.length, + padding: EdgeInsets.symmetric(horizontal: 0, vertical: 20.0), + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => handleShareClick(index), + child: Container( + width: 64, + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset('${shareList[index]['icon']}', width: 48.0), + SizedBox(height: 5), + Text( + '${shareList[index]['label']}', + style: TextStyle(fontSize: 12.0), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + ), + ), + + // 会话列表 + if (chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).isNotEmpty) + SizedBox( + height: 110, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length, + padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0), + itemBuilder: (context, index) { + final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList(); + return GestureDetector( + onTap: () => handlCoverClick(filteredList[index].conversation), + child: Container( + width: 64, + margin: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + NetworkOrAssetImage( + imageUrl: filteredList[index].faceUrl, + width: 48.0, + height: 48.0, + ), + const SizedBox(height: 5), + Text( + filteredList[index].conversation.showName ?? '', + style: const TextStyle(fontSize: 12.0), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + ), + ), + + // 取消按钮 + SafeArea( + top: false, + child: InkWell( + onTap: () => Get.back(), + child: Container( + alignment: Alignment.center, + width: double.infinity, + height: 50.0, + color: Colors.grey[50], + child: Text( + '取消', + style: TextStyle(color: Colors.black87), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + void handleShareClick(int index) { + print("分享项 $index 被点击"); + final videoId = videoList[videoModuleController.videoPlayIndex.value]['id']; + final videoUrl = videoList[videoModuleController.videoPlayIndex.value]['url']; + final description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '快来看看这个视频'; + var httpPrefix = 'http://43.143.227.203/adv'; + logger.i('分享链接地址----------------: $httpPrefix/goods-detail?id=$videoId'); + if (index == 0) { + // 分享好友 + Wxsdk.shareToFriend(title: '快来看看这个视频', description: description, webpageUrl: '$httpPrefix/video-detail?id=$videoId'); + } else if (index == 1) { + // 分享到朋友圈 + Wxsdk.shareToTimeline(title: '快来看看这个视频', webpageUrl: '$httpPrefix/goods-detail?id=$videoId'); + } else if (index == 2) { + // 复制链接到剪切板 + copyToClipboard(videoUrl); + } else if (index == 3) { + // 下载视频到本地 + _downloadVideoWithDio(videoUrl, description); + } + } + + // 复制链接到剪贴板 + void copyToClipboard(String text) async { + try { + await Clipboard.setData(ClipboardData(text: text)); + MyToast().tip( + title: '链接已复制到剪贴板', + position: 'center', + type: 'success', + ); + } catch (e) { + MyToast().tip( + title: '复制失败', + position: 'center', + type: 'success', + ); + } + } + + // 下载视频 + Future _downloadVideoWithDio(String videoUrl, String fileName) async { + try { + // 请求存储权限 + String? toastId; // 用于存储toast的ID,以便后续关闭 + var status = await Permissions.requestStoragePermission(); + if (!status) { + MyToast().tip( + title: '需要存储权限才能下载视频', + position: 'center', + type: 'success', + ); + return; + } + await DownloadManager.downloadFile( + url: videoUrl, + fileName: '$fileName.mp4', + onProgress: (progress) { + print("下载进度: $progress%"); + // 显示进度组件 + }, + onComplete: (filePath) { + MyToast().tip( + title: '下载完成', + position: 'center', + type: 'success', + ); + }, + onError: (error) { + MyToast().tip( + title: '下载失败: $error', + position: 'center', + type: 'error', + ); + }, + ); + } catch (e) { + print("下载视频失败: $e"); + } + } + + void handlCoverClick(V2TimConversation conv) async { + // 发送VideoMsg,获取当前视频信息 + final userId = conv.userID; + final String url = videoList[videoModuleController.videoPlayIndex.value]['url']; + final img = videoList[videoModuleController.videoPlayIndex.value]['firstFrameImg']; + final width = videoList[videoModuleController.videoPlayIndex.value]['width']; + final height = videoList[videoModuleController.videoPlayIndex.value]['height']; + final makeJson = jsonEncode({ + "width": width, + "height": height, + "imgUrl": img, + "videoUrl": url, + }); + final res = await IMMessage().createCustomMessage( + data: makeJson, + ); + if (res.success) { + final sendRes = await IMMessage().sendMessage(msg: res.data!.messageInfo!, toUserID: userId, cloudCustomData: SummaryType.shareVideo); + if (sendRes.success) { + MyToast().tip( + title: '分享成功', + position: 'center', + type: 'success', + ); + Get.back(); + } else { + logger.e(res.desc); + } + } else { + logger.e(res.desc); + } + } + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.black, + child: Column( + children: [ + // 添加暂无数据提示 + if (videoList.isEmpty && !isLoadingMore) + Expanded( + child: Center( + child: Text( + '暂无数据', + style: TextStyle( + color: Colors.white, + fontSize: 16.0, + ), + ), + ), + ) + else + Expanded( + child: Stack( + children: [ + /// 垂直滚动模块 + PageView.builder( + scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false), + scrollDirection: Axis.vertical, + controller: pageController, + onPageChanged: (index) async { + videoModuleController.updateVideoPlayIndex(index); + setState(() { + sliderValue = 0.0; + sliderDraging = false; + position = Duration.zero; + duration = Duration.zero; + }); + + player.stop(); + await player.open(Media(videoList[index]['url'])); + if (index == videoList.length - 2 && !isLoadingMore) { + await fetchVideoList(); + } + }, + itemCount: videoList.length, + itemBuilder: (context, index) { + final videoWidth = videoList[index]['width'] ?? 1; + final videoHeight = videoList[index]['height'] ?? 1; + final isHorizontal = videoWidth > videoHeight; + final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList(); + return Stack( + children: [ + // 视频区域 + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: GestureDetector( + child: Stack( + children: [ + Visibility( + visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero, + child: Video( + controller: videoController, + fit: isHorizontal ? BoxFit.contain : BoxFit.cover, + controls: NoVideoControls, + ), + ), + AnimatedOpacity( + opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0, + duration: Duration(milliseconds: 50), + child: Image.network( + videoList[index]['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png', + fit: isHorizontal ? BoxFit.contain : BoxFit.cover, + width: double.infinity, + height: double.infinity, + ), + ), + StreamBuilder( + stream: player.stream.playing, + builder: (context, playing) { + return Visibility( + visible: playing.data == false, + child: Center( + child: IconButton( + padding: EdgeInsets.zero, + onPressed: () { + player.playOrPause(); + }, + icon: Icon( + playing.data == true ? Icons.pause : Icons.play_arrow_rounded, + color: Colors.white60, + size: 80, + ), + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))), + ), + ), + ); + }, + ), + ], + ), + onTap: () { + player.playOrPause(); + }, + ), + ), + // 右侧操作栏 + Positioned( + bottom: 100.0, + right: 6.0, + child: Column( + spacing: 15.0, + children: [ + Stack( + children: [ + SizedBox( + height: 55.0, + width: 48.0, + child: GestureDetector( + onTap: () async { + player.pause(); + // 跳转到 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, + child: Container( + height: 48.0, + width: 48.0, + decoration: BoxDecoration( + border: Border.all(color: Colors.white, width: 2.0), + borderRadius: BorderRadius.circular(100.0), + ), + child: ClipOval( + child: NetworkOrAssetImage( + imageUrl: videoList[index]['commentUserFace'], + ), + ), + ), + ), + ), + ), + Positioned( + bottom: 0, + left: 15.0, + child: InkWell( + child: Container( + height: 18.0, + width: 18.0, + decoration: BoxDecoration( + color: videoList[index]['doIFollowVloger'] ? Colors.white : Color(0xFFFF5000), + borderRadius: BorderRadius.circular(100.0), + ), + child: Icon( + videoList[index]['doIFollowVloger'] ? Icons.check : Icons.add, + color: videoList[index]['doIFollowVloger'] ? Color(0xFFFF5000) : Colors.white, + size: 14.0, + ), + ), + 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']; + }); + } + } + }, + ), + ), + ], + ), + GestureDetector( + child: Column( + children: [ + SvgPicture.asset( + 'assets/images/svg/heart.svg', + colorFilter: ColorFilter.mode(videoList[index]['doILikeThisVlog'] ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn), + height: 40.0, + width: 40.0, + ), + Text( + '${videoList[index]['likeCounts'] + (videoList[index]['doILikeThisVlog'] ? 1 : 0)}', + style: TextStyle(color: Colors.white, fontSize: 12.0), + ), + ], + ), + onTap: () { + logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}'); + if (videoList[index]['doILikeThisVlog'] == true) { + logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}'); + doUnLikeVideo(videoList[index]); + } else { + doLikeVideo(videoList[index]); + } + }, + ), + GestureDetector( + child: Column( + children: [ + SvgPicture.asset( + 'assets/images/svg/reply.svg', + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), + height: 40.0, + width: 40.0, + ), + Text( + '${videoList[index]['commentsCounts']}', + style: TextStyle(color: Colors.white, fontSize: 12.0), + ), + ], + ), + onTap: () { + handleComment(index); + }, + ), + GestureDetector( + child: Column( + children: [ + SvgPicture.asset( + 'assets/images/svg/share.svg', + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), + height: 40.0, + width: 40.0, + ), + ], + ), + onTap: () { + handleShare(index); + }, + ), + GestureDetector( + child: Column( + children: [ + SvgPicture.asset( + 'assets/images/svg/report.svg', + colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), + height: 40.0, + width: 40.0, + ), + ], + ), + onTap: ()async { + player.pause(); + // 跳转到举报页面并等待返回结果 + final result = await Get.toNamed( + '/report', + arguments: videoList[videoModuleController + .videoPlayIndex.value]); + if (result != null) { + player.play(); + }; + }, + ), + ], + ), + ), + Positioned( + bottom: 15.0, + left: 10.0, + right: 80.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '@${videoList[videoModuleController.videoPlayIndex.value]['commentUserNickname'] ?? '未知'}', + style: const TextStyle(color: Colors.white, fontSize: 16.0), + ), + LayoutBuilder( + builder: (context, constraints) { + final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知'; + final span = TextSpan( + text: text, + style: const TextStyle(color: Colors.white, fontSize: 14.0), + ); + final tp = TextPainter( + text: span, + maxLines: 3, + textDirection: TextDirection.ltr, + ); + tp.layout(maxWidth: constraints.maxWidth); + final isOverflow = tp.didExceedMaxLines; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + text, + maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3, + overflow: + videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? TextOverflow.visible : TextOverflow.ellipsis, + style: const TextStyle(color: Colors.white, fontSize: 14.0), + ), + if (isOverflow) + Padding( + padding: const EdgeInsets.only(top: 6.0), + child: GestureDetector( + onTap: () { + setState(() { + videoList[videoModuleController.videoPlayIndex.value]['expanded'] = + !videoList[videoModuleController.videoPlayIndex.value]['expanded']; + }); + }, + child: Text( + videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多', + textAlign: TextAlign.right, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); + }, + ), + ], + )), + Positioned( + bottom: 0.0, + left: 6.0, + right: 6.0, + child: Visibility( + visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero, + child: Listener( + child: SliderTheme( + data: SliderThemeData( + trackHeight: sliderDraging ? 6.0 : 2.0, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0), + overlayShape: RoundSliderOverlayShape(overlayRadius: 0), + inactiveTrackColor: Colors.white24, + activeTrackColor: Colors.white, + thumbColor: Colors.white, + overlayColor: Colors.transparent, + ), + child: Slider( + value: sliderValue, + onChanged: (value) async { + setState(() { + sliderValue = value; + }); + await player.seek(duration * value.clamp(0.0, 1.0)); + }, + onChangeEnd: (value) async { + setState(() { + sliderDraging = false; + }); + if (!player.state.playing) { + await player.play(); + } + }, + ), + ), + onPointerMove: (e) { + setState(() { + sliderDraging = true; + }); + }, + ), + ), + ), + Positioned( + bottom: 100.0, + left: 10.0, + right: 10.0, + child: Visibility( + visible: sliderDraging, + child: DefaultTextStyle( + style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8.0, + children: [ + Text(position.label(reference: duration), style: TextStyle(color: Colors.white)), + Text('/', style: TextStyle(fontSize: 14.0)), + Text(duration.label(reference: duration)), + ], + ), + )), + ), + ], + ); + }, + ), + ], + ), ), ], ),