opt video header

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-06-30 18:40:02 +08:00
parent 0df637fb73
commit ed6353e6d5
8 changed files with 244 additions and 216 deletions

View File

@@ -65,7 +65,8 @@ import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/video_utils.dart';
import 'package:collection/collection.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'
show ExtendedNestedScrollViewState;
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -169,8 +170,7 @@ class VideoDetailController extends GetxController
late final RxDouble scrollRatio = 0.0.obs;
ScrollController? _scrollCtr;
ScrollController get scrollCtr =>
_scrollCtr ??= ScrollController()..addListener(scrollListener);
ScrollController get scrollCtr => _scrollCtr ??= ScrollController();
late bool isExpanding = false;
late bool isCollapsing = false;
@@ -315,26 +315,6 @@ class VideoDetailController extends GetxController
} catch (_) {}
}
void scrollListener() {
if (scrollCtr.hasClients) {
if (scrollCtr.offset == 0) {
scrollRatio.value = 0;
} else {
double offset = scrollCtr.offset - (videoHeight - minVideoHeight);
if (offset > 0) {
scrollRatio.value = clampDouble(
offset.toPrecision(2) /
(minVideoHeight - kToolbarHeight).toPrecision(2),
0.0,
1.0,
);
} else {
scrollRatio.value = 0;
}
}
}
}
final isLoginVideo = Accounts.get(AccountType.video).isLogin;
late final watchProgress = GStorage.watchProgress;
@@ -1247,9 +1227,7 @@ class VideoDetailController extends GetxController
introScrollCtr?.dispose();
introScrollCtr = null;
tabCtr.dispose();
_scrollCtr
?..removeListener(scrollListener)
..dispose();
_scrollCtr?.dispose();
animController
?..removeListener(_animListener)
..dispose();
@@ -1268,10 +1246,6 @@ class VideoDetailController extends GetxController
videoUrl = null;
audioUrl = null;
if (scrollRatio.value != 0) {
scrollRatio.refresh();
}
// danmaku
savedDanmaku = null;

View File

