mod: show fullscreen action item

Closes #367

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-03-05 21:49:57 +08:00
parent dc1451c3af
commit fac3c19d3f
8 changed files with 559 additions and 394 deletions

View File

@@ -2126,6 +2126,13 @@ List<SettingsModel> get extraSettings => [
GStorage.slideDismissReplyPage = value; GStorage.slideDismissReplyPage = value;
}, },
), ),
SettingsModel(
settingsType: SettingsType.sw1tch,
title: '全屏展示点赞/投币/收藏等操作按钮',
leading: Icon(MdiIcons.dotsHorizontalCircleOutline),
setKey: SettingBoxKey.showFSActionItem,
defaultVal: true,
),
SettingsModel( SettingsModel(
settingsType: SettingsType.sw1tch, settingsType: SettingsType.sw1tch,
enableFeedback: true, enableFeedback: true,

View File

@@ -861,4 +861,24 @@ class VideoIntroController extends GetxController
} }
return res; return res;
} }
// 收藏
showFavBottomSheet(BuildContext context, {type = 'tap'}) {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
// 快速收藏 &
// 点按 收藏至默认文件夹
// 长按选择文件夹
if (enableQuickFav) {
if (type == 'tap') {
actionFavVideo(type: 'default');
} else {
Utils.showFavBottomSheet(context: context, ctr: this);
}
} else if (type != 'longPress') {
Utils.showFavBottomSheet(context: context, ctr: this);
}
}
} }

View File

