Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-25 14:32:08 +08:00
parent 461e91239e
commit a366b8a9e4
11 changed files with 445 additions and 468 deletions

View File

@@ -20,7 +20,6 @@ import 'package:PiliPlus/pages/article/widgets/opus_content.dart';
import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -70,19 +69,18 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isPortrait = context.isPortrait;
final size = MediaQuery.sizeOf(context);
final maxWidth = size.width;
final isPortrait = size.height >= maxWidth;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(isPortrait),
appBar: _buildAppBar(isPortrait, maxWidth),
body: Padding(
padding: EdgeInsets.only(
left: padding.left,
right: padding.right,
),
padding: EdgeInsets.only(left: padding.left, right: padding.right),
child: Stack(
clipBehavior: Clip.none,
children: [
_buildPage(theme, isPortrait),
_buildPage(theme, isPortrait, maxWidth),
_buildBottom(theme),
],
),
@@ -90,101 +88,89 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
);
}
Widget _buildPage(ThemeData theme, bool isPortrait) {
return LayoutBuilder(
builder: (context, constraints) {
double padding = max(
context.width / 2 - Grid.smallCardWidth,
0,
);
if (isPortrait) {
final maxWidth = constraints.maxWidth - 2 * padding - 24;
return Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
_buildContent(theme, maxWidth),
SliverToBoxAdapter(
child: Divider(
thickness: 8,
color: theme.dividerColor.withValues(
alpha: 0.05,
),
),
),
buildReplyHeader(theme),
Obx(
() => _buildReplyList(
theme,
controller.loadingState.value,
),
),
],
Widget _buildPage(ThemeData theme, bool isPortrait, double maxWidth) {
double padding = max(maxWidth / 2 - Grid.smallCardWidth, 0);
if (isPortrait) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
_buildContent(
theme,
maxWidth - this.padding.horizontal - 2 * padding - 24,
),
);
}
padding = padding / 4;
final flex = controller.ratio[0].toInt();
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: flex,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
left: padding,
bottom: this.padding.bottom + 100,
),
sliver: _buildContent(
theme,
constraints.maxWidth * flex - padding - 24,
),
),
],
),
),
VerticalDivider(
thickness: 8,
color: theme.dividerColor.withValues(alpha: 0.05),
),
Expanded(
flex: controller.ratio[1].toInt(),
child: Scaffold(
key: scaffoldKey,
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: refreshIndicator(
onRefresh: controller.onRefresh,
child: Padding(
padding: EdgeInsets.only(right: padding),
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
buildReplyHeader(theme),
Obx(
() => _buildReplyList(
theme,
controller.loadingState.value,
),
),
],
),
),
),
SliverToBoxAdapter(
child: Divider(
thickness: 8,
color: theme.dividerColor.withValues(alpha: 0.05),
),
),
buildReplyHeader(theme),
Obx(() => _buildReplyList(theme, controller.loadingState.value)),
],
);
},
),
);
}
padding = padding / 4;
final flex = controller.ratio[0].toInt();
final flex1 = controller.ratio[1].toInt();
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: flex,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
left: padding,
bottom: this.padding.bottom + 100,
),
sliver: _buildContent(
theme,
(maxWidth - this.padding.horizontal) * flex / (flex + flex1) -
padding -
32,
),
),
],
),
),
VerticalDivider(
thickness: 8,
color: theme.dividerColor.withValues(alpha: 0.05),
),
Expanded(
flex: flex1,
child: Padding(
padding: EdgeInsets.only(right: padding),
child: Scaffold(
key: scaffoldKey,
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: refreshIndicator(
onRefresh: controller.onRefresh,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
buildReplyHeader(theme),
Obx(
() =>
_buildReplyList(theme, controller.loadingState.value),
),
],
),
),
),
),
),
],
);
}
@@ -484,7 +470,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
};
}
PreferredSizeWidget _buildAppBar(bool isPortrait) => AppBar(
PreferredSizeWidget _buildAppBar(bool isPortrait, double maxWidth) => AppBar(
title: Obx(() {
if (controller.isLoaded.value && controller.showTitle.value) {
return Text(controller.summary.title ?? '');
@@ -505,7 +491,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
top: 56,
right: 16,
),
width: context.width / 4,
width: maxWidth / 4,
height: 32,
child: Builder(
builder: (context) => Slider(

View File

@@ -163,42 +163,39 @@ abstract class CommonDynPageState<T extends CommonDynPage> extends State<T>
EasyThrottle.throttle('replyReply', const Duration(milliseconds: 500), () {
int oid = replyItem.oid.toInt();
int rpid = replyItem.id.toInt();
Widget replyReplyPage({bool showBackBtn = true}) => Padding(
padding: EdgeInsets.only(right: padding.right),
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
primary: showBackBtn,
toolbarHeight: showBackBtn ? null : 45,
title: const Text('评论详情'),
titleSpacing: showBackBtn ? null : 12,
automaticallyImplyLeading: showBackBtn,
actions: showBackBtn
? null
: [
IconButton(
tooltip: '关闭',
icon: const Icon(Icons.close, size: 20),
onPressed: Get.back,
),
],
shape: Border(
bottom: BorderSide(
color: Theme.of(
context,
).colorScheme.outline.withValues(alpha: 0.1),
),
Widget replyReplyPage({bool showBackBtn = true}) => Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
primary: showBackBtn,
toolbarHeight: showBackBtn ? null : 45,
title: const Text('评论详情'),
titleSpacing: showBackBtn ? null : 12,
automaticallyImplyLeading: showBackBtn,
actions: showBackBtn
? null
: [
IconButton(
tooltip: '关闭',
icon: const Icon(Icons.close, size: 20),
onPressed: Get.back,
),
],
shape: Border(
bottom: BorderSide(
color: Theme.of(
context,
).colorScheme.outline.withValues(alpha: 0.1),
),
),
body: VideoReplyReplyPanel(
enableSlide: false,
id: id,
oid: oid,
rpid: rpid,
isVideoDetail: false,
replyType: controller.replyType,
firstFloor: replyItem,
),
),
body: VideoReplyReplyPanel(
enableSlide: false,
id: id,
oid: oid,
rpid: rpid,
isVideoDetail: false,
replyType: controller.replyType,
firstFloor: replyItem,
),
);
if (this.context.isPortrait) {

View File

@@ -9,7 +9,6 @@ import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
import 'package:PiliPlus/pages/dynamics_detail/controller.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/num_util.dart';
@@ -54,117 +53,159 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isPortrait = context.isPortrait;
final size = MediaQuery.sizeOf(context);
final maxWidth = size.width;
final isPortrait = size.height >= maxWidth;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Padding(
padding: const EdgeInsets.only(right: 12),
child: Obx(
() {
final showTitle = controller.showTitle.value;
return AnimatedOpacity(
opacity: showTitle ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: IgnorePointer(
ignoring: !showTitle,
child: AuthorPanel(
item: controller.dynItem,
isDetail: true,
),
),
);
},
),
),
actions: isPortrait
? null
: [
IconButton(
tooltip: '页面比例调节',
onPressed: () => showDialog(
context: context,
builder: (context) => Align(
alignment: Alignment.topRight,
child: Container(
margin: const EdgeInsets.only(
top: 56,
right: 16,
),
width: context.width / 4,
height: 32,
child: Builder(
builder: (context) => Slider(
min: 1,
max: 100,
value: controller.ratio.first,
onChanged: (value) {
if (value >= 10 && value <= 90) {
value = value.toPrecision(2);
controller.ratio
..[0] = value
..[1] = 100 - value;
GStorage.setting.put(
SettingBoxKey.dynamicDetailRatio,
controller.ratio,
);
(context as Element).markNeedsBuild();
setState(() {});
}
},
),
),
),
),
),
icon: Transform.rotate(
angle: pi / 2,
child: const Icon(Icons.splitscreen, size: 19),
),
),
const SizedBox(width: 16),
],
),
appBar: _buildAppBar(isPortrait, maxWidth),
body: Padding(
padding: EdgeInsets.only(
left: padding.left,
right: padding.right,
),
padding: EdgeInsets.only(left: padding.left, right: padding.right),
child: isPortrait
? refreshIndicator(
onRefresh: controller.onRefresh,
child: _buildBody(isPortrait, theme),
child: _buildBody(theme, isPortrait, maxWidth),
)
: _buildBody(isPortrait, theme),
: _buildBody(theme, isPortrait, maxWidth),
),
);
}
Widget _buildBody(bool isPortrait, ThemeData theme) => Stack(
clipBehavior: Clip.none,
children: [
Builder(
builder: (context) {
double padding = max(context.width / 2 - Grid.smallCardWidth, 0);
if (isPortrait) {
return CustomScrollView(
PreferredSizeWidget _buildAppBar(bool isPortrait, double maxWidth) => AppBar(
title: Padding(
padding: const EdgeInsets.only(right: 12),
child: Obx(
() {
final showTitle = controller.showTitle.value;
return AnimatedOpacity(
opacity: showTitle ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: IgnorePointer(
ignoring: !showTitle,
child: AuthorPanel(
item: controller.dynItem,
isDetail: true,
),
),
);
},
),
),
actions: isPortrait
? null
: [
IconButton(
tooltip: '页面比例调节',
onPressed: () => showDialog(
context: context,
builder: (context) => Align(
alignment: Alignment.topRight,
child: Container(
margin: const EdgeInsets.only(top: 56, right: 16),
width: maxWidth / 4,
height: 32,
child: Builder(
builder: (context) => Slider(
min: 1,
max: 100,
value: controller.ratio.first,
onChanged: (value) {
if (value >= 10 && value <= 90) {
value = value.toPrecision(2);
controller.ratio
..[0] = value
..[1] = 100 - value;
GStorage.setting.put(
SettingBoxKey.dynamicDetailRatio,
controller.ratio,
);
(context as Element).markNeedsBuild();
setState(() {});
}
},
),
),
),
),
),
icon: Transform.rotate(
angle: pi / 2,
child: const Icon(Icons.splitscreen, size: 19),
),
),
const SizedBox(width: 16),
],
);
Widget _buildBody(ThemeData theme, bool isPortrait, double maxWidth) {
double padding = max(maxWidth / 2 - Grid.smallCardWidth, 0);
Widget child;
if (isPortrait) {
child = Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: DynamicPanel(
item: controller.dynItem,
isDetail: true,
callback: imageCallback,
maxWidth: maxWidth - this.padding.horizontal - 2 * padding,
),
),
buildReplyHeader(theme),
Obx(() => replyList(theme, controller.loadingState.value)),
],
),
);
} else {
padding = padding / 4;
final flex = controller.ratio[0].toInt();
final flex1 = controller.ratio[1].toInt();
child = Row(
children: [
Expanded(
flex: flex,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: padding),
sliver: SliverMainAxisGroup(
padding: EdgeInsets.only(
left: padding,
bottom: this.padding.bottom + 100,
),
sliver: SliverToBoxAdapter(
child: DynamicPanel(
item: controller.dynItem,
isDetail: true,
callback: imageCallback,
maxWidth:
(maxWidth - this.padding.horizontal) *
(flex / (flex + flex1)) -
padding,
),
),
),
],
),
),
Expanded(
flex: flex1,
child: Padding(
padding: EdgeInsets.only(right: padding),
child: Scaffold(
key: scaffoldKey,
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: refreshIndicator(
onRefresh: controller.onRefresh,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: LayoutBuilder(
builder: (_, constrains) => DynamicPanel(
item: controller.dynItem,
isDetail: true,
callback: imageCallback,
maxWidth: constrains.maxWidth,
),
),
),
buildReplyHeader(theme),
Obx(
() => replyList(theme, controller.loadingState.value),
@@ -172,74 +213,20 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
],
),
),
],
);
} else {
return Row(
children: [
Expanded(
flex: controller.ratio[0].toInt(),
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
left: padding / 4,
bottom: this.padding.bottom + 100,
),
sliver: SliverToBoxAdapter(
child: LayoutBuilder(
builder: (_, constraints) => DynamicPanel(
item: controller.dynItem,
isDetail: true,
callback: imageCallback,
maxWidth: constraints.maxWidth,
),
),
),
),
],
),
),
Expanded(
flex: controller.ratio[1].toInt(),
child: Scaffold(
key: scaffoldKey,
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: refreshIndicator(
onRefresh: controller.onRefresh,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(right: padding / 4),
sliver: buildReplyHeader(theme),
),
SliverPadding(
padding: EdgeInsets.only(right: padding / 4),
sliver: Obx(
() => replyList(
theme,
controller.loadingState.value,
),
),
),
],
),
),
),
),
],
);
}
},
),
_buildBottom(theme),
],
);
),
),
),
],
);
}
return Stack(
clipBehavior: Clip.none,
children: [
child,
_buildBottom(theme),
],
);
}
Widget _buildBottom(ThemeData theme) {
return Positioned(

View File

@@ -297,6 +297,7 @@ class _SavePanelState extends State<SavePanel> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final padding = MediaQuery.viewPaddingOf(context);
final maxWidth = context.mediaQueryShortestSide;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Get.back,
@@ -312,17 +313,15 @@ class _SavePanelState extends State<SavePanel> {
child: GestureDetector(
onTap: () {},
child: Container(
width: context.mediaQueryShortestSide,
margin: const EdgeInsets.symmetric(horizontal: 12),
width: maxWidth,
padding: const EdgeInsets.symmetric(horizontal: 12),
child: RepaintBoundary(
key: boundaryKey,
child: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: AnimatedSize(
curve: Curves.easeInOut,
@@ -343,13 +342,11 @@ class _SavePanelState extends State<SavePanel> {
)
else if (_item is DynamicItemModel)
IgnorePointer(
child: LayoutBuilder(
builder: (_, constrains) => DynamicPanel(
item: _item,
isDetail: true,
isSave: true,
maxWidth: constrains.maxWidth,
),
child: DynamicPanel(
item: _item,
isDetail: true,
isSave: true,
maxWidth: maxWidth - 24,
),
),
if (cover?.isNotEmpty == true &&

View File

@@ -414,6 +414,7 @@ class VideoDetailController extends GetxController
} else {
childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => panel(),
);
}
@@ -1381,6 +1382,7 @@ class VideoDetailController extends GetxController
} else {
childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => PostPanel(
videoDetailController: this,
plPlayerController: plPlayerController,
@@ -1689,6 +1691,7 @@ class VideoDetailController extends GetxController
} else {
childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => NoteListPage(
oid: aid,
heroTag: heroTag,

View File

@@ -143,6 +143,7 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
return;
}
_key.currentState?.showBottomSheet(
constraints: const BoxConstraints(),
(context) => WebviewPage(
oid: widget.oid,
title: widget.title,

View File

@@ -362,6 +362,7 @@ class _VideoReplyReplyPanelState
upMid: _controller.upMid,
showDialogue: () => _key.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => VideoReplyReplyPanel(
oid: replyItem.oid.toInt(),
rpid: replyItem.root.toInt(),

View File

@@ -87,50 +87,48 @@ class _SendDanmakuPanelState extends CommonTextPubPageState<SendDanmakuPanel> {
super.dispose();
}
Expanded get _buildColorPanel => Expanded(
Widget get _buildColorPanel => Expanded(
child: Obx(
() => LayoutBuilder(
key: ValueKey(_color.value),
builder: (context, constraints) {
final int crossAxisCount = (constraints.maxWidth / 40).toInt();
final bool isCustomColor = !_colorList.contains(_color.value);
final int length = _colorList.length + (isCustomColor ? 1 : 0) + 1;
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
),
itemCount: length,
itemBuilder: (context, index) {
if (index == length - 1) {
return GestureDetector(
onTap: _showColorPicker,
child: Container(
decoration: BoxDecoration(
color: themeData.colorScheme.secondaryContainer,
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
alignment: Alignment.center,
margin: const EdgeInsets.all(2),
child: Icon(
size: 22,
Icons.edit,
color: themeData.colorScheme.onSecondaryContainer,
() {
final bool isCustomColor = !_colorList.contains(_color.value);
final int length = _colorList.length + (isCustomColor ? 1 : 0) + 1;
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 42,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
),
itemCount: length,
itemBuilder: (context, index) {
if (index == length - 1) {
return GestureDetector(
onTap: _showColorPicker,
child: Container(
decoration: BoxDecoration(
color: themeData.colorScheme.secondaryContainer,
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
),
);
} else if (index == length - 2 && isCustomColor) {
return _buildColorItem(_color.value);
}
return _buildColorItem(_colorList[index]);
},
);
},
),
alignment: Alignment.center,
margin: const EdgeInsets.all(2),
child: Icon(
size: 22,
Icons.edit,
color: themeData.colorScheme.onSecondaryContainer,
),
),
);
} else if (index == length - 2 && isCustomColor) {
return _buildColorItem(_color.value);
}
return _buildColorItem(_colorList[index]);
},
);
},
),
);

View File

@@ -2028,6 +2028,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
int rpid = replyItem.id.toInt();
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => VideoReplyReplyPanel(
id: id,
oid: oid,
@@ -2046,6 +2047,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
void showAiBottomSheet() {
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) =>
AiConclusionPanel(item: ugcIntroController.aiConclusionResult!),
);
@@ -2057,6 +2059,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
) {
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => PgcIntroPanel(
item: videoDetail,
videoTags: videoTags,
@@ -2126,6 +2129,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => listSheetContent(),
);
}
@@ -2217,6 +2221,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => ViewPointsPage(
videoDetailController: videoDetailController,
plPlayerController: plPlayerController,
@@ -2241,6 +2246,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
void onShowMemberPage(int? mid) {
videoDetailController.childKey.currentState?.showBottomSheet(
shape: const RoundedRectangleBorder(),
constraints: const BoxConstraints(),
(context) {
return HorizontalMemberPage(
mid: mid,

View File

@@ -1378,6 +1378,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
child:
widget.bottomControl ??
BottomControl(
maxWidth: maxWidth,
isFullScreen: isFullScreen,
controller: plPlayerController,
buildBottomControl: () =>
buildBottomControl(maxWidth > maxHeight),

View File

@@ -11,11 +11,15 @@ import 'package:get/get.dart';
class BottomControl extends StatelessWidget {
const BottomControl({
super.key,
required this.maxWidth,
required this.isFullScreen,
required this.controller,
required this.buildBottomControl,
super.key,
});
final double maxWidth;
final bool isFullScreen;
final PlPlayerController controller;
final Widget Function() buildBottomControl;
@@ -33,130 +37,125 @@ class BottomControl extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 7),
child: LayoutBuilder(
builder: (context, constraints) {
final maxWidth = constraints.maxWidth;
return Obx(
() => Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomCenter,
children: [
Obx(() {
final int value =
controller.sliderPositionSeconds.value;
final int max =
controller.durationSeconds.value.inSeconds;
final int buffer = controller.bufferedSeconds.value;
if (value > max || max <= 0) {
return const SizedBox.shrink();
child: Obx(
() => Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomCenter,
children: [
Obx(() {
final int value = controller.sliderPositionSeconds.value;
final int max = controller.durationSeconds.value.inSeconds;
final int buffer = controller.bufferedSeconds.value;
if (value > max || max <= 0) {
return const SizedBox.shrink();
}
return ProgressBar(
progress: Duration(seconds: value),
buffered: Duration(seconds: buffer),
total: Duration(seconds: max),
progressBarColor: colorTheme,
baseBarColor: Colors.white.withValues(alpha: 0.2),
bufferedBarColor: colorTheme.withValues(alpha: 0.4),
timeLabelLocation: TimeLabelLocation.none,
thumbColor: colorTheme,
barHeight: 3.5,
thumbRadius: 7,
onDragStart: (duration) {
feedBack();
controller.onChangedSliderStart(duration.timeStamp);
},
onDragUpdate: (duration) {
if (controller.showSeekPreview) {
controller.updatePreviewIndex(
duration.timeStamp.inSeconds,
);
}
return ProgressBar(
progress: Duration(seconds: value),
buffered: Duration(seconds: buffer),
total: Duration(seconds: max),
progressBarColor: colorTheme,
baseBarColor: Colors.white.withValues(alpha: 0.2),
bufferedBarColor: colorTheme.withValues(alpha: 0.4),
timeLabelLocation: TimeLabelLocation.none,
thumbColor: colorTheme,
barHeight: 3.5,
thumbRadius: 7,
onDragStart: (duration) {
feedBack();
controller.onChangedSliderStart(duration.timeStamp);
},
onDragUpdate: (duration) {
if (controller.showSeekPreview) {
controller.updatePreviewIndex(
duration.timeStamp.inSeconds,
double newProgress = duration.timeStamp.inSeconds / max;
if ((newProgress - lastAnnouncedValue).abs() > 0.02) {
accessibilityDebounce?.cancel();
accessibilityDebounce = Timer(
const Duration(milliseconds: 200),
() {
SemanticsService.announce(
"${(newProgress * 100).round()}%",
TextDirection.ltr,
);
}
double newProgress =
duration.timeStamp.inSeconds / max;
if ((newProgress - lastAnnouncedValue).abs() >
0.02) {
accessibilityDebounce?.cancel();
accessibilityDebounce = Timer(
const Duration(milliseconds: 200),
() {
SemanticsService.announce(
"${(newProgress * 100).round()}%",
TextDirection.ltr,
);
lastAnnouncedValue = newProgress;
},
);
}
controller.onUpdatedSliderProgress(
duration.timeStamp,
);
},
onSeek: (duration) {
if (controller.showSeekPreview) {
controller.showPreview.value = false;
}
controller
..onChangedSliderEnd()
..onChangedSlider(duration.inSeconds.toDouble())
..seekTo(
Duration(seconds: duration.inSeconds),
isSeek: false,
);
SemanticsService.announce(
"${(duration.inSeconds / max * 100).round()}%",
TextDirection.ltr,
);
},
lastAnnouncedValue = newProgress;
},
);
}
controller.onUpdatedSliderProgress(
duration.timeStamp,
);
}),
if (controller.segmentList.isNotEmpty)
Positioned(
left: 0,
right: 0,
bottom: 5.25,
child: IgnorePointer(
child: RepaintBoundary(
child: CustomPaint(
key: const Key('segmentList'),
size: const Size(double.infinity, 3.5),
painter: SegmentProgressBar(
segmentColors: controller.segmentList,
),
),
},
onSeek: (duration) {
if (controller.showSeekPreview) {
controller.showPreview.value = false;
}
controller
..onChangedSliderEnd()
..onChangedSlider(duration.inSeconds.toDouble())
..seekTo(
Duration(seconds: duration.inSeconds),
isSeek: false,
);
SemanticsService.announce(
"${(duration.inSeconds / max * 100).round()}%",
TextDirection.ltr,
);
},
);
}),
if (controller.segmentList.isNotEmpty)
Positioned(
left: 0,
right: 0,
bottom: 5.25,
child: IgnorePointer(
child: RepaintBoundary(
child: CustomPaint(
key: const Key('segmentList'),
size: const Size(double.infinity, 3.5),
painter: SegmentProgressBar(
segmentColors: controller.segmentList,
),
),
),
if (controller.viewPointList.isNotEmpty &&
controller.showVP.value) ...[
Positioned(
left: 0,
right: 0,
bottom: 5.25,
child: IgnorePointer(
child: RepaintBoundary(
child: CustomPaint(
key: const Key('viewPointList'),
size: const Size(double.infinity, 3.5),
painter: SegmentProgressBar(
segmentColors: controller.viewPointList,
),
),
),
),
if (controller.viewPointList.isNotEmpty &&
controller.showVP.value) ...[
Positioned(
left: 0,
right: 0,
bottom: 5.25,
child: IgnorePointer(
child: RepaintBoundary(
child: CustomPaint(
key: const Key('viewPointList'),
size: const Size(double.infinity, 3.5),
painter: SegmentProgressBar(
segmentColors: controller.viewPointList,
),
),
),
buildViewPointWidget(
controller,
8.75,
maxWidth,
),
],
if (controller.dmTrend.isNotEmpty &&
controller.showDmTreandChart.value)
buildDmChart(theme, controller, 4.5),
],
),
);
},
),
),
buildViewPointWidget(
controller,
8.75,
maxWidth -
40 -
(isFullScreen
? MediaQuery.viewPaddingOf(context).horizontal
: 0),
),
],
if (controller.dmTrend.isNotEmpty &&
controller.showDmTreandChart.value)
buildDmChart(theme, controller, 4.5),
],
),
),
),
buildBottomControl(),