Files
huangguo_android/lib/hj_page/drama/view/drama_menu.dart
T
2026-09-15 15:44:13 +07:00

215 lines
6.9 KiB
Dart

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<DramaMenu> createState() => _DramaMenuState();
}
class _DramaMenuState extends State<DramaMenu> {
//信息栏各行的上间距(设计稿 剧名→标签行 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,
),
);
}
}