mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -143,6 +143,7 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
|
||||
return;
|
||||
}
|
||||
_key.currentState?.showBottomSheet(
|
||||
constraints: const BoxConstraints(),
|
||||
(context) => WebviewPage(
|
||||
oid: widget.oid,
|
||||
title: widget.title,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1378,6 +1378,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child:
|
||||
widget.bottomControl ??
|
||||
BottomControl(
|
||||
maxWidth: maxWidth,
|
||||
isFullScreen: isFullScreen,
|
||||
controller: plPlayerController,
|
||||
buildBottomControl: () =>
|
||||
buildBottomControl(maxWidth > maxHeight),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user