From 508384c016a0235d27515851ab432ed00523b718 Mon Sep 17 00:00:00 2001 From: dom Date: Wed, 3 Jun 2026 20:34:48 +0800 Subject: [PATCH] constraint video bottomsheet Closes #2277 Signed-off-by: dom --- .../widgets/fractionally_sized_box.dart | 112 ++++++++++++++++++ lib/pages/video/controller.dart | 1 - lib/pages/video/widgets/header_mixin.dart | 1 + lib/services/shutdown_timer_service.dart | 1 + lib/utils/page_utils.dart | 5 +- 5 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 lib/common/widgets/fractionally_sized_box.dart diff --git a/lib/common/widgets/fractionally_sized_box.dart b/lib/common/widgets/fractionally_sized_box.dart new file mode 100644 index 000000000..adf4679d2 --- /dev/null +++ b/lib/common/widgets/fractionally_sized_box.dart @@ -0,0 +1,112 @@ +import 'dart:math' as math; + +import 'package:flutter/rendering.dart' show RenderFractionallySizedOverflowBox; +import 'package:flutter/widgets.dart'; + +class CustomFractionallySizedBox extends FractionallySizedBox { + const CustomFractionallySizedBox({ + super.key, + super.alignment, + super.widthFactor, + super.heightFactor, + super.child, + required this.maxWidth, + }); + + final double maxWidth; + + @override + RenderFractionallySizedOverflowBox createRenderObject(BuildContext context) { + return CustomRenderFractionallySizedOverflowBox( + alignment: alignment, + widthFactor: widthFactor, + heightFactor: heightFactor, + textDirection: Directionality.maybeOf(context), + maxWidth: maxWidth, + ); + } +} + +class CustomRenderFractionallySizedOverflowBox + extends RenderFractionallySizedOverflowBox { + CustomRenderFractionallySizedOverflowBox({ + super.child, + super.widthFactor, + super.heightFactor, + super.alignment, + super.textDirection, + required this._maxWidth, + }); + + final double _maxWidth; + + BoxConstraints _getInnerConstraints(BoxConstraints constraints) { + double minWidth = constraints.minWidth; + double maxWidth = constraints.maxWidth; + if (widthFactor != null) { + final double width = math.min(_maxWidth, maxWidth * widthFactor!); + minWidth = width; + maxWidth = width; + } + double minHeight = constraints.minHeight; + double maxHeight = constraints.maxHeight; + if (heightFactor != null) { + final double height = maxHeight * heightFactor!; + minHeight = height; + maxHeight = height; + } + return BoxConstraints( + minWidth: minWidth, + maxWidth: maxWidth, + minHeight: minHeight, + maxHeight: maxHeight, + ); + } + + @override + @protected + Size computeDryLayout(covariant BoxConstraints constraints) { + if (child != null) { + final Size childSize = child!.getDryLayout( + _getInnerConstraints(constraints), + ); + return constraints.constrain(childSize); + } + return constraints.constrain( + _getInnerConstraints(constraints).constrain(Size.zero), + ); + } + + @override + double? computeDryBaseline( + covariant BoxConstraints constraints, + TextBaseline baseline, + ) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = _getInnerConstraints(constraints); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(childConstraints); + final Size size = getDryLayout(constraints); + return result + + resolvedAlignment.alongOffset(size - childSize as Offset).dy; + } + + @override + void performLayout() { + if (child != null) { + child!.layout(_getInnerConstraints(constraints), parentUsesSize: true); + size = constraints.constrain(child!.size); + alignChild(); + } else { + size = constraints.constrain( + _getInnerConstraints(constraints).constrain(Size.zero), + ); + } + } +} diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 58ad4b298..ddc8eaaac 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -480,7 +480,6 @@ class VideoDetailController extends GetxController } } - // 稍后再看面板展开 void showMediaListPanel(BuildContext context) { if (mediaList.isNotEmpty) { Widget panel() => MediaListPanel( diff --git a/lib/pages/video/widgets/header_mixin.dart b/lib/pages/video/widgets/header_mixin.dart index 84a48f608..8fff7a106 100644 --- a/lib/pages/video/widgets/header_mixin.dart +++ b/lib/pages/video/widgets/header_mixin.dart @@ -18,6 +18,7 @@ mixin HeaderMixin on State { }) { return PageUtils.showVideoBottomSheet( context, + maxWidth: 512, isFullScreen: () => isFullScreen, padding: padding, child: StatefulBuilder( diff --git a/lib/services/shutdown_timer_service.dart b/lib/services/shutdown_timer_service.dart index a169a37d7..d91a049b4 100644 --- a/lib/services/shutdown_timer_service.dart +++ b/lib/services/shutdown_timer_service.dart @@ -147,6 +147,7 @@ class ShutdownTimerService { } PageUtils.showVideoBottomSheet( context, + maxWidth: 512, isFullScreen: () => isFullScreen, child: StatefulBuilder( builder: (_, setState) { diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 2fa7b7c81..e9b829c2d 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:PiliPlus/common/widgets/fractionally_sized_box.dart'; import 'package:PiliPlus/common/widgets/image_viewer/gallery_viewer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero_dialog_route.dart'; import 'package:PiliPlus/grpc/im.dart'; @@ -496,6 +497,7 @@ abstract final class PageUtils { required Widget child, required ValueGetter isFullScreen, double? padding, + double maxWidth = 500, }) { if (!context.mounted) { return null; @@ -519,7 +521,8 @@ abstract final class PageUtils { ); } return SafeArea( - child: FractionallySizedBox( + child: CustomFractionallySizedBox( + maxWidth: maxWidth, widthFactor: 0.5, heightFactor: 1.0, alignment: Alignment.centerRight,