mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-27 05:50:12 +08:00
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user