@@ -232,26 +232,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
} }
} }
// 收藏
showFavBottomSheet({type = 'tap'}) {
if (videoIntroController.userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
// 快速收藏 &
// 点按 收藏至默认文件夹
// 长按选择文件夹
if (videoIntroController.enableQuickFav) {
if (type == 'tap') {
videoIntroController.actionFavVideo(type: 'default');
} else {
Utils.showFavBottomSheet(context: context, ctr: videoIntroController);
}
} else if (type != 'longPress') {
Utils.showFavBottomSheet(context: context, ctr: videoIntroController);
}
}
// 视频介绍 // 视频介绍
showIntroDetail() { showIntroDetail() {
if (widget.loadingStatus) { if (widget.loadingStatus) {
@@ -857,7 +837,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
); );
} }
Widget actionGrid(BuildContext context, videoIntroController) { Widget actionGrid(
BuildContext context, VideoIntroController videoIntroController) {
return LayoutBuilder( return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
return Container( return Container(
@@ -896,14 +877,15 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
Obx( Obx(
() => ActionItem( () => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsDown), icon: const Icon(FontAwesomeIcons.thumbsDown),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown), selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: () => onTap: () =>
handleState(videoIntroController.actionDislikeVideo), handleState(videoIntroController.actionDislikeVideo),
selectStatus: videoIntroController.hasDislike.value, selectStatus: videoIntroController.hasDislike.value,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '点踩', semanticsLabel: '点踩',
text: "点踩"), text: "点踩",
),
), ),
// ActionItem( // ActionItem(
// icon: const Icon(FontAwesomeIcons.clock), // icon: const Icon(FontAwesomeIcons.clock),
@@ -931,8 +913,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
key: _favKey, key: _favKey,
icon: const Icon(FontAwesomeIcons.star), icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar), selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(), onTap: () => videoIntroController.showFavBottomSheet(context),
onLongPress: () => showFavBottomSheet(type: 'longPress'), onLongPress: () => videoIntroController
.showFavBottomSheet(context, type: 'longPress'),
selectStatus: videoIntroController.hasFav.value, selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '收藏', semanticsLabel: '收藏',
@@ -943,31 +926,37 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
), ),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.comment), icon: const Icon(FontAwesomeIcons.comment),
onTap: () => videoDetailCtr.tabCtr onTap: () => videoDetailCtr.tabCtr
.animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1), .animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1),
selectStatus: false, selectStatus: false,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '评论', semanticsLabel: '评论',
text: !widget.loadingStatus text: !widget.loadingStatus
? Utils.numFormat(videoDetail.stat!.reply!) ? Utils.numFormat(videoDetail.stat!.reply!)
: '评论'), : '评论',
),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare), icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(), onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false, selectStatus: false,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '分享', semanticsLabel: '分享',
text: !widget.loadingStatus text: !widget.loadingStatus
? Utils.numFormat(videoDetail.stat!.share!) ? Utils.numFormat(videoDetail.stat!.share!)
: '分享'), : '分享',
),
], ],
), ),
); );
}); });
} }
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) { Widget actionRow(
BuildContext context,
VideoIntroController videoIntroController,
VideoDetailController videoDetailCtr,
) {
return Row(children: <Widget>[ return Row(children: <Widget>[
Obx( Obx(
() => ActionRowItem( () => ActionRowItem(
@@ -994,8 +983,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Obx( Obx(
() => ActionRowItem( () => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart), icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(), onTap: () => videoIntroController.showFavBottomSheet(context),
onLongPress: () => showFavBottomSheet(type: 'longPress'), onLongPress: () => videoIntroController.showFavBottomSheet(context,
type: 'longPress'),
selectStatus: videoIntroController.hasFav.value, selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus text: !widget.loadingStatus

View File

@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/feed_back.dart';
class ActionItem extends StatefulWidget { class ActionItem extends StatefulWidget {
final Icon? icon; final Icon icon;
final Icon? selectIcon; final Icon? selectIcon;
final Function? onTap; final Function? onTap;
final Function? onLongPress; final Function? onLongPress;
@@ -16,10 +16,11 @@ class ActionItem extends StatefulWidget {
final bool needAnim; final bool needAnim;
final bool hasOneThree; final bool hasOneThree;
final Function? callBack; final Function? callBack;
final bool? expand;
const ActionItem({ const ActionItem({
super.key, super.key,
this.icon, required this.icon,
this.selectIcon, this.selectIcon,
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
@@ -30,6 +31,7 @@ class ActionItem extends StatefulWidget {
this.hasOneThree = false, this.hasOneThree = false,
this.callBack, this.callBack,
required this.semanticsLabel, required this.semanticsLabel,
this.expand,
}); });
@override @override
@@ -113,8 +115,10 @@ class ActionItemState extends State<ActionItem> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return widget.expand == false ? _buildItem : Expanded(child: _buildItem);
child: Semantics( }
Widget get _buildItem => Semantics(
label: (widget.text ?? "") + label: (widget.text ?? "") +
(widget.selectStatus ? "" : "") + (widget.selectStatus ? "" : "") +
widget.semanticsLabel, widget.semanticsLabel,
@@ -155,42 +159,42 @@ class ActionItemState extends State<ActionItem> with TickerProviderStateMixin {
Icon( Icon(
widget.selectStatus widget.selectStatus
? widget.selectIcon!.icon! ? widget.selectIcon!.icon!
: widget.icon!.icon!, : widget.icon.icon,
size: 18, size: 18,
color: widget.selectStatus color: widget.selectStatus
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline, : widget.icon.color ??
Theme.of(context).colorScheme.outline,
), ),
], ],
), ),
AnimatedOpacity( if (widget.text != null)
opacity: widget.loadingStatus! ? 0 : 1, AnimatedOpacity(
duration: const Duration(milliseconds: 200), opacity: widget.loadingStatus! ? 0 : 1,
child: AnimatedSwitcher( duration: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 300), child: AnimatedSwitcher(
transitionBuilder: duration: const Duration(milliseconds: 300),
(Widget child, Animation<double> animation) { transitionBuilder:
return ScaleTransition(scale: animation, child: child); (Widget child, Animation<double> animation) {
}, return ScaleTransition(scale: animation, child: child);
child: Text( },
widget.text ?? '', child: Text(
key: ValueKey<String>(widget.text ?? ''), widget.text!,
style: TextStyle( key: ValueKey<String>(widget.text ?? ''),
color: widget.selectStatus style: TextStyle(
? Theme.of(context).colorScheme.primary color: widget.selectStatus
: Theme.of(context).colorScheme.outline, ? Theme.of(context).colorScheme.primary
fontSize: : Theme.of(context).colorScheme.outline,
Theme.of(context).textTheme.labelSmall!.fontSize), fontSize:
semanticsLabel: "", Theme.of(context).textTheme.labelSmall!.fontSize),
semanticsLabel: "",
),
), ),
), ),
),
], ],
), ),
), ),
), );
);
}
} }
class _ArcPainter extends CustomPainter { class _ArcPainter extends CustomPainter {

View File

@@ -5,6 +5,7 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/models/common/super_resolution_type.dart'; import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/pages/setting/widgets/switch_item.dart'; import 'package:PiliPlus/pages/setting/widgets/switch_item.dart';
import 'package:PiliPlus/pages/video/detail/introduction/widgets/action_item.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
@@ -28,7 +29,6 @@ import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/services/shutdown_timer_service.dart'; import 'package:PiliPlus/services/shutdown_timer_service.dart';
import '../../../../models/video/play/CDN.dart'; import '../../../../models/video/play/CDN.dart';
import '../../../../models/video_detail_res.dart';
import '../../../setting/widgets/select_dialog.dart'; import '../../../setting/widgets/select_dialog.dart';
import '../introduction/index.dart'; import '../introduction/index.dart';
import 'package:marquee/marquee.dart'; import 'package:marquee/marquee.dart';
@@ -61,13 +61,14 @@ class _HeaderControlState extends State<HeaderControl> {
double buttonSpace = 8; double buttonSpace = 8;
String get heroTag => widget.heroTag; String get heroTag => widget.heroTag;
late VideoIntroController videoIntroController; late VideoIntroController videoIntroController;
late VideoDetailData videoDetail;
late bool horizontalScreen; late bool horizontalScreen;
RxString now = ''.obs; RxString now = ''.obs;
Timer? clock; Timer? clock;
late String defaultCDNService; late String defaultCDNService;
bool get isFullScreen => widget.controller.isFullScreen.value; bool get isFullScreen => widget.controller.isFullScreen.value;
Box get setting => GStorage.setting; Box get setting => GStorage.setting;
late final _coinKey = GlobalKey<ActionItemState>();
late final _favKey = GlobalKey<ActionItemState>();
@override @override
void initState() { void initState() {
@@ -1746,346 +1747,483 @@ class _HeaderControlState extends State<HeaderControl> {
}); });
} }
@override Widget _buildHeader(bool showFSActionItem) => AppBar(
Widget build(BuildContext context) {
final plPlayerController = widget.controller;
// final bool isLandscape =
// MediaQuery.of(context).orientation == Orientation.landscape;
bool equivalentFullScreen = !isFullScreen &&
!horizontalScreen &&
MediaQuery.of(context).orientation == Orientation.landscape;
return LayoutBuilder(builder: (context, boxConstraints) {
return AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.white, foregroundColor: Colors.white,
primary: false, primary: false,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: Row( toolbarHeight: showFSActionItem && isFullScreen ? 112 : null,
flexibleSpace: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox( const SizedBox(height: 11),
width: 42, Row(
height: 34, children: [
child: IconButton( SizedBox(
tooltip: '返回', width: 42,
icon: const Icon( height: 34,
FontAwesomeIcons.arrowLeft, child: IconButton(
size: 15, tooltip: '返回',
color: Colors.white, icon: const Icon(
), FontAwesomeIcons.arrowLeft,
onPressed: () { size: 15,
if (isFullScreen) { color: Colors.white,
widget.controller.triggerFullScreen(status: false); ),
} else if (MediaQuery.of(context).orientation == onPressed: () {
Orientation.landscape && if (isFullScreen) {
!horizontalScreen) { widget.controller.triggerFullScreen(status: false);
verticalScreenForTwoSeconds(); } else if (MediaQuery.of(context).orientation ==
} else { Orientation.landscape &&
Get.back(); !horizontalScreen) {
} verticalScreenForTwoSeconds();
}, } else {
), Get.back();
), }
if (!isFullScreen || },
MediaQuery.of(context).orientation != Orientation.portrait)
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '返回主页',
icon: const Icon(
FontAwesomeIcons.house,
size: 15,
color: Colors.white,
), ),
onPressed: () {
widget.videoDetailCtr.plPlayerController.backToHome = true;
Get.until((route) => route.isFirst);
},
), ),
), if (!isFullScreen ||
if ((videoIntroController.videoDetail.value.title != null) && MediaQuery.of(context).orientation != Orientation.portrait)
(isFullScreen || equivalentFullScreen)) SizedBox(
Column( width: 42,
crossAxisAlignment: CrossAxisAlignment.start, height: 34,
children: [ child: IconButton(
ConstrainedBox( tooltip: '返回主页',
constraints: BoxConstraints( icon: const Icon(
maxWidth: boxConstraints.maxWidth / 2 - 60, FontAwesomeIcons.house,
maxHeight: 25), size: 15,
child: Obx( color: Colors.white,
() => Marquee( ),
text: videoIntroController.videoDetail.value.title!, onPressed: () {
style: const TextStyle( widget.videoDetailCtr.plPlayerController.backToHome =
color: Colors.white, true;
fontSize: 16, Get.until((route) => route.isFirst);
},
),
),
if ((videoIntroController.videoDetail.value.title != null) &&
(isFullScreen ||
(!isFullScreen &&
!horizontalScreen &&
MediaQuery.of(context).orientation ==
Orientation.landscape)))
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
constraints: BoxConstraints(maxHeight: 25),
child: Obx(
() => Marquee(
text:
videoIntroController.videoDetail.value.title!,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 200,
velocity: 40,
startAfter: const Duration(seconds: 1),
showFadingOnlyWhenScrolling: true,
fadingEdgeStartFraction: 0,
fadingEdgeEndFraction: 0.1,
numberOfRounds: 1,
startPadding: 0,
accelerationDuration: const Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration:
const Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
),
),
), ),
scrollAxis: Axis.horizontal, if (videoIntroController.isShowOnlineTotal)
crossAxisAlignment: CrossAxisAlignment.start, Obx(
blankSpace: 200, () => Text(
velocity: 40, '${videoIntroController.total.value}人正在看',
startAfter: const Duration(seconds: 1), style: const TextStyle(
showFadingOnlyWhenScrolling: true, color: Colors.white,
fadingEdgeStartFraction: 0, fontSize: 11,
fadingEdgeEndFraction: 0.1, ),
numberOfRounds: 1, ),
startPadding: 0, ),
accelerationDuration: const Duration(seconds: 1), ],
accelerationCurve: Curves.linear, ),
decelerationDuration: const Duration(milliseconds: 500), )
decelerationCurve: Curves.easeOut, else
const Spacer(),
if (MediaQuery.of(context).orientation ==
Orientation.landscape &&
(isFullScreen || !horizontalScreen)) ...[
// const Spacer(),
// show current datetime
Obx(
() => Text(
now.value,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
), ),
), ),
), ),
if (videoIntroController.isShowOnlineTotal) const SizedBox(width: 15),
Obx(
() => Text(
'${videoIntroController.total.value}人正在看',
style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
),
),
], ],
), // ComBtn(
const Spacer(), // icon: const Icon(
if (MediaQuery.of(context).orientation == Orientation.landscape && // FontAwesomeIcons.cropSimple,
(isFullScreen || !horizontalScreen)) ...[ // size: 15,
// const Spacer(), // color: Colors.white,
// show current datetime // ),
Obx( // fuc: () => _.screenshot(),
() => Text( // ),
now.value, if (widget.videoDetailCtr.enableSponsorBlock == true)
style: const TextStyle( SizedBox(
color: Colors.white, width: 42,
fontSize: 13, height: 34,
child: IconButton(
tooltip: '提交片段',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => widget.videoDetailCtr.onBlock(context),
icon: Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.shield_outlined,
size: 19,
color: Colors.white,
),
Icon(
Icons.play_arrow_rounded,
size: 13,
color: Colors.white,
),
],
),
),
),
Obx(
() => widget.videoDetailCtr.segmentList.isNotEmpty == true
? SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '片段信息',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () =>
widget.videoDetailCtr.showSBDetail(context),
icon: Icon(
MdiIcons.advertisements,
size: 19,
color: Colors.white,
),
),
)
: const SizedBox.shrink(),
),
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '发弹幕',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: widget.videoDetailCtr.showShootDanmakuSheet,
icon: const Icon(
Icons.comment_outlined,
size: 19,
color: Colors.white,
),
), ),
), ),
), SizedBox(
const SizedBox(width: 15), width: 42,
], height: 34,
// ComBtn( child: Obx(
// icon: const Icon( () => IconButton(
// FontAwesomeIcons.cropSimple, tooltip:
// size: 15, "${plPlayerController.isOpenDanmu.value ? '关闭' : '开启'}弹幕",
// color: Colors.white, style: ButtonStyle(
// ), padding: WidgetStateProperty.all(EdgeInsets.zero),
// fuc: () => _.screenshot(), ),
// ), onPressed: () {
if (widget.videoDetailCtr.enableSponsorBlock == true) plPlayerController.isOpenDanmu.value =
SizedBox( !plPlayerController.isOpenDanmu.value;
width: 42, setting.put(SettingBoxKey.enableShowDanmaku,
height: 34, plPlayerController.isOpenDanmu.value);
child: IconButton( // SmartDialog.showToast(
tooltip: '提交片段', // "已${plPlayerController.isOpenDanmu.value ? '开启' : '关闭'}弹幕",
style: ButtonStyle( // displayTime: const Duration(seconds: 1));
padding: WidgetStateProperty.all(EdgeInsets.zero), },
), icon: Icon(
onPressed: () => widget.videoDetailCtr.onBlock(context), plPlayerController.isOpenDanmu.value
icon: Stack( ? Icons.subtitles_outlined
alignment: Alignment.center, : Icons.subtitles_off_outlined,
children: [
Icon(
Icons.shield_outlined,
size: 19, size: 19,
color: Colors.white, color: Colors.white,
), ),
Icon( ),
Icons.play_arrow_rounded, ),
size: 13, ),
color: Colors.white, if (Platform.isAndroid)
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '画中画',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
), ),
], onPressed: () async {
), bool canUsePiP = widget.floating != null &&
), await widget.floating!.isPipAvailable;
), widget.controller.hiddenControls(false);
Obx( if (canUsePiP) {
() => widget.videoDetailCtr.segmentList.isNotEmpty == true bool enableBackgroundPlay = setting.get(
? SizedBox( SettingBoxKey.enableBackgroundPlay,
width: 42, defaultValue: true);
height: 34, if (!enableBackgroundPlay && context.mounted) {
child: IconButton( // SmartDialog.showToast('建议开启【后台播放】功能\n避免画中画没有暂停按钮');
tooltip: '片段信息', // await Future.delayed(const Duration(seconds: 2), () {
style: ButtonStyle( // });
padding: WidgetStateProperty.all(EdgeInsets.zero), ScaffoldMessenger.of(context).showSnackBar(
), SnackBar(
onPressed: () => content: Column(
widget.videoDetailCtr.showSBDetail(context),
icon: Icon(
MdiIcons.advertisements,
size: 19,
color: Colors.white,
),
),
)
: const SizedBox.shrink(),
),
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '发弹幕',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: widget.videoDetailCtr.showShootDanmakuSheet,
icon: const Icon(
Icons.comment_outlined,
size: 19,
color: Colors.white,
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => IconButton(
tooltip:
"${plPlayerController.isOpenDanmu.value ? '关闭' : '开启'}弹幕",
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
plPlayerController.isOpenDanmu.value =
!plPlayerController.isOpenDanmu.value;
setting.put(SettingBoxKey.enableShowDanmaku,
plPlayerController.isOpenDanmu.value);
// SmartDialog.showToast(
// "已${plPlayerController.isOpenDanmu.value ? '开启' : '关闭'}弹幕",
// displayTime: const Duration(seconds: 1));
},
icon: Icon(
plPlayerController.isOpenDanmu.value
? Icons.subtitles_outlined
: Icons.subtitles_off_outlined,
size: 19,
color: Colors.white,
),
),
),
),
if (Platform.isAndroid)
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '画中画',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
bool canUsePiP = widget.floating != null &&
await widget.floating!.isPipAvailable;
widget.controller.hiddenControls(false);
if (canUsePiP) {
bool enableBackgroundPlay = setting.get(
SettingBoxKey.enableBackgroundPlay,
defaultValue: true);
if (!enableBackgroundPlay && context.mounted) {
// SmartDialog.showToast('建议开启【后台播放】功能\n避免画中画没有暂停按钮');
// await Future.delayed(const Duration(seconds: 2), () {
// });
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Column(
children: [
const Row(
children: [ children: [
Icon( const Row(
Icons.check, children: [
color: Colors.green, Icon(
Icons.check,
color: Colors.green,
),
SizedBox(width: 10),
Text('画中画',
style: TextStyle(
fontSize: 15, height: 1.5))
],
), ),
SizedBox(width: 10), const SizedBox(height: 10),
Text('画中画', const Text(
'建议开启【后台音频服务】\n'
'避免画中画没有暂停按钮',
style: TextStyle( style: TextStyle(
fontSize: 15, height: 1.5)) fontSize: 12.5, height: 1.5)),
Row(children: [
TextButton(
style: ButtonStyle(
foregroundColor:
WidgetStateProperty.resolveWith(
(states) {
return Theme.of(context)
.snackBarTheme
.actionTextColor;
}),
),
onPressed: () async {
plPlayerController
.setBackgroundPlay(true);
SmartDialog.showToast("请重新载入本页面刷新");
// Get.back();
},
child: const Text('启用后台音频服务')),
const SizedBox(width: 10),
TextButton(
style: ButtonStyle(
foregroundColor:
WidgetStateProperty.resolveWith(
(states) {
return Theme.of(context)
.snackBarTheme
.actionTextColor;
}),
),
onPressed: () {},
child: const Text('不启用'))
])
], ],
), ),
const SizedBox(height: 10), duration: const Duration(seconds: 2),
const Text( showCloseIcon: true,
'建议开启【后台音频服务】\n' ),
'避免画中画没有暂停按钮', );
style: await Future.delayed(
TextStyle(fontSize: 12.5, height: 1.5)), const Duration(seconds: 3), () {});
Row(children: [ }
TextButton( final Rational aspectRatio = Rational(
style: ButtonStyle( widget
foregroundColor: .videoDetailCtr.data.dash!.video!.first.width!,
WidgetStateProperty.resolveWith( widget
(states) { .videoDetailCtr.data.dash!.video!.first.height!,
return Theme.of(context) );
.snackBarTheme if (!context.mounted) return;
.actionTextColor; await widget.floating!.enable(EnableManual(
}), aspectRatio: aspectRatio,
), ));
onPressed: () async { } else {}
plPlayerController },
.setBackgroundPlay(true); icon: const Icon(
SmartDialog.showToast("请重新载入本页面刷新"); Icons.picture_in_picture_outlined,
// Get.back(); size: 19,
}, color: Colors.white,
child: const Text('启用后台音频服务')), ),
const SizedBox(width: 10), ),
TextButton( ),
style: ButtonStyle( SizedBox(
foregroundColor: width: 42,
WidgetStateProperty.resolveWith( height: 34,
(states) { child: IconButton(
return Theme.of(context) tooltip: "更多设置",
.snackBarTheme style: ButtonStyle(
.actionTextColor; padding: WidgetStateProperty.all(EdgeInsets.zero),
}), ),
), onPressed: showSettingSheet,
onPressed: () {}, icon: const Icon(
child: const Text('不启用')) Icons.more_vert_outlined,
]) size: 19,
], color: Colors.white,
), ),
duration: const Duration(seconds: 2),
showCloseIcon: true,
),
);
await Future.delayed(const Duration(seconds: 3), () {});
}
final Rational aspectRatio = Rational(
widget.videoDetailCtr.data.dash!.video!.first.width!,
widget.videoDetailCtr.data.dash!.video!.first.height!,
);
if (!context.mounted) return;
await widget.floating!.enable(EnableManual(
aspectRatio: aspectRatio,
));
} else {}
},
icon: const Icon(
Icons.picture_in_picture_outlined,
size: 19,
color: Colors.white,
), ),
), ),
), ],
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: "更多设置",
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: showSettingSheet,
icon: const Icon(
Icons.more_vert_outlined,
size: 19,
color: Colors.white,
),
),
), ),
if (showFSActionItem)
isFullScreen
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
icon: const Icon(
FontAwesomeIcons.thumbsUp,
color: Colors.white,
),
selectIcon:
const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: videoIntroController.actionLikeVideo,
onLongPress: videoIntroController.actionOneThree,
selectStatus: videoIntroController.hasLike.value,
semanticsLabel: '点赞',
needAnim: true,
hasOneThree: videoIntroController.hasLike.value &&
videoIntroController.hasCoin.value &&
videoIntroController.hasFav.value,
callBack: (start) {
if (start) {
_coinKey.currentState?.controller?.forward();
_favKey.currentState?.controller?.forward();
} else {
_coinKey.currentState?.controller?.reverse();
_favKey.currentState?.controller?.reverse();
}
},
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
icon: const Icon(
FontAwesomeIcons.thumbsDown,
color: Colors.white,
),
selectIcon:
const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: videoIntroController.actionDislikeVideo,
selectStatus:
videoIntroController.hasDislike.value,
semanticsLabel: '点踩',
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
key: _coinKey,
expand: false,
icon: const Icon(
FontAwesomeIcons.b,
color: Colors.white,
),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: videoIntroController.actionCoinVideo,
selectStatus: videoIntroController.hasCoin.value,
semanticsLabel: '投币',
needAnim: true,
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
key: _favKey,
expand: false,
icon: const Icon(
FontAwesomeIcons.star,
color: Colors.white,
),
selectIcon:
const Icon(FontAwesomeIcons.solidStar),
onTap: () => videoIntroController
.showFavBottomSheet(context),
onLongPress: () => videoIntroController
.showFavBottomSheet(context,
type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
semanticsLabel: '收藏',
needAnim: true,
),
),
),
SizedBox(
width: 42,
height: 34,
child: ActionItem(
expand: false,
icon: const Icon(
FontAwesomeIcons.shareFromSquare,
color: Colors.white,
),
onTap: videoIntroController.actionShareVideo,
selectStatus: false,
semanticsLabel: '分享',
),
),
],
)
: const SizedBox.shrink(),
], ],
), ),
); );
});
PlPlayerController get plPlayerController => widget.controller;
@override
Widget build(BuildContext context) {
// final bool isLandscape =
// MediaQuery.of(context).orientation == Orientation.landscape;
return plPlayerController.showFSActionItem
? Obx(() => _buildHeader(true))
: _buildHeader(false);
} }
} }

