mod: 视频详情页样式

This commit is contained in:
guozhigq
2023-07-22 22:30:50 +08:00
parent 057b00f538
commit 8076848df3
7 changed files with 552 additions and 278 deletions

View File

@ -1,15 +1,11 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/fav/index.dart';
import 'package:pilipala/pages/favDetail/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/widgets/expandable_section.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
@ -18,6 +14,9 @@ import 'package:pilipala/pages/video/detail/introduction/controller.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import 'widgets/action_row_item.dart';
import 'widgets/intro_detail.dart';
import 'widgets/menu_row.dart';
import 'widgets/season.dart';
class VideoIntroPanel extends StatefulWidget {
@ -231,135 +230,99 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return SliverPadding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 10),
padding: const EdgeInsets.only(left: 12, right: 12, top: 15),
sliver: SliverToBoxAdapter(
child: !widget.loadingStatus || videoItem.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableRegion(
magnifierConfiguration: const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text(
!widget.loadingStatus
? widget.videoDetail!.title
: videoItem['title'],
style: Theme.of(context).textTheme.titleMedium!.copyWith(
letterSpacing: 0.5,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
InkWell(
splashColor: Colors.transparent,
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
onTap: () {
_manualController!.animateTo(isExpand ? 0 : 0.5);
setState(() {
isExpand = !isExpand;
});
},
child: Row(
children: [
const SizedBox(width: 2),
StatView(
theme: 'gray',
view: !widget.loadingStatus
? widget.videoDetail!.stat!.view
: videoItem['stat'].view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'gray',
danmu: !widget.loadingStatus
? widget.videoDetail!.stat!.danmaku
: videoItem['stat'].danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(
!widget.loadingStatus
? widget.videoDetail!.pubdate
: videoItem['pubdate'],
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline),
),
const Spacer(),
RotationTransition(
turns: _manualAnimation!,
child: SizedBox(
width: 35,
height: 35,
child: IconButton(
padding: const EdgeInsets.all(2.0),
onPressed: () {
/// 0.5代表 180弧度
_manualController!
.animateTo(isExpand ? 0 : 0.5);
setState(() {
isExpand = !isExpand;
});
},
icon: Icon(
FontAwesomeIcons.angleUp,
size: 15,
color: Theme.of(context).colorScheme.outline,
),
),
),
),
const SizedBox(width: 10),
],
),
),
// 简介 默认收起
if (!widget.loadingStatus)
ExpandedSection(
expand: isExpand,
begin: 0.0,
end: 1.0,
child: DefaultTextStyle(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
height: 1.5,
fontSize:
Theme.of(context).textTheme.labelMedium?.fontSize,
),
child: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: SelectableRegion(
magnifierConfiguration:
const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.videoDetail!.bvid!),
Text.rich(
TextSpan(
children: [
buildContent(
context, widget.videoDetail!),
],
),
),
],
),
),
Row(
children: [
Expanded(
child: Text(
!widget.loadingStatus
? widget.videoDetail!.title
: videoItem['title'],
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(
letterSpacing: 0.5,
fontWeight: FontWeight.bold),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(height: 8),
const SizedBox(width: 20),
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
return Theme.of(context)
.highlightColor
.withOpacity(0.2);
}),
),
onPressed: () {
showBottomSheet(
context: context,
enableDrag: true,
builder: (BuildContext context) {
return IntroDetail(
videoDetail: widget.videoDetail!);
});
},
icon: const Icon(Icons.more_horiz),
),
),
],
),
const SizedBox(height: 6),
Row(
children: [
const SizedBox(width: 2),
StatView(
theme: 'black',
view: !widget.loadingStatus
? widget.videoDetail!.stat!.view
: videoItem['stat'].view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'black',
danmu: !widget.loadingStatus
? widget.videoDetail!.stat!.danmaku
: videoItem['stat'].danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(
!widget.loadingStatus
? widget.videoDetail!.pubdate
: videoItem['pubdate'],
formatType: 'detail'),
style: const TextStyle(fontSize: 12),
),
],
),
// 点赞收藏转发
_actionGrid(context, videoIntroController, videoDetailCtr),
Padding(
padding: const EdgeInsets.only(top: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: actionRow(
context,
videoIntroController,
videoDetailCtr,
),
),
),
// 合集
if (!widget.loadingStatus &&
widget.videoDetail!.ugcSeason != null) ...[
@ -390,12 +353,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
src: !widget.loadingStatus
? widget.videoDetail!.owner!.face
: videoItem['owner'].face,
width: 38,
height: 38,
width: 34,
height: 34,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
),
const SizedBox(width: 14),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -421,7 +384,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
opacity: widget.loadingStatus ? 0 : 1,
duration: const Duration(milliseconds: 150),
child: SizedBox(
height: 36,
height: 34,
child: Obx(
() => videoIntroController.followStatus.isNotEmpty
? ElevatedButton(
@ -444,107 +407,83 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
],
),
),
const SizedBox(height: 8),
Divider(
height: 26,
height: 12,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
// const SizedBox(height: 10),
],
)
: const Center(child: CircularProgressIndicator()),
: const SizedBox(
height: 100,
child: Center(
child: CircularProgressIndicator(),
),
),
),
);
}
// 喜欢 投币 分享
Widget _actionGrid(
BuildContext context, videoIntroController, videoDetailCtr) {
return LayoutBuilder(builder: (context, constraints) {
return SizedBox(
height: constraints.maxWidth / 5 * 0.8,
child: Material(
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 5,
childAspectRatio: 1.25,
children: <Widget>[
// InkWell(
// onTap: () => videoIntroController.actionOneThree(),
// borderRadius: StyleString.mdRadius,
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Image.asset(
// 'assets/images/logo/logo_big.png',
// width: 10,
// height: 10,
// ),
// ),
// ),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: () => videoIntroController.actionLikeVideo(),
selectStatus: videoIntroController.hasLike.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.like!.toString()
: '-'),
),
// ActionItem(
// icon: const Icon(FontAwesomeIcons.thumbsDown),
// selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
// onTap: () => {},
// selectStatus: false,
// loadingStatus: widget.loadingStatus,
// text: '不喜欢'),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.coin!.toString()
: '-'),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.heart),
selectIcon: const Icon(FontAwesomeIcons.heartCircleCheck),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-'),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.share!.toString()
: '-'),
ActionItem(
icon: const Icon(FontAwesomeIcons.comments),
onTap: () {
videoDetailCtr.tabCtr.animateTo(1);
},
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.reply!.toString()
: '-'),
],
),
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
return Row(children: [
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
onTap: () => videoIntroController.actionLikeVideo(),
selectStatus: videoIntroController.hasLike.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.like!.toString()
: '-',
),
);
});
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.coin!.toString()
: '-',
),
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-',
),
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.comment),
onTap: () {
videoDetailCtr.tabCtr.animateTo(1);
},
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.reply!.toString()
: '-',
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.share),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.share!.toString()
: '-',
),
]);
}
InlineSpan buildContent(BuildContext context, content) {
@ -583,54 +522,3 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
return TextSpan(children: spanChilds);
}
}
class ActionItem extends StatelessWidget {
Icon? icon;
Icon? selectIcon;
Function? onTap;
bool? loadingStatus;
String? text;
bool selectStatus = false;
ActionItem({
Key? key,
this.icon,
this.selectIcon,
this.onTap,
this.loadingStatus,
this.text,
required this.selectStatus,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onTap!(),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
selectStatus
? Icon(selectIcon!.icon!,
size: 21, color: Theme.of(context).primaryColor)
: Icon(icon!.icon!,
size: 21, color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 4),
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text ?? '',
style: TextStyle(
color: selectStatus
? Theme.of(context).primaryColor
: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall?.fontSize),
),
),
],
),
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
class ActionItem extends StatelessWidget {
Icon? icon;
Icon? selectIcon;
Function? onTap;
bool? loadingStatus;
String? text;
bool selectStatus = false;
ActionItem({
Key? key,
this.icon,
this.selectIcon,
this.onTap,
this.loadingStatus,
this.text,
required this.selectStatus,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onTap!(),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
selectStatus
? Icon(selectIcon!.icon!,
size: 21, color: Theme.of(context).primaryColor)
: Icon(icon!.icon!,
size: 21, color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 4),
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text ?? '',
style: TextStyle(
color: selectStatus
? Theme.of(context).primaryColor
: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall?.fontSize),
),
),
],
),
);
}
}