@@ -11,7 +11,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
import 'package:PiliPlus/common/widgets/route_aware_mixin.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_dynamic_header.dart';
import 'package:PiliPlus/common/widgets/sliver/video_header.dart';
import 'package:PiliPlus/common/widgets/svg/play_icon.dart';
import 'package:PiliPlus/models/common/episode_panel_type.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart';
@@ -493,24 +493,21 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
() {
final scrollRatio =
videoDetailController.scrollRatio.value;
final flag =
isPortrait &&
videoDetailController.scrollCtr.offset != 0;
return AppBar(
backgroundColor: flag && scrollRatio > 0
toolbarHeight: 0,
backgroundColor: isPortrait && scrollRatio > 0
? Color.lerp(
Colors.black,
themeData.colorScheme.surface,
scrollRatio,
)
: Colors.black,
toolbarHeight: 0,
systemOverlayStyle: Platform.isAndroid
? SystemUiOverlayStyle(
statusBarIconBrightness:
flag && scrollRatio >= 0.5
isPortrait && scrollRatio >= 0.5
? themeData.brightness.reverse
: Brightness.light,
: .light,
systemNavigationBarIconBrightness:
themeData.brightness.reverse,
)
@@ -557,179 +554,20 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
? videoDetailController.animHeight
: videoDetailController.videoHeight;
return [
SliverPinnedDynamicHeader(
VideoHeader(
minExtent: kToolbarHeight,
maxExtent: height,
minVideoHeight: videoDetailController.minVideoHeight,
onScrollRatioChanged: videoDetailController.scrollRatio.call,
child: Stack(
clipBehavior: Clip.none,
clipBehavior: .none,
children: [
SizedBox(
width: maxWidth,
height: height,
child: videoPlayer(
width: maxWidth,
height: height,
),
),
Obx(
() {
Widget toolbar() => Opacity(
opacity: videoDetailController.scrollRatio.value,
child: Container(
color: themeData.colorScheme.surface,
alignment: Alignment.topCenter,
child: SizedBox(
height: kToolbarHeight,
child: Stack(
clipBehavior: Clip.none,
children: [
Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '返回',
icon: Icon(
FontAwesomeIcons.arrowLeft,
size: 15,
color: themeData
.colorScheme
.onSurface,
),
onPressed: Get.back,
),
),
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '返回主页',
icon: Icon(
FontAwesomeIcons.house,
size: 15,
color: themeData
.colorScheme
.onSurface,
),
onPressed: videoDetailController
.plPlayerController
.onCloseAll,
),
),
],
),
),
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.play_arrow_rounded,
color:
themeData.colorScheme.primary,
),
Text(
'${videoDetailController.playedTime == null
? '立即'
: plPlayerController!.isCompleted
? '重新'
: '继续'}播放',
style: TextStyle(
color:
themeData.colorScheme.primary,
),
),
],
),
),
Align(
alignment: Alignment.centerRight,
child:
videoDetailController.playedTime ==
null
? _moreBtn(
themeData.colorScheme.onSurface,
)
: SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: "更多设置",
style: const ButtonStyle(
padding:
WidgetStatePropertyAll(
EdgeInsets.zero,
),
),
onPressed: () =>
(videoDetailController
.headerCtrKey
.currentState
as HeaderControlState?)
?.showSettingSheet(),
icon: Icon(
Icons.more_vert_outlined,
size: 19,
color: themeData
.colorScheme
.onSurface,
),
),
),
),
],
),
),
),
);
return videoDetailController.scrollRatio.value == 0 ||
videoDetailController.scrollCtr.offset == 0 ||
!isPortrait
? const SizedBox.shrink()
: Positioned.fill(
bottom: -2,
child: GestureDetector(
onTap: () {
if (!videoDetailController.isFileSource) {
if (videoDetailController.isQuerying) {
if (kDebugMode) {
debugPrint('handlePlay: querying');
}
return;
}
if (videoDetailController.videoUrl ==
null ||
videoDetailController.audioUrl ==
null) {
if (kDebugMode) {
debugPrint(
'handlePlay: videoUrl/audioUrl not initialized',
);
}
videoDetailController.queryVideoUrl();
return;
}
}
videoDetailController.scrollRatio.value =
0;
if (plPlayerController == null ||
videoDetailController.playedTime ==
null) {
handlePlay();
} else {
plPlayerController!.onDoubleTapCenter();
}
},
behavior: HitTestBehavior.opaque,
child: toolbar(),
),
);
},
child: videoPlayer(width: maxWidth, height: height),
),
_buildHeaderOverlay(),
],
),
),
@@ -766,6 +604,163 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
);
}
Widget _buildOverlayToolBar(double scrollRatio) {
final Icon icon;
final double spacing;
final String playStat;
if (videoDetailController.playedTime == null) {
spacing = 2;
icon = Icon(
Icons.play_arrow_rounded,
color: themeData.colorScheme.primary,
);
playStat = '立即';
} else if (plPlayerController!.isCompleted) {
spacing = 4;
icon = Icon(
size: 18,
Icons.replay_rounded,
color: themeData.colorScheme.primary,
);
playStat = '重新';
} else {
spacing = 2;
icon = Icon(
Icons.play_arrow_rounded,
color: themeData.colorScheme.primary,
);
playStat = '继续';
}
final playBtn = Row(
spacing: spacing,
mainAxisSize: .min,
children: [
icon,
Text(
'$playStat播放',
style: TextStyle(color: themeData.colorScheme.primary),
),
],
);
return Opacity(
opacity: videoDetailController.scrollRatio.value,
child: Container(
color: themeData.colorScheme.surface,
alignment: .topCenter,
child: SizedBox(
height: kToolbarHeight,
child: Stack(
clipBehavior: .none,
children: [
Align(
alignment: .centerLeft,
child: Row(
mainAxisSize: .min,
children: [
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '返回',
icon: Icon(
FontAwesomeIcons.arrowLeft,
size: 15,
color: themeData.colorScheme.onSurface,
),
onPressed: Get.back,
),
),
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '返回主页',
icon: Icon(
FontAwesomeIcons.house,
size: 15,
color: themeData.colorScheme.onSurface,
),
onPressed:
videoDetailController.plPlayerController.onCloseAll,
),
),
],
),
),
Center(child: playBtn),
Align(
alignment: .centerRight,
child: videoDetailController.playedTime == null
? _moreBtn(themeData.colorScheme.onSurface)
: SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: "更多设置",
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
),
onPressed: () =>
(videoDetailController.headerCtrKey.currentState
as HeaderControlState?)
?.showSettingSheet(),
icon: Icon(
Icons.more_vert_outlined,
size: 19,
color: themeData.colorScheme.onSurface,
),
),
),
),
],
),
),
),
);
}
Widget _buildHeaderOverlay() {
return Obx(
() {
final scrollRatio = videoDetailController.scrollRatio.value;
if (scrollRatio == 0) {
return const SizedBox.shrink();
}
return Positioned.fill(
bottom: -2,
child: GestureDetector(
onTap: () {
if (!videoDetailController.isFileSource) {
if (videoDetailController.isQuerying) {
if (kDebugMode) {
debugPrint('handlePlay: querying');
}
return;
}
if (videoDetailController.videoUrl == null ||
videoDetailController.audioUrl == null) {
if (kDebugMode) {
debugPrint('handlePlay: videoUrl/audioUrl not initialized');
}
videoDetailController.queryVideoUrl();
return;
}
}
if (plPlayerController == null ||
videoDetailController.playedTime == null) {
handlePlay();
} else {
plPlayerController!.onDoubleTapCenter();
}
},
behavior: .opaque,
child: _buildOverlayToolBar(scrollRatio),
),
);
},
);
}
Widget get childWhenDisabledLandscape => Obx(
() {
final isFullScreen = this.isFullScreen;

View File

@@ -180,8 +180,8 @@ class PlayerFocus extends StatelessWidget {
return true;
case LogicalKeyboardKey.keyD:
final newVal = !plPlayerController.enableShowDanmaku.value;
plPlayerController.enableShowDanmaku.value = newVal;
final newVal = !plPlayerController.enableShowDanmakuAdaptive.value;
plPlayerController.enableShowDanmakuAdaptive.value = newVal;
if (!plPlayerController.tempPlayerConf) {
GStorage.setting.put(
plPlayerController.isLive