View File

@@ -254,6 +254,8 @@ class PlPlayerController {
/// 弹幕开关 /// 弹幕开关
Rx<bool> isOpenDanmu = false.obs; Rx<bool> isOpenDanmu = false.obs;
late final showFSActionItem = GStorage.showFSActionItem;
/// 弹幕权重 /// 弹幕权重
int danmakuWeight = 0; int danmakuWeight = 0;
int filterCount = 0; int filterCount = 0;

View File

@@ -28,7 +28,7 @@ class AppBarAni extends StatelessWidget implements PreferredSizeWidget {
parent: controller, parent: controller,
curve: Curves.linear, curve: Curves.linear,
)), )),
child: Container( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: position! == 'top' gradient: position! == 'top'
? const LinearGradient( ? const LinearGradient(

View File

@@ -394,6 +394,9 @@ class GStorage {
static bool slideDismissReplyPage = GStorage.setting static bool slideDismissReplyPage = GStorage.setting
.get(SettingBoxKey.slideDismissReplyPage, defaultValue: Platform.isIOS); .get(SettingBoxKey.slideDismissReplyPage, defaultValue: Platform.isIOS);
static bool get showFSActionItem =>
GStorage.setting.get(SettingBoxKey.showFSActionItem, defaultValue: true);
static List<double> get dynamicDetailRatio => List<double>.from(setting static List<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -649,6 +652,7 @@ class SettingBoxKey {
collapsibleVideoPage = 'collapsibleVideoPage', collapsibleVideoPage = 'collapsibleVideoPage',
enableHttp2 = 'enableHttp2', enableHttp2 = 'enableHttp2',
slideDismissReplyPage = 'slideDismissReplyPage', slideDismissReplyPage = 'slideDismissReplyPage',
showFSActionItem = 'showFSActionItem',
// Sponsor Block // Sponsor Block
enableSponsorBlock = 'enableSponsorBlock', enableSponsorBlock = 'enableSponsorBlock',