View File

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
class ActionRowItem extends StatelessWidget {
Icon? icon;
Icon? selectIcon;
Function? onTap;
bool? loadingStatus;
String? text;
bool selectStatus = false;
ActionRowItem({
Key? key,
this.icon,
this.selectIcon,
this.onTap,
this.loadingStatus,
this.text,
required this.selectStatus,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: selectStatus
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.6)
: Theme.of(context).highlightColor.withOpacity(0.2),
borderRadius: const BorderRadius.all(Radius.circular(30)),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTap!(),
child: Padding(
padding: const EdgeInsets.fromLTRB(13, 6.5, 15, 6.3),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon!.icon!,
size: 13,
color: selectStatus
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSecondaryContainer),
const SizedBox(width: 6),
],
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text ?? '',
style: TextStyle(
color: selectStatus
? Theme.of(context).colorScheme.primary
: null,
fontSize:
Theme.of(context).textTheme.labelMedium?.fontSize),
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,140 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/utils/utils.dart';
class IntroDetail extends StatelessWidget {
var videoDetail;
IntroDetail({
Key? key,
this.videoDetail,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(left: 14, right: 14),
height: 570,
child: Column(
children: [
Container(
height: 25,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 40,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onSecondaryContainer
.withOpacity(0.5),
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
videoDetail!.title,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
letterSpacing: 0.5, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Row(
children: [
const SizedBox(width: 2),
StatView(
theme: 'black',
view: videoDetail!.stat!.view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'black',
danmu: videoDetail!.stat!.danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(videoDetail!.pubdate,
formatType: 'detail'),
style: const TextStyle(fontSize: 12),
),
],
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: SelectableRegion(
magnifierConfiguration:
const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(videoDetail!.bvid!),
const SizedBox(height: 4),
Text.rich(
TextSpan(
children: [
buildContent(context, videoDetail!),
],
),
),
],
),
),
),
],
),
),
)
],
));
}
InlineSpan buildContent(BuildContext context, content) {
String desc = content.desc;
List descV2 = content.descV2;
// type
// 1 普通文本
// 2 @用户
List<InlineSpan> spanChilds = [];
if (descV2.isNotEmpty) {
for (var i = 0; i < descV2.length; i++) {
if (descV2[i].type == 1) {
spanChilds.add(TextSpan(text: descV2[i].rawText));
} else if (descV2[i].type == 2) {
spanChilds.add(
TextSpan(
text: '@${descV2[i].rawText}',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
recognizer: TapGestureRecognizer()
..onTap = () {
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
Get.toNamed(
'/member?mid=${descV2[i].bizId}',
arguments: {'face': '', 'heroTag': heroTag},
);
},
),
);
}
}
} else {
spanChilds.add(TextSpan(text: desc));
}
return TextSpan(children: spanChilds);
}
}

