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;
},
),
SettingsModel(
settingsType: SettingsType.sw1tch,
title: '全屏展示点赞/投币/收藏等操作按钮',
leading: Icon(MdiIcons.dotsHorizontalCircleOutline),
setKey: SettingBoxKey.showFSActionItem,
defaultVal: true,
),
SettingsModel(
settingsType: SettingsType.sw1tch,
enableFeedback: true,

View File

@@ -861,4 +861,24 @@ class VideoIntroController extends GetxController
}
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() {
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(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
@@ -903,7 +884,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
selectStatus: videoIntroController.hasDislike.value,
loadingStatus: widget.loadingStatus,
semanticsLabel: '点踩',
text: "点踩"),
text: "点踩",
),
),
// ActionItem(
// icon: const Icon(FontAwesomeIcons.clock),
@@ -931,8 +913,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
key: _favKey,
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
onTap: () => videoIntroController.showFavBottomSheet(context),
onLongPress: () => videoIntroController
.showFavBottomSheet(context, type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
semanticsLabel: '收藏',
@@ -951,7 +934,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
semanticsLabel: '评论',
text: !widget.loadingStatus
? Utils.numFormat(videoDetail.stat!.reply!)
: '评论'),
: '评论',
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
@@ -960,14 +944,19 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
semanticsLabel: '分享',
text: !widget.loadingStatus
? Utils.numFormat(videoDetail.stat!.share!)
: '分享'),
: '分享',
),
],
),
);
});
}
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
Widget actionRow(
BuildContext context,
VideoIntroController videoIntroController,
VideoDetailController videoDetailCtr,
) {
return Row(children: <Widget>[
Obx(
() => ActionRowItem(
@@ -994,8 +983,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
onTap: () => videoIntroController.showFavBottomSheet(context),
onLongPress: () => videoIntroController.showFavBottomSheet(context,
type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus

View File

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

View File

@@ -5,6 +5,7 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/models/common/super_resolution_type.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/id_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/services/shutdown_timer_service.dart';
import '../../../../models/video/play/CDN.dart';
import '../../../../models/video_detail_res.dart';
import '../../../setting/widgets/select_dialog.dart';
import '../introduction/index.dart';
import 'package:marquee/marquee.dart';
@@ -61,13 +61,14 @@ class _HeaderControlState extends State<HeaderControl> {
double buttonSpace = 8;
String get heroTag => widget.heroTag;
late VideoIntroController videoIntroController;
late VideoDetailData videoDetail;
late bool horizontalScreen;
RxString now = ''.obs;
Timer? clock;
late String defaultCDNService;
bool get isFullScreen => widget.controller.isFullScreen.value;
Box get setting => GStorage.setting;
late final _coinKey = GlobalKey<ActionItemState>();
late final _favKey = GlobalKey<ActionItemState>();
@override
void initState() {
@@ -1746,22 +1747,17 @@ class _HeaderControlState extends State<HeaderControl> {
});
}
@override
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(
Widget _buildHeader(bool showFSActionItem) => AppBar(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
primary: false,
automaticallyImplyLeading: false,
title: Row(
toolbarHeight: showFSActionItem && isFullScreen ? 112 : null,
flexibleSpace: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 11),
Row(
children: [
SizedBox(
width: 42,
@@ -1799,23 +1795,30 @@ class _HeaderControlState extends State<HeaderControl> {
color: Colors.white,
),
onPressed: () {
widget.videoDetailCtr.plPlayerController.backToHome = true;
widget.videoDetailCtr.plPlayerController.backToHome =
true;
Get.until((route) => route.isFirst);
},
),
),
if ((videoIntroController.videoDetail.value.title != null) &&
(isFullScreen || equivalentFullScreen))
Column(
(isFullScreen ||
(!isFullScreen &&
!horizontalScreen &&
MediaQuery.of(context).orientation ==
Orientation.landscape)))
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: boxConstraints.maxWidth / 2 - 60,
maxHeight: 25),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
constraints: BoxConstraints(maxHeight: 25),
child: Obx(
() => Marquee(
text: videoIntroController.videoDetail.value.title!,
text:
videoIntroController.videoDetail.value.title!,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
@@ -1832,7 +1835,8 @@ class _HeaderControlState extends State<HeaderControl> {
startPadding: 0,
accelerationDuration: const Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: const Duration(milliseconds: 500),
decelerationDuration:
const Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
),
),
@@ -1849,8 +1853,11 @@ class _HeaderControlState extends State<HeaderControl> {
),
],
),
)
else
const Spacer(),
if (MediaQuery.of(context).orientation == Orientation.landscape &&
if (MediaQuery.of(context).orientation ==
Orientation.landscape &&
(isFullScreen || !horizontalScreen)) ...[
// const Spacer(),
// show current datetime
@@ -2007,8 +2014,8 @@ class _HeaderControlState extends State<HeaderControl> {
const Text(
'建议开启【后台音频服务】\n'
'避免画中画没有暂停按钮',
style:
TextStyle(fontSize: 12.5, height: 1.5)),
style: TextStyle(
fontSize: 12.5, height: 1.5)),
Row(children: [
TextButton(
style: ButtonStyle(
@@ -2047,11 +2054,14 @@ class _HeaderControlState extends State<HeaderControl> {
showCloseIcon: true,
),
);
await Future.delayed(const Duration(seconds: 3), () {});
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!,
widget
.videoDetailCtr.data.dash!.video!.first.width!,
widget
.videoDetailCtr.data.dash!.video!.first.height!,
);
if (!context.mounted) return;
await widget.floating!.enable(EnableManual(
@@ -2084,8 +2094,136 @@ class _HeaderControlState extends State<HeaderControl> {
),
],
),
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;
late final showFSActionItem = GStorage.showFSActionItem;
/// 弹幕权重
int danmakuWeight = 0;
int filterCount = 0;

View File

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

View File

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