215 lines
6.9 KiB
Dart
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,
|
|
),
|
|
);
|
|
}
|
|
}
|