View File

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
class MenuRow extends StatelessWidget {
bool? loadingStatus;
MenuRow({
Key? key,
this.loadingStatus,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(top: 9, bottom: 9, left: 12),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: [
actionRowLineItem(
context,
() => {},
loadingStatus,
'推荐',
selectStatus: true,
),
const SizedBox(width: 8),
actionRowLineItem(
context,
() => {},
loadingStatus,
'弹幕',
selectStatus: false,
),
const SizedBox(width: 8),
actionRowLineItem(
context,
() => {},
loadingStatus,
'评论列表',
selectStatus: false,
),
const SizedBox(width: 8),
actionRowLineItem(
context,
() => {},
loadingStatus,
'播放列表',
selectStatus: false,
),
]),
),
);
}
Widget actionRowLineItem(
context, Function? onTap, bool? loadingStatus, String? text,
{bool selectStatus = false}) {
return Material(
color: selectStatus
? Theme.of(context).highlightColor.withOpacity(0.2)
: Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(30)),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTap!(),
child: Container(
padding: const EdgeInsets.fromLTRB(13, 5.5, 13, 5.5),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(
color: selectStatus
? Colors.transparent
: Theme.of(context).highlightColor.withOpacity(0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text!,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium?.fontSize),
),
),
],
),
),
),
);
}
}

View File

@ -5,6 +5,8 @@ import 'package:flutter_meedu_media_kit/meedu_player.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/sliver_header.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
import 'package:pilipala/pages/video/detail/reply/index.dart';
import 'package:pilipala/pages/video/detail/controller.dart';
import 'package:pilipala/pages/video/detail/introduction/index.dart';
@ -234,7 +236,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
children: [
Container(
width: double.infinity,
height: 45,
height: 0,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
@ -254,8 +256,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
() => TabBar(
controller: videoDetailController.tabCtr,
dividerColor: Colors.transparent,
// indicatorColor:
// Theme.of(context).colorScheme.background,
indicatorColor:
Theme.of(context).colorScheme.background,
tabs: videoDetailController.tabs
.map((String name) => Tab(text: name))
.toList(),
@ -271,11 +273,19 @@ class _VideoDetailPageState extends State<VideoDetailPage>
children: [
Builder(
builder: (context) {
return const CustomScrollView(
key: PageStorageKey<String>('简介'),
return CustomScrollView(
key: const PageStorageKey<String>('简介'),
slivers: <Widget>[
VideoIntroPanel(),
RelatedVideoPanel(),
const VideoIntroPanel(),
SliverPersistentHeader(
floating: true,
pinned: true,
delegate: SliverHeaderDelegate(
height: 50,
child: MenuRow(loadingStatus: false),
),
),
const RelatedVideoPanel(),
],
);
},