From 90d8e8b2f202eb3bcd7188b5601e55389584d489 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 1 Aug 2026 11:25:14 +0800 Subject: [PATCH] opt ui Signed-off-by: dom --- lib/common/widgets/animated_height.dart | 126 +++++++++++++++--- lib/common/widgets/scaffold/bottom_sheet.dart | 6 +- lib/pages/audio/view.dart | 3 +- lib/pages/dynamics_repost/view.dart | 72 +++++----- lib/pages/pgc_index/view.dart | 88 ++++++------ lib/pages/setting/pages/color_select.dart | 10 +- lib/pages/setting/pages/play_speed_set.dart | 3 + lib/pages/video/introduction/ugc/view.dart | 2 +- 8 files changed, 199 insertions(+), 111 deletions(-) diff --git a/lib/common/widgets/animated_height.dart b/lib/common/widgets/animated_height.dart index 3e2c4ac85..f9fb0464c 100644 --- a/lib/common/widgets/animated_height.dart +++ b/lib/common/widgets/animated_height.dart @@ -6,8 +6,102 @@ import 'package:flutter/rendering.dart' typedef Heights = ({double from, double to}); /// ref [AnimatedSize] -class AnimatedHeight extends StatefulWidget { - const AnimatedHeight({ + +class AnimatedHeightWidgetExt extends AnimatedHeightWidget { + const AnimatedHeightWidgetExt({ + super.key, + required super.child, + super.curve, + required super.duration, + super.reverseDuration, + super.clipBehavior, + required super.expand, + }); + + @override + State createState() => _AnimatedHeightWidgetExtState(); +} + +class _AnimatedHeightWidgetExtState extends _AnimatedHeightWidgetState { + @override + Widget build(BuildContext context) { + return AnimatedHeightExt( + curve: widget.curve, + duration: widget.duration, + reverseDuration: widget.reverseDuration, + vsync: this, + clipBehavior: widget.clipBehavior, + expand: widget.expand, + child: widget.child, + ); + } +} + +class AnimatedHeightExt extends AnimatedHeight { + const AnimatedHeightExt({ + super.key, + required super.child, + super.curve, + required super.duration, + super.reverseDuration, + required super.vsync, + super.clipBehavior, + required super.expand, + }); + + @override + RenderAnimatedHeight createRenderObject(BuildContext context) { + return RenderAnimatedHeightExt( + duration: duration, + reverseDuration: reverseDuration, + curve: curve, + vsync: vsync, + clipBehavior: clipBehavior, + expand: expand, + ); + } +} + +class RenderAnimatedHeightExt extends RenderAnimatedHeight { + RenderAnimatedHeightExt({ + required super.vsync, + required super.duration, + super.reverseDuration, + super.curve, + super.clipBehavior, + required super.expand, + }); + + bool get _isInvisible => !isAnimating && !expand; + + @override + double animTo(Size childSize) => expand ? childSize.height : 0.0; + + @override + void performLayout() { + if (_isInvisible) { + _heights = const (from: 0, to: 0); + child!.layout(constraints); + size = constraints.constrain(.zero); + return; + } + + super.performLayout(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_isInvisible) { + _clipRectLayer.layer = null; + return; + } + + super.paint(context, offset); + } +} + +class AnimatedHeightWidget extends StatefulWidget { + const AnimatedHeightWidget({ super.key, required this.child, this.curve = Curves.linear, @@ -25,14 +119,14 @@ class AnimatedHeight extends StatefulWidget { final bool expand; @override - State createState() => _AnimatedHeightState(); + State createState() => _AnimatedHeightWidgetState(); } -class _AnimatedHeightState extends State +class _AnimatedHeightWidgetState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - return _AnimatedHeight( + return AnimatedHeight( curve: widget.curve, duration: widget.duration, reverseDuration: widget.reverseDuration, @@ -44,8 +138,9 @@ class _AnimatedHeightState extends State } } -class _AnimatedHeight extends SingleChildRenderObjectWidget { - const _AnimatedHeight({ +class AnimatedHeight extends SingleChildRenderObjectWidget { + const AnimatedHeight({ + super.key, required Widget super.child, this.curve = Curves.linear, required this.duration, @@ -123,7 +218,6 @@ class RenderAnimatedHeight extends RenderProxyBox { late final AnimationController _controller; bool get isAnimating => _controller.isAnimating; - bool get _isInvisible => !isAnimating && !expand; double? _lastValue; Heights? _heights; @@ -178,17 +272,12 @@ class RenderAnimatedHeight extends RenderProxyBox { super.detach(); } + double animTo(Size childSize) => childSize.height; + @override void performLayout() { final BoxConstraints constraints = this.constraints; - if (_isInvisible) { - _heights = const (from: 0, to: 0); - child!.layout(constraints); - size = constraints.constrain(.zero); - return; - } - _lastValue = _controller.value; final childSize = (child!..layout(constraints, parentUsesSize: true)).size; @@ -196,7 +285,7 @@ class RenderAnimatedHeight extends RenderProxyBox { final Size animatedSize; if (isAnimating && _heights != null) { - final to = expand ? childSize.height : 0.0; + final to = animTo(childSize); if (_heights!.to != to) { _heights = (from: size.height, to: to); } @@ -214,11 +303,6 @@ class RenderAnimatedHeight extends RenderProxyBox { @override void paint(PaintingContext context, Offset offset) { - if (_isInvisible) { - _clipRectLayer.layer = null; - return; - } - if (isAnimating && clipBehavior != .none) { final Rect rect = Offset.zero & size; _clipRectLayer.layer = context.pushClipRect( diff --git a/lib/common/widgets/scaffold/bottom_sheet.dart b/lib/common/widgets/scaffold/bottom_sheet.dart index 22224e5da..8061fb544 100644 --- a/lib/common/widgets/scaffold/bottom_sheet.dart +++ b/lib/common/widgets/scaffold/bottom_sheet.dart @@ -33,10 +33,8 @@ class _MiniBottomSheetState extends BottomSheetState { @override void dispose() { super.dispose(); - if (widget.enableDrag) { - _verticalDragGestureRecognizer?.dispose(); - _verticalDragGestureRecognizer = null; - } + _verticalDragGestureRecognizer?.dispose(); + _verticalDragGestureRecognizer = null; } void _onPointerDown(PointerDownEvent event) { diff --git a/lib/pages/audio/view.dart b/lib/pages/audio/view.dart index bbef52f73..35b18eccf 100644 --- a/lib/pages/audio/view.dart +++ b/lib/pages/audio/view.dart @@ -918,9 +918,10 @@ class _AudioPageState extends State { Expanded( child: Center( child: ListView( - key: const PageStorageKey(_AudioPageState), + padding: .zero, shrinkWrap: true, physics: platformClampingPhysics, + key: const PageStorageKey(_AudioPageState), children: [ Center( child: GestureDetector( diff --git a/lib/pages/dynamics_repost/view.dart b/lib/pages/dynamics_repost/view.dart index a9ad292dc..961676e72 100644 --- a/lib/pages/dynamics_repost/view.dart +++ b/lib/pages/dynamics_repost/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/animated_height.dart'; import 'package:PiliPlus/common/widgets/draggable_sheet/dyn.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -29,6 +30,7 @@ class RepostPanel extends CommonRichTextPubPage { this.pic, this.title, this.uname, + super.autofocus = false, }); // video @@ -46,16 +48,16 @@ class RepostPanel extends CommonRichTextPubPage { State createState() => _RepostPanelState(); } -class _RepostPanelState extends CommonRichTextPubPageState { - late bool _isMax = false; - late bool _isExpanded = false; - - late final _key = GlobalKey(); +class _RepostPanelState extends CommonRichTextPubPageState + with SingleTickerProviderStateMixin { + bool _expanded = false; late final String? _pic; late final String _text; late final String? _uname; + static const _durtion = Duration(milliseconds: 300); + @override void initState() { super.initState(); @@ -90,16 +92,15 @@ class _RepostPanelState extends CommonRichTextPubPageState { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); + final theme = Theme.of(context); Widget page([ScrollController? scrollController]) => Column( - key: _isMax ? _key : null, - mainAxisSize: _isMax ? MainAxisSize.max : MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, + mainAxisSize: _expanded ? .max : .min, children: [ - if (!_isMax) const SizedBox(height: 10), + if (!_expanded) const SizedBox(height: 10), _buildAppBar(theme), - if (_isMax) ...[ + if (_expanded) ...[ Expanded( child: ListView( padding: EdgeInsets.zero, @@ -117,32 +118,29 @@ class _RepostPanelState extends CommonRichTextPubPageState { ], ); - Widget child() => _isMax - ? DynDraggableScrollableSheet( - snap: true, - expand: false, - initialChildSize: 1, - minChildSize: 0, - maxChildSize: 1, - snapSizes: const [1], - builder: (context, scrollController) => page(scrollController), - ) - : page(); - - return _isExpanded - ? child() - : AnimatedSize( - alignment: Alignment.topCenter, - curve: Curves.ease, - duration: const Duration(milliseconds: 300), - child: child(), - ); + return AnimatedHeight( + vsync: this, + expand: _expanded, + curve: Curves.ease, + duration: _durtion, + child: _expanded + ? DynDraggableScrollableSheet( + snap: true, + expand: false, + minChildSize: 0, + maxChildSize: 1, + initialChildSize: 1, + snapSizes: const [1], + builder: (context, scrollController) => page(scrollController), + ) + : page(), + ); } List _buildEditPanel(ThemeData theme) => [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: _isMax + padding: const .symmetric(horizontal: 16), + child: _expanded ? _buildEditWidget(theme) : DecoratedBox( decoration: BoxDecoration( @@ -209,11 +207,10 @@ class _RepostPanelState extends CommonRichTextPubPageState { Widget _buildEditPlaceHolder(ThemeData theme) => GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { - setState(() => _isMax = true); - Future.delayed(const Duration(milliseconds: 300), () { + setState(() => _expanded = true); + Future.delayed(_durtion, () { if (mounted) { focusNode.requestFocus(); - setState(() => _isExpanded = true); } }); }, @@ -242,6 +239,7 @@ class _RepostPanelState extends CommonRichTextPubPageState { controller: editController, minLines: 4, maxLines: null, + autofocus: false, focusNode: focusNode, onSubmitted: onSubmitted, readOnly: readOnly.value, @@ -259,7 +257,7 @@ class _RepostPanelState extends CommonRichTextPubPageState { ), ); - Widget _buildAppBar(ThemeData theme) => !_isMax + Widget _buildAppBar(ThemeData theme) => !_expanded ? Row( children: [ const SizedBox(width: 16), diff --git a/lib/pages/pgc_index/view.dart b/lib/pages/pgc_index/view.dart index 31a473066..81b891709 100644 --- a/lib/pages/pgc_index/view.dart +++ b/lib/pages/pgc_index/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/common/widgets/animated_height.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; @@ -73,14 +74,16 @@ class _PgcIndexPageState extends State if (widget.indexType != null) const SliverToBoxAdapter(child: SizedBox(height: 12)), SliverToBoxAdapter( - child: AnimatedSize( - curve: Curves.easeInOut, - alignment: Alignment.topCenter, - duration: const Duration(milliseconds: 200), - child: count > 5 - ? Obx(() => _buildSortsWidget(theme, count, response)) - : _buildSortsWidget(theme, count, response), - ), + child: count > 5 + ? Obx( + () => AnimatedHeightWidget( + curve: Curves.easeInOut, + expand: _ctr.isExpand.value, + duration: const Duration(milliseconds: 200), + child: _buildSortsWidget(theme, count, response), + ), + ) + : _buildSortsWidget(theme, count, response), ), SliverPadding( padding: EdgeInsets.only( @@ -112,42 +115,39 @@ class _PgcIndexPageState extends State Object item, Map indexParams, ) { + final String text; + final String key; + final String? value; + final bool isCurr; + if (item is PgcConditionOrder) { - final isCurr = indexParams['order'] == item.field; - return SearchText( - bgColor: isCurr - ? theme.colorScheme.secondaryContainer - : Colors.transparent, - textColor: isCurr - ? theme.colorScheme.onSecondaryContainer - : theme.colorScheme.onSurfaceVariant, - text: item.name!, - padding: const .symmetric(horizontal: 6, vertical: 3), - onTap: (_) => _ctr - ..indexParams['order'] = item.field - ..onReload(), - ); + text = item.name!; + key = 'order'; + value = item.field; + isCurr = indexParams[key] == item.field; + } else if (item is PgcConditionValue) { + text = item.name!; + if (data.order?.isNotEmpty == true) index -= 1; + key = data.filter![index].field!; + value = item.keyword; + isCurr = indexParams[key] == item.keyword; + } else { + throw UnsupportedError(item.toString()); } - if (item is PgcConditionValue) { - final hasOrder = data.order?.isNotEmpty == true; - if (hasOrder) index -= 1; - final key = data.filter![index].field!; - final isCurr = indexParams[key] == item.keyword; - return SearchText( - bgColor: isCurr - ? theme.colorScheme.secondaryContainer - : Colors.transparent, - textColor: isCurr - ? theme.colorScheme.onSecondaryContainer - : theme.colorScheme.onSurfaceVariant, - text: item.name!, - padding: const .symmetric(horizontal: 6, vertical: 3), - onTap: (_) => _ctr - ..indexParams[key] = item.keyword - ..onReload(), - ); - } - throw UnsupportedError(item.toString()); + + return SearchText( + bgColor: isCurr + ? theme.colorScheme.secondaryContainer + : Colors.transparent, + textColor: isCurr + ? theme.colorScheme.onSecondaryContainer + : theme.colorScheme.onSurfaceVariant, + text: text, + padding: const .symmetric(horizontal: 6, vertical: 3), + onTap: (_) => _ctr + ..indexParams[key] = value + ..onReload(), + ); } Widget _buildSortsWidget( @@ -205,9 +205,7 @@ class _PgcIndexPageState extends State children: [ Text( _ctr.isExpand.value ? '收起' : '展开', - style: TextStyle( - color: theme.colorScheme.outline, - ), + style: TextStyle(color: theme.colorScheme.outline), ), Icon( _ctr.isExpand.value diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart index 7eea81e2d..c735d0d17 100644 --- a/lib/pages/setting/pages/color_select.dart +++ b/lib/pages/setting/pages/color_select.dart @@ -19,6 +19,7 @@ import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/theme_utils.dart'; import 'package:collection/collection.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart'; +import 'package:flutter/foundation.dart' show kReleaseMode; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -50,7 +51,9 @@ class _ColorSelectPageState extends State { val ??= !ctr.dynamicColor.value; if (val && !await MyApp.initPlatformState()) { SmartDialog.showToast('设备可能不支持动态取色'); - return; + if (kReleaseMode) { + return; + } } ctr.dynamicColor.value = val; await GStorage.setting.put(SettingBoxKey.dynamicColor, val); @@ -71,6 +74,9 @@ class _ColorSelectPageState extends State { return SimpleScaffold( appBar: AppBar(title: const Text('选择应用主题')), body: ListView( + padding: .only( + bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + ), children: [ ListTile( onTap: () async { @@ -138,7 +144,7 @@ class _ColorSelectPageState extends State { Padding( padding: padding + const .all(12), child: Obx( - () => AnimatedHeight( + () => AnimatedHeightWidgetExt( expand: !ctr.dynamicColor.value, duration: const Duration(milliseconds: 200), child: Wrap( diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 395d8ff23..7f5b72b63 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -199,6 +199,9 @@ class _PlaySpeedPageState extends State { ), body: ViewSafeArea( child: ListView( + padding: .only( + bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + ), children: [ Padding( padding: const EdgeInsets.only( diff --git a/lib/pages/video/introduction/ugc/view.dart b/lib/pages/video/introduction/ugc/view.dart index e666a7e0c..60e9bd21f 100644 --- a/lib/pages/video/introduction/ugc/view.dart +++ b/lib/pages/video/introduction/ugc/view.dart @@ -141,7 +141,7 @@ class _UgcIntroPanelState extends State { ..._infos(videoDetail) else Obx( - () => AnimatedHeight( + () => AnimatedHeightWidgetExt( expand: introController.expand.value, duration: const Duration(milliseconds: 300), child: TranslucentColumn(