import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hgdj/assets_tool/images.dart'; import 'package:hgdj/hj_model/video_model.dart'; import 'package:hgdj/hj_page/drama/drama_detail_page.dart'; import 'package:hgdj/hj_page/drama/drama_list_page.dart'; import 'package:hgdj/hj_page/short_video/view/media_action_menu.dart'; import 'package:hgdj/hj_page/short_video/view/video_menu_kit.dart'; import 'package:hgdj/hj_utils/const.dart'; import 'package:hgdj/hj_utils/screen.dart'; import 'package:video_player/video_player.dart'; import '../../../tools_base/widget/shrink_wrap.dart'; /// 短剧操作台:左侧「剧名 + 第N集 + 标签」+ 右侧动作菜单 + 中间暂停播放按钮。 /// 点赞/收藏/评论/分享全部挂在**剧**上,下载则是当前这一集;短剧没有作者人设 class DramaMenu extends StatefulWidget { final VideoPlayerController? playerCtr; final VideoModel? videoModel; final VoidCallback? onSwitchLine; //切换 CDN 线路后重新起播 final bool showEntry; //剧名是否带箭头可点进二级页;二级页自身传 false,否则会重复入栈 /// 信息栏底部要距播放器底多远。由播放器按「进度条 + 剧集条 + 底部安全区」算好传进来—— /// 写死过一次(40+48),二级页在带手势条的机型上安全区一顶就和进度条压到一起了 final double bottomInset; const DramaMenu({ super.key, this.playerCtr, this.videoModel, this.onSwitchLine, this.showEntry = true, required this.bottomInset, }); @override State createState() => _DramaMenuState(); } class _DramaMenuState extends State { //信息栏各行的上间距(设计稿 剧名→标签行 12) double get gapValue => 12; VideoModel? get _video => widget.videoModel; @override void initState() { super.initState(); VideoMenuSwitch.addListen(_menuListen); } @override void dispose() { VideoMenuSwitch.removeLister(_menuListen); super.dispose(); } void _menuListen(bool isShow) { if (mounted) setState(() {}); } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.only(bottom: 4), child: Stack( alignment: Alignment.center, children: [ Container( //外层那个 bottom:4 已经占掉一部分,这里补齐到调用方要求的总距离 padding: EdgeInsets.only(bottom: widget.bottomInset - 4), child: Row( children: [ Expanded( child: MenuReveal( visible: VideoMenuSwitch.isShow, offset: const Offset(-0.1, 0), // 隐藏时向左滑出 child: _buildLeftPanel(), ), ), const SizedBox(width: 8), Column( mainAxisAlignment: MainAxisAlignment.end, children: [ MediaActionMenu( style: MediaStyle.Drama, videoModel: _video, playerCtr: widget.playerCtr, onSwitchLine: widget.onSwitchLine, ), ], ), 12.sizeBoxW, ], ), ), if (widget.playerCtr != null) CenterPlayPause(controller: widget.playerCtr!), ], ), ); } // ============ 左侧信息栏(剧名 / 第N集 + 标签)============ Widget _buildLeftPanel() { return Container( padding: const EdgeInsets.only(left: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ //剧名,二级页里不带箭头也不可点 _buildTitleItem(), //第N集 + 标签 _buildTagItem(), ], ), ); } Widget _buildTitleItem() { final title = _video?.title?.trim() ?? ''; if (title.isEmpty) return const SizedBox(); final content = Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w500), ), ), if (widget.showEntry) Image.asset('chevron_right.webp'.videoPath, width: 20, height: 20), ], ); return Container( padding: EdgeInsets.only(top: gapValue), child: widget.showEntry ? InkWell(enableFeedback: false, onTap: _openDrama, child: content) : content, ); } Widget _buildTagItem() { final episodeText = Text( "第${_video?.episodeNo ?? 1}集", style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.w500), ); if (_video?.tags?.isNotEmpty != true) { return Container( padding: EdgeInsets.only(top: gapValue), child: episodeText); } return Container( padding: EdgeInsets.only(top: gapValue), child: Row( children: [ episodeText, 6.sizeBoxW, Expanded(child: _tagWrap()), ], ), ); } Widget _tagWrap() { return ShrinkWrap( spacing: 0, runSpacing: 6, maxLines: 1, children: _video?.tags ?.map((e) => InkWell( enableFeedback: false, onTap: () { widget.playerCtr?.pause(); //短剧标签要出的是「剧」,不能用视频标签页——那边的卡片点开跳的是视频详情页 DramaListPage.toTag(e); }, child: Container( padding: const EdgeInsets.fromLTRB(8, 3, 8, 3), margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration( color: const Color(0x0DFFFFFF), borderRadius: BorderRadius.circular(3), ), child: Text(e.name ?? '', style: const TextStyle( color: Color(0xffffffff), fontSize: 11)), ), )) .toList() ?? [], ); } /// 进短剧二级页,定位到当前这一集。只有刷剧模式挂得上(二级页 showEntry=false) void _openDrama() { widget.playerCtr?.pause(); final index = (_video?.episodeNo ?? 1) - 1; //把这一刻的集数+秒数带进连播模式,写法与 DramaVideoPlayer._openDrama 一致 Get.to( () => DramaDetailPage( drama: _video?.dramaInfo, initialIndex: index < 0 ? 0 : index, initialSeconds: widget.playerCtr?.value.position.inSeconds ?? 0, ), ); } }