opt video/intro page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-10 11:07:38 +08:00
parent 34c5d6812f
commit 6093848811
9 changed files with 705 additions and 756 deletions

View File

@@ -20,7 +20,6 @@ import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart';
@@ -559,7 +558,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
onPressed: () {
final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
if (!Pref.tempPlayerConf) {
if (!plPlayerController.tempPlayerConf) {
GStorage.setting.put(
SettingBoxKey.enableShowDanmaku,
newVal,

View File

@@ -4,7 +4,6 @@ import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -89,7 +88,7 @@ class BottomControl extends StatelessWidget {
onPressed: () {
final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
if (!Pref.tempPlayerConf) {
if (!plPlayerController.tempPlayerConf) {
GStorage.setting.put(
SettingBoxKey.enableShowDanmaku,
newVal,

View File

@@ -26,6 +26,7 @@ class PgcIntroPage extends StatefulWidget {
final String heroTag;
final Function showEpisodes;
final Function showIntroDetail;
final double maxWidth;
const PgcIntroPage({
super.key,
@@ -33,14 +34,16 @@ class PgcIntroPage extends StatefulWidget {
required this.heroTag,
required this.showEpisodes,
required this.showIntroDetail,
required this.maxWidth,
});
@override
State<PgcIntroPage> createState() => _PgcIntroPageState();
}
class _PgcIntroPageState extends State<PgcIntroPage>
class _PgcIntroPageState extends TripleState<PgcIntroPage>
with AutomaticKeepAliveClientMixin {
@override
late PgcIntroController introController;
late VideoDetailController videoDetailCtr;
@@ -89,12 +92,15 @@ class _PgcIntroPageState extends State<PgcIntroPage>
),
);
if (!introController.isPgc) {
sliver = SliverMainAxisGroup(
slivers: [
sliver,
?_buildBreif(item),
],
);
final breif = _buildBreif(item);
if (breif != null) {
sliver = SliverMainAxisGroup(
slivers: [
sliver,
breif,
],
);
}
}
return SliverPadding(
padding:
@@ -107,32 +113,28 @@ class _PgcIntroPageState extends State<PgcIntroPage>
Widget? _buildBreif(PgcInfoModel item) {
final img = item.brief?.img;
if (img != null && img.isNotEmpty) {
return SliverLayoutBuilder(
builder: (context, constraints) {
final maxWidth = constraints.crossAxisExtent;
double padding = max(0, maxWidth - 400);
final imgWidth = maxWidth - padding;
padding = padding / 2;
return SliverPadding(
padding: EdgeInsetsGeometry.only(
top: 10,
left: padding,
right: padding,
),
sliver: SliverMainAxisGroup(
slivers: img.map((e) {
return SliverToBoxAdapter(
child: NetworkImgLayer(
radius: 0,
src: e.url,
width: imgWidth,
height: imgWidth * e.aspectRatio,
),
);
}).toList(),
),
);
},
final maxWidth = widget.maxWidth - 2 * StyleString.safeSpace;
double padding = max(0, maxWidth - 400);
final imgWidth = maxWidth - padding;
padding = padding / 2;
return SliverPadding(
padding: EdgeInsetsGeometry.only(
top: 10,
left: padding,
right: padding,
),
sliver: SliverMainAxisGroup(
slivers: img.map((e) {
return SliverToBoxAdapter(
child: NetworkImgLayer(
radius: 0,
src: e.url,
width: imgWidth,
height: imgWidth * e.aspectRatio,
),
);
}).toList(),
),
);
}
return null;
@@ -404,75 +406,65 @@ class _PgcIntroPageState extends State<PgcIntroPage>
) {
return SizedBox(
height: 48,
child: TripleBuilder(
introController: introController,
builder: (context, tripleAnimation, onStartTriple, onCancelTriple) {
return Row(
children: [
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: () => introController.handleAction(
introController.actionLikeVideo,
),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
text: NumUtil.numFormat(item.stat!.like),
onStartTriple: onStartTriple,
onCancelTriple: onCancelTriple,
),
child: Row(
children: [
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
text: NumUtil.numFormat(item.stat!.like),
onStartTriple: onStartTriple,
onCancelTriple: onCancelTriple,
),
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: introController.actionCoinVideo,
selectStatus: introController.hasCoin,
semanticsLabel: '投币',
text: NumUtil.numFormat(item.stat!.coin),
),
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => introController.showFavBottomSheet(context),
onLongPress: () => introController.showFavBottomSheet(
context,
isLongPress: true,
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => introController.handleAction(
introController.actionCoinVideo,
),
selectStatus: introController.hasCoin,
semanticsLabel: '投币',
text: NumUtil.numFormat(item.stat!.coin),
),
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => introController.showFavBottomSheet(context),
onLongPress: () => introController.showFavBottomSheet(
context,
isLongPress: true,
),
selectStatus: introController.hasFav.value,
semanticsLabel: '收藏',
text: NumUtil.numFormat(item.stat!.favorite),
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
selectIcon: const Icon(FontAwesomeIcons.solidClock),
onTap: () =>
introController.handleAction(introController.viewLater),
selectStatus: introController.hasLater.value,
semanticsLabel: '再看',
text: '再看',
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => introController.actionShareVideo(context),
selectStatus: false,
semanticsLabel: '转发',
text: NumUtil.numFormat(item.stat!.share),
),
],
);
},
selectStatus: introController.hasFav.value,
semanticsLabel: '收藏',
text: NumUtil.numFormat(item.stat!.favorite),
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
selectIcon: const Icon(FontAwesomeIcons.solidClock),
onTap: () =>
introController.handleAction(introController.viewLater),
selectStatus: introController.hasLater.value,
semanticsLabel: '再看',
text: '再看',
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => introController.actionShareVideo(context),
selectStatus: false,
semanticsLabel: '转发',
text: NumUtil.numFormat(item.stat!.share),
),
],
),
);
}

View File

@@ -41,18 +41,21 @@ class UgcIntroPanel extends StatefulWidget {
required this.showAiBottomSheet,
required this.showEpisodes,
required this.onShowMemberPage,
required this.isHorizontal,
});
final String heroTag;
final Function showAiBottomSheet;
final Function showEpisodes;
final ValueChanged<int?> onShowMemberPage;
final bool isHorizontal;
@override
State<UgcIntroPanel> createState() => _UgcIntroPanelState();
}
class _UgcIntroPanelState extends State<UgcIntroPanel>
class _UgcIntroPanelState extends TripleState<UgcIntroPanel>
with AutomaticKeepAliveClientMixin {
@override
late UgcIntroController introController;
late final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: widget.heroTag);
@@ -75,267 +78,258 @@ class _UgcIntroPanelState extends State<UgcIntroPanel>
sizeCurve: Curves.linear,
);
final isPortrait = context.isPortrait;
return SliverLayoutBuilder(
builder: (context, constraints) {
bool isHorizontal =
!isPortrait &&
constraints.crossAxisExtent >
constraints.viewportMainAxisExtent * 1.25;
return SliverPadding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
top: 10,
),
sliver: Obx(
() {
VideoDetailData videoDetail = introController.videoDetail.value;
bool isLoading = videoDetail.bvid == null;
int? mid = videoDetail.owner?.mid;
return SliverToBoxAdapter(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (isLoading) {
return;
}
feedBack();
introController.expandableCtr.toggle();
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {},
child: Row(
children: [
if (videoDetail.staff.isNullOrEmpty) ...[
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: _buildAvatar(
theme,
() {
if (mid != null) {
feedBack();
if (!isPortrait &&
introController
.horizontalMemberPage) {
widget.onShowMemberPage(mid);
} else {
Get.toNamed(
'/member?mid=$mid&from_view_aid=${videoDetailCtr.aid}',
);
}
}
},
),
),
),
followButton(context, theme),
] else
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: ReloadScrollPhysics(
controller: introController,
),
child: Row(
spacing: 25,
children: videoDetail.staff!
.map(
(e) => _buildStaff(
theme,
isPortrait,
mid,
e,
),
)
.toList(),
),
),
),
if (isHorizontal) ...[
const SizedBox(width: 10),
Expanded(
child: actionGrid(
context,
isLoading,
videoDetail,
introController,
),
),
],
],
),
),
const SizedBox(height: 8),
if (isLoading)
_buildVideoTitle(theme, videoDetail)
else
ExpandablePanel(
controller: introController.expandableCtr,
collapsed: GestureDetector(
onLongPress: () {
Feedback.forLongPress(context);
Utils.copyText(videoDetail.title ?? '');
},
child: _buildVideoTitle(theme, videoDetail),
),
expanded: GestureDetector(
onLongPress: () {
Feedback.forLongPress(context);
Utils.copyText(videoDetail.title ?? '');
},
child: _buildVideoTitle(
theme,
videoDetail,
isExpand: true,
),
),
theme: expandTheme,
),
const SizedBox(height: 8),
Stack(
clipBehavior: Clip.none,
children: [
_buildInfo(theme, videoDetail),
if (introController.enableAi) _aiBtn,
],
),
if (videoDetail.argueInfo?.argueMsg?.isNotEmpty == true &&
introController.showArgueMsg) ...[
const SizedBox(height: 2),
Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
size: 13,
Icons.error_outline,
color: theme.colorScheme.outline,
),
),
const WidgetSpan(child: SizedBox(width: 2)),
TextSpan(
text: '${videoDetail.argueInfo!.argueMsg}',
),
],
),
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
],
ExpandablePanel(
controller: introController.expandableCtr,
collapsed: const SizedBox.shrink(),
expanded: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
GestureDetector(
onTap: () =>
Utils.copyText('${videoDetail.bvid}'),
child: Text(
videoDetail.bvid ?? '',
style: TextStyle(
fontSize: 14,
color: theme.colorScheme.secondary,
),
),
),
if (videoDetail.descV2?.isNotEmpty == true) ...[
const SizedBox(height: 8),
SelectableText.rich(
style: const TextStyle(
height: 1.4,
),
TextSpan(
children: [
buildContent(theme, videoDetail),
],
),
),
],
Obx(() {
final videoTags = introController.videoTags.value;
if (videoTags.isNullOrEmpty) {
return const SizedBox.shrink();
}
return _buildTags(videoTags!);
}),
],
),
theme: expandTheme,
),
Obx(
() => introController.status.value
? const SizedBox.shrink()
: Center(
child: TextButton.icon(
icon: const Icon(Icons.refresh),
onPressed: () {
introController
..status.value = true
..queryVideoIntro();
if (videoDetailCtr.videoUrl.isNullOrEmpty &&
!videoDetailCtr.isQuerying) {
videoDetailCtr.queryVideoUrl();
final isHorizontal = !isPortrait && widget.isHorizontal;
return SliverPadding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
top: 10,
),
sliver: Obx(
() {
VideoDetailData videoDetail = introController.videoDetail.value;
bool isLoading = videoDetail.bvid == null;
int? mid = videoDetail.owner?.mid;
return SliverToBoxAdapter(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (isLoading) {
return;
}
feedBack();
introController.expandableCtr.toggle();
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {},
child: Row(
children: [
if (videoDetail.staff.isNullOrEmpty) ...[
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: _buildAvatar(
theme,
() {
if (mid != null) {
feedBack();
if (!isPortrait &&
introController.horizontalMemberPage) {
widget.onShowMemberPage(mid);
} else {
Get.toNamed(
'/member?mid=$mid&from_view_aid=${videoDetailCtr.aid}',
);
}
},
label: const Text("点此重新加载"),
),
}
},
),
),
),
followButton(context, theme),
] else
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: ReloadScrollPhysics(
controller: introController,
),
child: Row(
spacing: 25,
children: videoDetail.staff!
.map(
(e) => _buildStaff(
theme,
isPortrait,
mid,
e,
),
)
.toList(),
),
),
),
if (isHorizontal) ...[
const SizedBox(width: 10),
Expanded(
child: actionGrid(
context,
isLoading,
videoDetail,
introController,
),
),
],
],
),
),
const SizedBox(height: 8),
if (isLoading)
_buildVideoTitle(theme, videoDetail)
else
ExpandablePanel(
controller: introController.expandableCtr,
collapsed: GestureDetector(
onLongPress: () {
Feedback.forLongPress(context);
Utils.copyText(videoDetail.title ?? '');
},
child: _buildVideoTitle(theme, videoDetail),
),
// 点赞收藏转发 布局样式2
if (!isHorizontal) ...[
const SizedBox(height: 8),
actionGrid(
context,
isLoading,
expanded: GestureDetector(
onLongPress: () {
Feedback.forLongPress(context);
Utils.copyText(videoDetail.title ?? '');
},
child: _buildVideoTitle(
theme,
videoDetail,
introController,
isExpand: true,
),
],
// 合集
if (!isLoading &&
videoDetail.ugcSeason != null &&
(isPortrait ||
!videoDetailCtr
.plPlayerController
.horizontalSeasonPanel))
SeasonPanel(
heroTag: widget.heroTag,
showEpisodes: widget.showEpisodes,
ugcIntroController: introController,
),
if (!isLoading &&
videoDetail.pages != null &&
videoDetail.pages!.length > 1 &&
(isPortrait ||
!videoDetailCtr
.plPlayerController
.horizontalSeasonPanel)) ...[
PagesPanel(
heroTag: widget.heroTag,
ugcIntroController: introController,
bvid: introController.bvid,
showEpisodes: widget.showEpisodes,
),
],
),
theme: expandTheme,
),
const SizedBox(height: 8),
Stack(
clipBehavior: Clip.none,
children: [
_buildInfo(theme, videoDetail),
if (introController.enableAi) _aiBtn,
],
),
),
);
},
),
);
},
if (videoDetail.argueInfo?.argueMsg?.isNotEmpty == true &&
introController.showArgueMsg) ...[
const SizedBox(height: 2),
Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
size: 13,
Icons.error_outline,
color: theme.colorScheme.outline,
),
),
const WidgetSpan(child: SizedBox(width: 2)),
TextSpan(
text: '${videoDetail.argueInfo!.argueMsg}',
),
],
),
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
],
ExpandablePanel(
controller: introController.expandableCtr,
collapsed: const SizedBox.shrink(),
expanded: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
GestureDetector(
onTap: () => Utils.copyText('${videoDetail.bvid}'),
child: Text(
videoDetail.bvid ?? '',
style: TextStyle(
fontSize: 14,
color: theme.colorScheme.secondary,
),
),
),
if (videoDetail.descV2?.isNotEmpty == true) ...[
const SizedBox(height: 8),
SelectableText.rich(
style: const TextStyle(
height: 1.4,
),
TextSpan(
children: [
buildContent(theme, videoDetail),
],
),
),
],
Obx(() {
final videoTags = introController.videoTags.value;
if (videoTags.isNullOrEmpty) {
return const SizedBox.shrink();
}
return _buildTags(videoTags!);
}),
],
),
theme: expandTheme,
),
Obx(
() => introController.status.value
? const SizedBox.shrink()
: Center(
child: TextButton.icon(
icon: const Icon(Icons.refresh),
onPressed: () {
introController
..status.value = true
..queryVideoIntro();
if (videoDetailCtr.videoUrl.isNullOrEmpty &&
!videoDetailCtr.isQuerying) {
videoDetailCtr.queryVideoUrl();
}
},
label: const Text("点此重新加载"),
),
),
),
// 点赞收藏转发 布局样式2
if (!isHorizontal) ...[
const SizedBox(height: 8),
actionGrid(
context,
isLoading,
videoDetail,
introController,
),
],
// 合集
if (!isLoading &&
videoDetail.ugcSeason != null &&
(isPortrait ||
!videoDetailCtr
.plPlayerController
.horizontalSeasonPanel))
SeasonPanel(
heroTag: widget.heroTag,
showEpisodes: widget.showEpisodes,
ugcIntroController: introController,
),
if (!isLoading &&
videoDetail.pages != null &&
videoDetail.pages!.length > 1 &&
(isPortrait ||
!videoDetailCtr
.plPlayerController
.horizontalSeasonPanel)) ...[
PagesPanel(
heroTag: widget.heroTag,
ugcIntroController: introController,
bvid: introController.bvid,
showEpisodes: widget.showEpisodes,
),
],
],
),
),
);
},
),
);
}
@@ -506,95 +500,85 @@ class _UgcIntroPanelState extends State<UgcIntroPanel>
) {
return SizedBox(
height: 48,
child: TripleBuilder(
introController: introController,
builder: (context, tripleAnimation, onStartTriple, onCancelTriple) {
return Row(
children: [
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: () => introController.handleAction(
introController.actionLikeVideo,
),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.like)
: null,
onStartTriple: onStartTriple,
onCancelTriple: onCancelTriple,
),
child: Row(
children: [
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.like)
: null,
onStartTriple: onStartTriple,
onCancelTriple: onCancelTriple,
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsDown),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: () => introController.handleAction(
introController.actionDislikeVideo,
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsDown),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: () => introController.handleAction(
introController.actionDislikeVideo,
),
selectStatus: introController.hasDislike.value,
semanticsLabel: '点踩',
text: "点踩",
),
selectStatus: introController.hasDislike.value,
semanticsLabel: '点踩',
text: "点踩",
),
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: introController.actionCoinVideo,
selectStatus: introController.hasCoin,
semanticsLabel: '投币',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.coin)
: null,
),
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => introController.showFavBottomSheet(context),
onLongPress: () => introController.showFavBottomSheet(
context,
isLongPress: true,
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => introController.handleAction(
introController.actionCoinVideo,
),
selectStatus: introController.hasCoin,
semanticsLabel: '投币',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.coin)
: null,
),
),
Obx(
() => ActionItem(
animation: tripleAnimation,
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => introController.showFavBottomSheet(context),
onLongPress: () => introController.showFavBottomSheet(
context,
isLongPress: true,
),
selectStatus: introController.hasFav.value,
semanticsLabel: '收藏',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.favorite)
: null,
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
selectIcon: const Icon(FontAwesomeIcons.solidClock),
onTap: () =>
introController.handleAction(introController.viewLater),
selectStatus: introController.hasLater.value,
semanticsLabel: '再看',
text: '再看',
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => introController.actionShareVideo(context),
selectStatus: false,
semanticsLabel: '分享',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.share!)
: null,
),
],
);
},
selectStatus: introController.hasFav.value,
semanticsLabel: '收藏',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.favorite)
: null,
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
selectIcon: const Icon(FontAwesomeIcons.solidClock),
onTap: () =>
introController.handleAction(introController.viewLater),
selectStatus: introController.hasLater.value,
semanticsLabel: '再看',
text: '再看',
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => introController.actionShareVideo(context),
selectStatus: false,
semanticsLabel: '分享',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.share!)
: null,
),
],
),
);
}

