/// 精选推荐模块 library; import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.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/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/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 RecommendModule extends StatefulWidget { const RecommendModule({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() => _RecommendModuleState(); } class CommentBottomSheet extends StatefulWidget { final int videoId; const CommentBottomSheet({super.key, required this.videoId}); @override _CommentBottomSheetState createState() => _CommentBottomSheetState(); } class _CommentBottomSheetState extends State { // 评论相关状态(移动到内部) int commentPage = 1; final int commentPageSize = 10; bool isLoadingMoreComments = false; bool hasMoreComments = true; List> commentList = []; int replyingCommentId = 0; String replyingCommentUser = ''; @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(); } 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++; }); logger.d('成功加载 ${newComments.length} 条评论,总共 $total 条'); } } catch (e) { logger.e('获取评论异常: $e'); } finally { setState(() { isLoadingMoreComments = false; }); } } Future postComment(String content, {int parentCommentId = 0}) async { try { final res = await Http.post(VideoApi.doVideoComment, data: { 'vlogId': widget.videoId, 'content': content, 'fatherCommentId': parentCommentId, }); if (res['code'] == 0) { // 评论成功,重新加载评论列表 fetchComments(false); MyToast().tip( title: '评论成功', position: 'center', type: 'success', ); } } catch (e) { logger.i('发布评论失败: $e'); MyToast().tip( title: '评论失败', position: 'center', type: 'error', ); } } @override Widget build(BuildContext context) { return Material( color: Colors.white, child: Column( children: [ 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), ), ], ), ), 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]; 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, ), ), ), 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), ), Icon( Icons.arrow_forward_ios, size: 10.0, ) ]), ), ), Text( '${comment['createTime']?.toString().substring(0, 10) ?? ''}', style: TextStyle(color: Colors.grey, fontSize: 12.0), ), ], ), ); }, ), ), ), if (replyingCommentId > 0) 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), ), Spacer(), GestureDetector( onTap: () { setState(() { replyingCommentId = 0; 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 > 0 ? '回复 @$replyingCommentUser' : '说点什么...', style: TextStyle(color: Colors.black54, fontSize: 14.0), ), ], ), ), 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(); // 分页内容 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; int replyingCommentId = 0; // 正在回复的评论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 void initState() { super.initState(); videoModuleController.needRefresh.listen((need) { if (need) { reInit(); videoModuleController.clearNeedRefresh(); } }); RecommendModule.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.vlogList, data: { 'current': page, 'size': pageSize, }); final data = res['data']; logger.d('关注用户的视频列表:$data'); if (data == null || (data is List && data.isEmpty)) { 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('获取新的视频数据了'); // 初始化播放器 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 } } // 修改 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'); try { final res = await Http.post(VideoApi.unlike, data:{ 'vlogId': 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:{ 'vlogId': 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 videoIdStr = videoList[index]['id'].toString(); final videoId = int.tryParse(videoIdStr) ?? 0; 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); }, ); } // 分享弹框 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, ), ], ), ), ); }, ), ), // 会话列表 SizedBox( height: 110, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length, padding: 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: EdgeInsets.symmetric(horizontal: 8.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ NetworkOrAssetImage( imageUrl: filteredList[index].faceUrl, width: 48.0, height: 48.0, ), SizedBox(height: 5), Text( '${filteredList[index].conversation.showName}', style: 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 description = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '获取title失败'; if (index == 1) { // 好友 Wxsdk.shareToFriend(title: '快来看看这个视频', description: description, webpageUrl: 'https://baidu.com'); } else if (index == 2) { // 朋友圈 Wxsdk.shareToTimeline(title: '快来看看这个视频', webpageUrl: 'https://baidu.com'); } } 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: [ 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: () { player.pause(); Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]); }, 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: () { 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: () { // 举报 }, ), ], ), ), 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)), ], ), )), ), ], ); }, ), ], ), ), ], ), ); } }