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/common/dyn/common_dyn_page.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.dart'; import 'package:PiliPlus/pages/dynamics_repost/view.dart';
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.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/date_util.dart';
import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -70,19 +69,18 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(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( return Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: _buildAppBar(isPortrait), appBar: _buildAppBar(isPortrait, maxWidth),
body: Padding( body: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(left: padding.left, right: padding.right),
left: padding.left,
right: padding.right,
),
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
_buildPage(theme, isPortrait), _buildPage(theme, isPortrait, maxWidth),
_buildBottom(theme), _buildBottom(theme),
], ],
), ),
@@ -90,101 +88,89 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
); );
} }
Widget _buildPage(ThemeData theme, bool isPortrait) { Widget _buildPage(ThemeData theme, bool isPortrait, double maxWidth) {
return LayoutBuilder( double padding = max(maxWidth / 2 - Grid.smallCardWidth, 0);
builder: (context, constraints) { if (isPortrait) {
double padding = max( return Padding(
context.width / 2 - Grid.smallCardWidth, padding: EdgeInsets.symmetric(horizontal: padding),
0, child: CustomScrollView(
); controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
if (isPortrait) { slivers: [
final maxWidth = constraints.maxWidth - 2 * padding - 24; _buildContent(
return Padding( theme,
padding: EdgeInsets.symmetric(horizontal: padding), maxWidth - this.padding.horizontal - 2 * padding - 24,
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,
),
),
],
), ),
); SliverToBoxAdapter(
} child: Divider(
thickness: 8,
padding = padding / 4; color: theme.dividerColor.withValues(alpha: 0.05),
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,
),
),
],
),
),
),
), ),
), ),
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(() { title: Obx(() {
if (controller.isLoaded.value && controller.showTitle.value) { if (controller.isLoaded.value && controller.showTitle.value) {
return Text(controller.summary.title ?? ''); return Text(controller.summary.title ?? '');
@@ -505,7 +491,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
top: 56, top: 56,
right: 16, right: 16,
), ),
width: context.width / 4, width: maxWidth / 4,
height: 32, height: 32,
child: Builder( child: Builder(
builder: (context) => Slider( 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), () { EasyThrottle.throttle('replyReply', const Duration(milliseconds: 500), () {
int oid = replyItem.oid.toInt(); int oid = replyItem.oid.toInt();
int rpid = replyItem.id.toInt(); int rpid = replyItem.id.toInt();
Widget replyReplyPage({bool showBackBtn = true}) => Padding( Widget replyReplyPage({bool showBackBtn = true}) => Scaffold(
padding: EdgeInsets.only(right: padding.right), resizeToAvoidBottomInset: false,
child: Scaffold( appBar: AppBar(
resizeToAvoidBottomInset: false, primary: showBackBtn,
appBar: AppBar( toolbarHeight: showBackBtn ? null : 45,
primary: showBackBtn, title: const Text('评论详情'),
toolbarHeight: showBackBtn ? null : 45, titleSpacing: showBackBtn ? null : 12,
title: const Text('评论详情'), automaticallyImplyLeading: showBackBtn,
titleSpacing: showBackBtn ? null : 12, actions: showBackBtn
automaticallyImplyLeading: showBackBtn, ? null
actions: showBackBtn : [
? null IconButton(
: [ tooltip: '关闭',
IconButton( icon: const Icon(Icons.close, size: 20),
tooltip: '关闭', onPressed: Get.back,
icon: const Icon(Icons.close, size: 20), ),
onPressed: Get.back, ],
), shape: Border(
], bottom: BorderSide(
shape: Border( color: Theme.of(
bottom: BorderSide( context,
color: Theme.of( ).colorScheme.outline.withValues(alpha: 0.1),
context,
).colorScheme.outline.withValues(alpha: 0.1),
),
), ),
), ),
body: VideoReplyReplyPanel( ),
enableSlide: false, body: VideoReplyReplyPanel(
id: id, enableSlide: false,
oid: oid, id: id,
rpid: rpid, oid: oid,
isVideoDetail: false, rpid: rpid,
replyType: controller.replyType, isVideoDetail: false,
firstFloor: replyItem, replyType: controller.replyType,
), firstFloor: replyItem,
), ),
); );
if (this.context.isPortrait) { 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/widgets/dynamic_panel.dart';
import 'package:PiliPlus/pages/dynamics_detail/controller.dart'; import 'package:PiliPlus/pages/dynamics_detail/controller.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.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/feed_back.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/num_util.dart'; import 'package:PiliPlus/utils/num_util.dart';
@@ -54,117 +53,159 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(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( return Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: AppBar( appBar: _buildAppBar(isPortrait, maxWidth),
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),
],
),
body: Padding( body: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(left: padding.left, right: padding.right),
left: padding.left,
right: padding.right,
),
child: isPortrait child: isPortrait
? refreshIndicator( ? refreshIndicator(
onRefresh: controller.onRefresh, 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( PreferredSizeWidget _buildAppBar(bool isPortrait, double maxWidth) => AppBar(
clipBehavior: Clip.none, title: Padding(
children: [ padding: const EdgeInsets.only(right: 12),
Builder( child: Obx(
builder: (context) { () {
double padding = max(context.width / 2 - Grid.smallCardWidth, 0); final showTitle = controller.showTitle.value;
if (isPortrait) { return AnimatedOpacity(
return CustomScrollView( 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, controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverPadding( SliverPadding(
padding: EdgeInsets.symmetric(horizontal: padding), padding: EdgeInsets.only(
sliver: SliverMainAxisGroup( 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: [ slivers: [
SliverToBoxAdapter(
child: LayoutBuilder(
builder: (_, constrains) => DynamicPanel(
item: controller.dynItem,
isDetail: true,
callback: imageCallback,
maxWidth: constrains.maxWidth,
),
),
),
buildReplyHeader(theme), buildReplyHeader(theme),
Obx( Obx(
() => replyList(theme, controller.loadingState.value), () => replyList(theme, controller.loadingState.value),
@@ -172,74 +213,20 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
], ],
), ),
), ),
], ),
); ),
} else { ),
return Row( ],
children: [ );
Expanded( }
flex: controller.ratio[0].toInt(), return Stack(
child: CustomScrollView( clipBehavior: Clip.none,
controller: controller.scrollController, children: [
physics: const AlwaysScrollableScrollPhysics(), child,
slivers: [ _buildBottom(theme),
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),
],
);
Widget _buildBottom(ThemeData theme) { Widget _buildBottom(ThemeData theme) {
return Positioned( return Positioned(

View File

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

View File

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

View File

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

View File

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

View File

@@ -87,50 +87,48 @@ class _SendDanmakuPanelState extends CommonTextPubPageState<SendDanmakuPanel> {
super.dispose(); super.dispose();
} }
Expanded get _buildColorPanel => Expanded( Widget get _buildColorPanel => Expanded(
child: Obx( child: Obx(
() => LayoutBuilder( () {
key: ValueKey(_color.value), final bool isCustomColor = !_colorList.contains(_color.value);
builder: (context, constraints) { final int length = _colorList.length + (isCustomColor ? 1 : 0) + 1;
final int crossAxisCount = (constraints.maxWidth / 40).toInt(); return GridView.builder(
final bool isCustomColor = !_colorList.contains(_color.value); shrinkWrap: true,
final int length = _colorList.length + (isCustomColor ? 1 : 0) + 1; physics: const NeverScrollableScrollPhysics(),
return GridView.builder( padding: EdgeInsets.zero,
shrinkWrap: true, gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
physics: const NeverScrollableScrollPhysics(), maxCrossAxisExtent: 42,
padding: EdgeInsets.zero, crossAxisSpacing: 4,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( mainAxisSpacing: 4,
crossAxisCount: crossAxisCount, ),
crossAxisSpacing: 4, itemCount: length,
mainAxisSpacing: 4, itemBuilder: (context, index) {
), if (index == length - 1) {
itemCount: length, return GestureDetector(
itemBuilder: (context, index) { onTap: _showColorPicker,
if (index == length - 1) { child: Container(
return GestureDetector( decoration: BoxDecoration(
onTap: _showColorPicker, color: themeData.colorScheme.secondaryContainer,
child: Container( borderRadius: const BorderRadius.all(
decoration: BoxDecoration( Radius.circular(8),
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,
), ),
), ),
); alignment: Alignment.center,
} else if (index == length - 2 && isCustomColor) { margin: const EdgeInsets.all(2),
return _buildColorItem(_color.value); child: Icon(
} size: 22,
return _buildColorItem(_colorList[index]); 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(); int rpid = replyItem.id.toInt();
videoDetailController.childKey.currentState?.showBottomSheet( videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => VideoReplyReplyPanel( (context) => VideoReplyReplyPanel(
id: id, id: id,
oid: oid, oid: oid,
@@ -2046,6 +2047,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
void showAiBottomSheet() { void showAiBottomSheet() {
videoDetailController.childKey.currentState?.showBottomSheet( videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => (context) =>
AiConclusionPanel(item: ugcIntroController.aiConclusionResult!), AiConclusionPanel(item: ugcIntroController.aiConclusionResult!),
); );
@@ -2057,6 +2059,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
) { ) {
videoDetailController.childKey.currentState?.showBottomSheet( videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => PgcIntroPanel( (context) => PgcIntroPanel(
item: videoDetail, item: videoDetail,
videoTags: videoTags, videoTags: videoTags,
@@ -2126,6 +2129,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
} else { } else {
videoDetailController.childKey.currentState?.showBottomSheet( videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => listSheetContent(), (context) => listSheetContent(),
); );
} }
@@ -2217,6 +2221,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
} else { } else {
videoDetailController.childKey.currentState?.showBottomSheet( videoDetailController.childKey.currentState?.showBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
constraints: const BoxConstraints(),
(context) => ViewPointsPage( (context) => ViewPointsPage(
videoDetailController: videoDetailController, videoDetailController: videoDetailController,
plPlayerController: plPlayerController, plPlayerController: plPlayerController,
@@ -2241,6 +2246,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
void onShowMemberPage(int? mid) { void onShowMemberPage(int? mid) {
videoDetailController.childKey.currentState?.showBottomSheet( videoDetailController.childKey.currentState?.showBottomSheet(
shape: const RoundedRectangleBorder(), shape: const RoundedRectangleBorder(),
constraints: const BoxConstraints(),
(context) { (context) {
return HorizontalMemberPage( return HorizontalMemberPage(
mid: mid, mid: mid,

View File

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

View File

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