View File

@@ -79,9 +79,7 @@ class ActionItem extends StatelessWidget {
)
: child,
);
return expand
? Expanded(child: child)
: Material(type: MaterialType.transparency, child: child);
return expand ? Expanded(child: child) : child;
}
Widget _buildText(ThemeData theme) {
@@ -135,7 +133,7 @@ class _ArcPainter extends CustomPainter {
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
bool shouldRepaint(covariant _ArcPainter oldDelegate) {
return sweepAngle != oldDelegate.sweepAngle || color != oldDelegate.color;
}
}

View File

@@ -9,29 +9,26 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
abstract class TripleState<T extends StatefulWidget> extends State<T>
with SingleTickerProviderStateMixin {
late final tripleAnimCtr = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1000),
reverseDuration: const Duration(milliseconds: 400),
);
CommonIntroController get introController;
late final tripleAnimation = Tween<double>(
// no need for pugv
AnimationController? _tripleAnimCtr;
Animation<double>? _tripleAnimation;
AnimationController get tripleAnimCtr =>
_tripleAnimCtr ??= AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
reverseDuration: const Duration(milliseconds: 400),
);
Animation<double> get tripleAnimation => _tripleAnimation ??= Tween<double>(
begin: 0,
end: -2 * pi,
).animate(CurvedAnimation(parent: tripleAnimCtr, curve: Curves.easeInOut));
CommonIntroController get introController;
Timer? _timer;
// @mustCallSuper
// void tripleListener(AnimationStatus status) {
// if (status == AnimationStatus.completed) {
// tripleAnimCtr.reset();
// onTriple();
// }
// }
void _cancelTimer() {
_timer?.cancel();
_timer = null;
@@ -40,14 +37,14 @@ abstract class TripleState<T extends StatefulWidget> extends State<T>
@override
void dispose() {
_cancelTimer();
tripleAnimCtr.dispose();
_tripleAnimCtr?.dispose();
super.dispose();
}
void onStartTriple() {
_timer ??= Timer(const Duration(milliseconds: 200), () {
HapticFeedback.lightImpact();
if (introController.hasTriple) {
HapticFeedback.lightImpact();
SmartDialog.showToast('已完成三连');
} else {
tripleAnimCtr.forward().whenComplete(() {
@@ -71,39 +68,3 @@ abstract class TripleState<T extends StatefulWidget> extends State<T>
}
}
}
class TripleBuilder extends StatefulWidget {
const TripleBuilder({
super.key,
required this.builder,
required this.introController,
// this.tripleListener,
});
final CommonIntroController introController;
final Widget Function(
BuildContext context,
Animation<double> tripleAnimation,
void Function() onStartTriple,
void Function([bool]) onCancelTriple,
)
builder;
// final AnimationStatusListener? tripleListener;
@override
State<TripleBuilder> createState() => _TripleBuilderState();
}
class _TripleBuilderState extends TripleState<TripleBuilder> {
@override
Widget build(BuildContext context) =>
widget.builder(context, tripleAnimation, onStartTriple, onCancelTriple);
@override
late final CommonIntroController introController = widget.introController;
// @override
// void tripleListener(AnimationStatus status) {
// super.tripleListener(status);
// widget.tripleListener?.call(status);
// }
}

View File

@@ -43,14 +43,12 @@ import 'package:PiliPlus/plugin/pl_player/view.dart';
import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/services/shutdown_timer_service.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/image_util.dart';
import 'package:PiliPlus/utils/num_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:auto_orientation/auto_orientation.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_debounce/easy_throttle.dart';
@@ -105,19 +103,17 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
bool get isFullScreen => plPlayerController?.isFullScreen.value ?? false;
bool get _shouldShowSeasonPanel {
if (!videoDetailController.isUgc) {
if (isPortrait || !videoDetailController.isUgc) {
return false;
}
late final videoDetail = ugcIntroController.videoDetail.value;
return videoDetailController.plPlayerController.horizontalSeasonPanel &&
(videoDetail.ugcSeason != null ||
((videoDetail.pages?.length ?? 0) > 1)) &&
context.isLandscape;
((videoDetail.pages?.length ?? 0) > 1));
}
bool get _horizontalPreview =>
videoDetailController.plPlayerController.horizontalPreview &&
context.isLandscape;
!isPortrait && videoDetailController.plPlayerController.horizontalPreview;
StreamSubscription? _listenerFS;
@@ -203,11 +199,10 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
final isVertical = videoDetailController.isVertical.value;
final mode = plPlayerController?.mode;
late final size = Get.size;
if (!(mode == FullScreenMode.vertical ||
(mode == FullScreenMode.auto && isVertical) ||
(mode == FullScreenMode.ratio &&
(isVertical || size.height / size.width < 1.25)))) {
(isVertical || maxHeight / maxWidth < 1.25)))) {
landScape();
}
});
@@ -543,11 +538,8 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
Widget get childWhenDisabled {
final isPortrait = context.isPortrait;
final isFullScreen = this.isFullScreen;
final useSafeArea = !removeSafeArea && isPortrait && isFullScreen;
final size = MediaQuery.sizeOf(context);
final double width = size.width;
final double height = size.height;
return SafeArea(
top: useSafeArea,
bottom: useSafeArea,
@@ -611,8 +603,8 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
controller: videoDetailController.scrollCtr,
onlyOneScrollInBody: true,
pinnedHeaderSliverHeightBuilder: () {
double pinnedHeight = isFullScreen || context.isLandscape
? MediaQuery.sizeOf(context).height
double pinnedHeight = this.isFullScreen || !isPortrait
? maxHeight
: videoDetailController.isExpanding ||
videoDetailController.isCollapsing
? animHeight
@@ -638,6 +630,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
return pinnedHeight;
},
headerSliverBuilder: (context, innerBoxIsScrolled) {
final isFullScreen = this.isFullScreen;
return [
SliverAppBar(
elevation: 0,
@@ -646,7 +639,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
automaticallyImplyLeading: false,
pinned: true,
expandedHeight: isFullScreen || !isPortrait
? height
? maxHeight
: videoDetailController.isExpanding ||
videoDetailController.isCollapsing
? animHeight
@@ -674,21 +667,19 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
return SizedBox(
height: !isPortrait || isFullScreen
? height -
? maxHeight -
(!isPortrait || removeSafeArea
? 0
: MediaQuery.paddingOf(
this.context,
).top)
: padding.top)
: videoDetailController.isExpanding ||
videoDetailController.isCollapsing
? animHeight
: videoDetailController.videoHeight,
width: width,
width: maxWidth,
child: videoPlayer(
width,
maxWidth,
!isPortrait || isFullScreen
? height
? maxHeight
: videoDetailController.isExpanding ||
videoDetailController.isCollapsing
? animHeight
@@ -952,7 +943,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
child: videoTabBarView(
controller: videoDetailController.tabCtr,
children: [
videoIntro(true, false),
videoIntro(isHorizontal: false, needCtr: false),
if (videoDetailController.showReply)
videoReplyPanel(false),
if (_shouldShowSeasonPanel) seasonPanel,
@@ -969,20 +960,17 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Widget get childWhenDisabledAlmostSquareInner => Obx(
() {
final size = MediaQuery.sizeOf(context);
final double width = size.width;
final double height = size.height;
final padding = MediaQuery.paddingOf(context);
final isFullScreen = this.isFullScreen;
if (videoDetailController.isVertical.value && enableVerticalExpand) {
final double videoHeight =
height - (removeSafeArea ? 0 : padding.vertical);
maxHeight - (removeSafeArea ? 0 : padding.vertical);
final double videoWidth = videoHeight * 9 / 16;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: videoHeight,
width: isFullScreen ? width : videoWidth,
width: isFullScreen ? maxWidth : videoWidth,
child: videoPlayer(videoWidth, videoHeight),
),
Expanded(
@@ -997,7 +985,10 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
child: videoTabBarView(
controller: videoDetailController.tabCtr,
children: [
videoIntro(),
videoIntro(
width: maxWidth - videoWidth,
height: maxHeight,
),
if (videoDetailController.showReply)
videoReplyPanel(),
if (_shouldShowSeasonPanel) seasonPanel,
@@ -1011,15 +1002,16 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
],
);
}
final double videoHeight = height / 2.5;
final double videoHeight = maxHeight / 2.5;
final shouldShowSeasonPanel = _shouldShowSeasonPanel;
return Column(
children: [
SizedBox(
width: width,
width: maxWidth,
height: isFullScreen
? height - (removeSafeArea ? 0 : padding.vertical)
? maxHeight - (removeSafeArea ? 0 : padding.vertical)
: videoHeight,
child: videoPlayer(width, videoHeight),
child: videoPlayer(maxWidth, videoHeight),
),
Expanded(
child: Scaffold(
@@ -1032,11 +1024,20 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Expanded(
child: Row(
children: [
Expanded(child: videoIntro()),
Expanded(
child: videoIntro(
width: () {
double flex = 1;
if (videoDetailController.showReply) flex++;
if (shouldShowSeasonPanel) flex++;
return maxWidth / flex;
}(),
height: maxHeight - videoHeight,
),
),
if (videoDetailController.showReply)
Expanded(child: videoReplyPanel()),
if (_shouldShowSeasonPanel)
Expanded(child: seasonPanel),
if (shouldShowSeasonPanel) Expanded(child: seasonPanel),
],
),
),
@@ -1051,21 +1052,23 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Widget get childWhenDisabledLandscapeInner => Obx(
() {
final size = MediaQuery.sizeOf(context);
final double width = size.width;
final double height = size.height;
final padding = MediaQuery.paddingOf(context);
final isFullScreen = this.isFullScreen;
if (videoDetailController.isVertical.value && enableVerticalExpand) {
final double videoHeight = height - (removeSafeArea ? 0 : padding.top);
final double videoHeight =
maxHeight - (removeSafeArea ? 0 : padding.top);
final double videoWidth = videoHeight * 9 / 16;
return Row(
children: [
Expanded(
child: isFullScreen ? const SizedBox.shrink() : videoIntro(),
),
if (!isFullScreen)
Expanded(
child: videoIntro(
width: (maxWidth - videoWidth) / 2,
height: maxHeight,
),
),
SizedBox(
height: videoHeight,
width: isFullScreen ? width : videoWidth,
width: isFullScreen ? maxWidth : videoWidth,
child: videoPlayer(videoWidth, videoHeight),
),
Expanded(
@@ -1093,27 +1096,34 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
],
);
}
double videoWidth = clampDouble(height / width * 1.08, 0.5, 0.7) * width;
if (width >= 560) {
videoWidth = min(videoWidth, width - 280);
double videoWidth =
clampDouble(maxHeight / maxWidth * 1.08, 0.5, 0.7) * maxWidth;
if (maxWidth >= 560) {
videoWidth = min(videoWidth, maxWidth - 280);
}
final double videoHeight = videoWidth * 9 / 16;
final introHeight =
maxHeight - videoHeight - (removeSafeArea ? 0 : padding.top);
return Row(
children: [
Column(
children: [
SizedBox(
width: isFullScreen ? width : videoWidth,
height: isFullScreen ? height : videoHeight,
width: isFullScreen ? maxWidth : videoWidth,
height: isFullScreen ? maxHeight : videoHeight,
child: videoPlayer(videoWidth, videoHeight),
),
Offstage(
offstage: isFullScreen,
child: SizedBox(
width: videoWidth,
height:
height - videoHeight - (removeSafeArea ? 0 : padding.top),
child: videoIntro(false, false),
height: introHeight,
child: videoIntro(
width: videoWidth,
height: introHeight,
needRelated: false,
needCtr: false,
),
),
),
],
@@ -1122,10 +1132,10 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
offstage: isFullScreen,
child: SizedBox(
width:
width -
maxWidth -
videoWidth -
(removeSafeArea ? 0 : padding.horizontal),
height: height - (removeSafeArea ? 0 : padding.top),
height: maxHeight - (removeSafeArea ? 0 : padding.top),
child: Scaffold(
key: videoDetailController.childKey,
resizeToAvoidBottomInset: false,
@@ -1173,46 +1183,52 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
},
);
Widget get childWhenDisabledLandscape => Stack(
clipBehavior: Clip.none,
children: [
Scaffold(
resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey,
appBar: (removeSafeArea || isFullScreen)
? null
: AppBar(
backgroundColor: Colors.black,
toolbarHeight: 0,
),
body: SafeArea(
left: !removeSafeArea && !isFullScreen,
right: !removeSafeArea && !isFullScreen,
top: !removeSafeArea && !isFullScreen,
bottom: false,
child: childWhenDisabledLandscapeInner,
),
),
],
);
Widget get childWhenDisabledAlmostSquare => Scaffold(
resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey,
appBar: (removeSafeArea || isFullScreen)
? null
: AppBar(
backgroundColor: Colors.black,
toolbarHeight: 0,
Widget get childWhenDisabledLandscape {
final isFullScreen = this.isFullScreen;
return Stack(
clipBehavior: Clip.none,
children: [
Scaffold(
resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey,
appBar: (removeSafeArea || isFullScreen)
? null
: AppBar(
backgroundColor: Colors.black,
toolbarHeight: 0,
),
body: SafeArea(
left: !removeSafeArea && !isFullScreen,
right: !removeSafeArea && !isFullScreen,
top: !removeSafeArea && !isFullScreen,
bottom: false,
child: childWhenDisabledLandscapeInner,
),
body: SafeArea(
left: !removeSafeArea && !isFullScreen,
right: !removeSafeArea && !isFullScreen,
top: !removeSafeArea && !isFullScreen,
bottom: false,
child: childWhenDisabledAlmostSquareInner,
),
);
),
],
);
}
Widget get childWhenDisabledAlmostSquare {
final isFullScreen = this.isFullScreen;
return Scaffold(
resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey,
appBar: (removeSafeArea || isFullScreen)
? null
: AppBar(
backgroundColor: Colors.black,
toolbarHeight: 0,
),
body: SafeArea(
left: !removeSafeArea && !isFullScreen,
right: !removeSafeArea && !isFullScreen,
top: !removeSafeArea && !isFullScreen,
bottom: false,
child: childWhenDisabledAlmostSquareInner,
),
);
}
Widget get manualPlayerWidget => Obx(() {
if (!videoDetailController.autoPlay.value) {
@@ -1400,28 +1416,33 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
late ThemeData themeData;
Widget get child {
if (!videoDetailController.horizontalScreen) {
return autoChoose(childWhenDisabled);
}
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > constraints.maxHeight * 1.25) {
return autoChoose(childWhenDisabledLandscape);
} else if (constraints.maxWidth * (9 / 16) <
(2 / 5) * constraints.maxHeight) {
return autoChoose(childWhenDisabled);
} else {
return autoChoose(childWhenDisabledAlmostSquare);
}
},
);
}
late bool isPortrait;
late double maxWidth;
late double maxHeight;
late EdgeInsets padding;
@override
Widget build(BuildContext context) {
padding = MediaQuery.paddingOf(context);
Widget child = LayoutBuilder(
builder: (context, constraints) {
maxWidth = constraints.maxWidth;
maxHeight = constraints.maxHeight;
isPortrait = maxHeight > maxWidth;
if (!videoDetailController.horizontalScreen) {
return autoChoose(childWhenDisabled);
}
if (maxWidth > maxHeight * 1.25) {
return autoChoose(childWhenDisabledLandscape);
}
if (maxWidth * (9 / 16) < (2 / 5) * maxHeight) {
return autoChoose(childWhenDisabled);
}
return autoChoose(childWhenDisabledAlmostSquare);
},
);
return videoDetailController.plPlayerController.darkVideoPage
? Theme(data: themeData, child: child)
: child;
@@ -1542,19 +1563,13 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
height: 38,
child: Obx(
() {
final enableShowDanmaku = videoDetailController
.plPlayerController
.enableShowDanmaku
.value;
final ctr = videoDetailController.plPlayerController;
final enableShowDanmaku = ctr.enableShowDanmaku.value;
return IconButton(
onPressed: () {
final newVal = !enableShowDanmaku;
videoDetailController
.plPlayerController
.enableShowDanmaku
.value =
newVal;
if (!Pref.tempPlayerConf) {
ctr.enableShowDanmaku.value = newVal;
if (!ctr.tempPlayerConf) {
GStorage.setting.put(
SettingBoxKey.enableShowDanmaku,
newVal,
@@ -1585,10 +1600,11 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
Widget videoPlayer(double videoWidth, double videoHeight) {
final isFullScreen = this.isFullScreen;
return PopScope(
canPop:
!isFullScreen &&
(videoDetailController.horizontalScreen || context.isPortrait),
(videoDetailController.horizontalScreen || isPortrait),
onPopInvokedWithResult: _onPopInvokedWithResult,
child: Stack(
clipBehavior: Clip.none,
@@ -1627,7 +1643,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
videoDetailController.continuePlayingPart)
Positioned(
left: 16,
bottom: isFullScreen ? max(75, Get.height * 0.25) : 75,
bottom: isFullScreen ? max(75, maxHeight * 0.25) : 75,
child: SizedBox(
width: MediaQuery.textScalerOf(context).scale(120),
child: AnimatedList(
@@ -1746,8 +1762,13 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
);
}
Widget videoIntro([bool needRelated = true, bool needCtr = true]) {
final bottom = MediaQuery.paddingOf(context).bottom;
Widget videoIntro({
double? width,
double? height,
bool? isHorizontal,
bool needRelated = true,
bool needCtr = true,
}) {
Widget introPanel() => CustomScrollView(
key: const PageStorageKey<String>('简介'),
controller: needCtr ? introScrollController : null,
@@ -1762,6 +1783,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
showAiBottomSheet: showAiBottomSheet,
showEpisodes: showEpisodes,
onShowMemberPage: onShowMemberPage,
isHorizontal: isHorizontal ?? width! > height! * 1.25,
),
if (needRelated &&
videoDetailController.plPlayerController.showRelatedVideo) ...[
@@ -1779,9 +1801,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
RelatedVideoPanel(key: relatedVideoPanelKey, heroTag: heroTag),
] else
SliverToBoxAdapter(
child: SizedBox(
height: bottom + StyleString.safeSpace,
),
child: SizedBox(height: padding.bottom + StyleString.safeSpace),
),
] else
PgcIntroPage(
@@ -1790,14 +1810,13 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
cid: videoDetailController.cid.value,
showEpisodes: showEpisodes,
showIntroDetail: showIntroDetail,
maxWidth: width ?? maxWidth,
),
SliverToBoxAdapter(
child: SizedBox(
height:
bottom +
(videoDetailController.isPlayAll && context.isLandscape
? 75
: 0),
padding.bottom +
(videoDetailController.isPlayAll && !isPortrait ? 75 : 0),
),
),
],
@@ -1810,7 +1829,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Positioned(
left: 12,
right: 12,
bottom: bottom + 12,
bottom: padding.bottom + 12,
child: Material(
type: MaterialType.transparency,
child: InkWell(
@@ -2013,6 +2032,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
void showEpisodes([int? index, season, episodes, bvid, aid, cid]) {
final isFullScreen = this.isFullScreen;
if (cid == null) {
videoDetailController.showMediaListPanel(context);
return;
@@ -2180,7 +2200,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
if (isFullScreen) {
plPlayerController!.triggerFullScreen(status: false);
}
if (!videoDetailController.horizontalScreen && context.isLandscape) {
if (!videoDetailController.horizontalScreen && !isPortrait) {
verticalScreenForTwoSeconds();
}
}

View File

@@ -29,7 +29,6 @@ import 'package:PiliPlus/utils/image_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/video_utils.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart';
@@ -63,7 +62,7 @@ class HeaderControl extends StatefulWidget {
State<HeaderControl> createState() => HeaderControlState();
}
class HeaderControlState extends State<HeaderControl> {
class HeaderControlState extends TripleState<HeaderControl> {
late final PlPlayerController plPlayerController = widget.controller;
late final VideoDetailController videoDetailCtr = widget.videoDetailCtr;
late final PlayUrlModel videoInfo = videoDetailCtr.data;
@@ -72,6 +71,7 @@ class HeaderControlState extends State<HeaderControl> {
String get heroTag => widget.heroTag;
late final UgcIntroController ugcIntroController;
late final PgcIntroController pgcIntroController;
@override
late CommonIntroController introController = videoDetailCtr.isUgc
? ugcIntroController
: pgcIntroController;
@@ -664,7 +664,7 @@ class HeaderControlState extends State<HeaderControl> {
..updatePlayer();
// update
if (!Pref.tempPlayerConf) {
if (!plPlayerController.tempPlayerConf) {
final res = await Connectivity().checkConnectivity();
if (res.contains(ConnectivityResult.wifi)) {
setting.put(
@@ -741,7 +741,7 @@ class HeaderControlState extends State<HeaderControl> {
..updatePlayer();
// update
if (!Pref.tempPlayerConf) {
if (!plPlayerController.tempPlayerConf) {
final res = await Connectivity().checkConnectivity();
if (res.contains(ConnectivityResult.wifi)) {
setting.put(
@@ -2102,7 +2102,7 @@ class HeaderControlState extends State<HeaderControl> {
onPressed: () {
final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
if (!Pref.tempPlayerConf) {
if (!plPlayerController.tempPlayerConf) {
setting.put(SettingBoxKey.enableShowDanmaku, newVal);
}
},
@@ -2228,118 +2228,112 @@ class HeaderControlState extends State<HeaderControl> {
],
),
if (showFSActionItem && isFullScreen)
TripleBuilder(
introController: introController,
builder: (context, tripleAnimation, onStartTriple, onCancelTriple) {
return 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,
),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
animation: tripleAnimation,
onStartTriple: () {
plPlayerController.tripling = true;
onStartTriple();
},
onCancelTriple: ([bool isTap = false]) {
plPlayerController
..tripling = false
..hideTaskControls();
onCancelTriple(isTap);
},
),
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,
),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
animation: tripleAnimation,
onStartTriple: () {
plPlayerController.tripling = true;
onStartTriple();
},
onCancelTriple: ([bool isTap = false]) {
plPlayerController
..tripling = false
..hideTaskControls();
onCancelTriple(isTap);
},
),
if (introController case UgcIntroController ugc)
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
icon: const Icon(
FontAwesomeIcons.thumbsDown,
color: Colors.white,
),
selectIcon: const Icon(
FontAwesomeIcons.solidThumbsDown,
),
onTap: ugc.actionDislikeVideo,
selectStatus: ugc.hasDislike.value,
semanticsLabel: '点踩',
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
animation: tripleAnimation,
icon: const Icon(
FontAwesomeIcons.b,
color: Colors.white,
),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: introController.actionCoinVideo,
selectStatus: introController.hasCoin,
semanticsLabel: '投币',
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
animation: tripleAnimation,
icon: const Icon(
FontAwesomeIcons.star,
color: Colors.white,
),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () =>
introController.showFavBottomSheet(context),
onLongPress: () => introController.showFavBottomSheet(
context,
isLongPress: true,
),
selectStatus: introController.hasFav.value,
semanticsLabel: '收藏',
),
),
),
SizedBox(
width: 42,
height: 34,
child: ActionItem(
),
),
if (introController case UgcIntroController ugc)
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
icon: const Icon(
FontAwesomeIcons.shareFromSquare,
FontAwesomeIcons.thumbsDown,
color: Colors.white,
),
onTap: () => introController.actionShareVideo(context),
semanticsLabel: '分享',
selectIcon: const Icon(
FontAwesomeIcons.solidThumbsDown,
),
onTap: () => ugc.handleAction(ugc.actionDislikeVideo),
selectStatus: ugc.hasDislike.value,
semanticsLabel: '点踩',
),
),
],
);
},
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
animation: tripleAnimation,
icon: const Icon(
FontAwesomeIcons.b,
color: Colors.white,
),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: introController.actionCoinVideo,
selectStatus: introController.hasCoin,
semanticsLabel: '投币',
),
),
),
SizedBox(
width: 42,
height: 34,
child: Obx(
() => ActionItem(
expand: false,
animation: tripleAnimation,
icon: const Icon(
FontAwesomeIcons.star,
color: Colors.white,
),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => introController.showFavBottomSheet(context),
onLongPress: () => introController.showFavBottomSheet(
context,
isLongPress: true,
),
selectStatus: introController.hasFav.value,
semanticsLabel: '收藏',
),
),
),
SizedBox(
width: 42,
height: 34,
child: ActionItem(
expand: false,
icon: const Icon(
FontAwesomeIcons.shareFromSquare,
color: Colors.white,
),
onTap: () => introController.actionShareVideo(context),
semanticsLabel: '分享',
),
),
],
),
],
),

View File

@@ -336,6 +336,8 @@ class PlPlayerController {
late final bool pipNoDanmaku = Pref.pipNoDanmaku;
late final bool removeSafeArea = Pref.removeSafeArea;
late final bool tempPlayerConf = Pref.tempPlayerConf;
int? cacheVideoQa;
late int cacheAudioQa;
bool enableHeart = true;
@@ -656,7 +658,7 @@ class PlPlayerController {
type ??= superResolutionType;
} else {
superResolutionType = type;
if (isAnim && !Pref.tempPlayerConf) {
if (isAnim && !tempPlayerConf) {
GStorage.setting.put(SettingBoxKey.superResolutionType, type);
}
}
@@ -1314,7 +1316,7 @@ class PlPlayerController {
/// 设置后台播放
Future<void> setBackgroundPlay(bool val) async {
videoPlayerServiceHandler.enableBackgroundPlay = val;
if (!Pref.tempPlayerConf) {
if (!tempPlayerConf) {
setting.put(SettingBoxKey.enableBackgroundPlay, val);
}
}
@@ -1596,7 +1598,7 @@ class PlPlayerController {
void setContinuePlayInBackground() {
_continuePlayInBackground.value = !_continuePlayInBackground.value;
if (!Pref.tempPlayerConf) {
if (!tempPlayerConf) {
setting.put(
SettingBoxKey.continuePlayInBackground,
_continuePlayInBackground.value,