Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-08-01 11:25:14 +08:00
parent 5c0c6b3cfd
commit 90d8e8b2f2
8 changed files with 199 additions and 111 deletions

View File

@@ -6,8 +6,102 @@ import 'package:flutter/rendering.dart'
typedef Heights = ({double from, double to}); typedef Heights = ({double from, double to});
/// ref [AnimatedSize] /// 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<AnimatedHeightWidget> 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, super.key,
required this.child, required this.child,
this.curve = Curves.linear, this.curve = Curves.linear,
@@ -25,14 +119,14 @@ class AnimatedHeight extends StatefulWidget {
final bool expand; final bool expand;
@override @override
State<AnimatedHeight> createState() => _AnimatedHeightState(); State<AnimatedHeightWidget> createState() => _AnimatedHeightWidgetState();
} }
class _AnimatedHeightState extends State<AnimatedHeight> class _AnimatedHeightWidgetState extends State<AnimatedHeightWidget>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _AnimatedHeight( return AnimatedHeight(
curve: widget.curve, curve: widget.curve,
duration: widget.duration, duration: widget.duration,
reverseDuration: widget.reverseDuration, reverseDuration: widget.reverseDuration,
@@ -44,8 +138,9 @@ class _AnimatedHeightState extends State<AnimatedHeight>
} }
} }
class _AnimatedHeight extends SingleChildRenderObjectWidget { class AnimatedHeight extends SingleChildRenderObjectWidget {
const _AnimatedHeight({ const AnimatedHeight({
super.key,
required Widget super.child, required Widget super.child,
this.curve = Curves.linear, this.curve = Curves.linear,
required this.duration, required this.duration,
@@ -123,7 +218,6 @@ class RenderAnimatedHeight extends RenderProxyBox {
late final AnimationController _controller; late final AnimationController _controller;
bool get isAnimating => _controller.isAnimating; bool get isAnimating => _controller.isAnimating;
bool get _isInvisible => !isAnimating && !expand;
double? _lastValue; double? _lastValue;
Heights? _heights; Heights? _heights;
@@ -178,17 +272,12 @@ class RenderAnimatedHeight extends RenderProxyBox {
super.detach(); super.detach();
} }
double animTo(Size childSize) => childSize.height;
@override @override
void performLayout() { void performLayout() {
final BoxConstraints constraints = this.constraints; final BoxConstraints constraints = this.constraints;
if (_isInvisible) {
_heights = const (from: 0, to: 0);
child!.layout(constraints);
size = constraints.constrain(.zero);
return;
}
_lastValue = _controller.value; _lastValue = _controller.value;
final childSize = (child!..layout(constraints, parentUsesSize: true)).size; final childSize = (child!..layout(constraints, parentUsesSize: true)).size;
@@ -196,7 +285,7 @@ class RenderAnimatedHeight extends RenderProxyBox {
final Size animatedSize; final Size animatedSize;
if (isAnimating && _heights != null) { if (isAnimating && _heights != null) {
final to = expand ? childSize.height : 0.0; final to = animTo(childSize);
if (_heights!.to != to) { if (_heights!.to != to) {
_heights = (from: size.height, to: to); _heights = (from: size.height, to: to);
} }
@@ -214,11 +303,6 @@ class RenderAnimatedHeight extends RenderProxyBox {
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (_isInvisible) {
_clipRectLayer.layer = null;
return;
}
if (isAnimating && clipBehavior != .none) { if (isAnimating && clipBehavior != .none) {
final Rect rect = Offset.zero & size; final Rect rect = Offset.zero & size;
_clipRectLayer.layer = context.pushClipRect( _clipRectLayer.layer = context.pushClipRect(

View File

@@ -33,10 +33,8 @@ class _MiniBottomSheetState extends BottomSheetState {
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
if (widget.enableDrag) { _verticalDragGestureRecognizer?.dispose();
_verticalDragGestureRecognizer?.dispose(); _verticalDragGestureRecognizer = null;
_verticalDragGestureRecognizer = null;
}
} }
void _onPointerDown(PointerDownEvent event) { void _onPointerDown(PointerDownEvent event) {

View File

@@ -918,9 +918,10 @@ class _AudioPageState extends State<AudioPage> {
Expanded( Expanded(
child: Center( child: Center(
child: ListView( child: ListView(
key: const PageStorageKey(_AudioPageState), padding: .zero,
shrinkWrap: true, shrinkWrap: true,
physics: platformClampingPhysics, physics: platformClampingPhysics,
key: const PageStorageKey(_AudioPageState),
children: [ children: [
Center( Center(
child: GestureDetector( child: GestureDetector(

View File

@@ -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/draggable_sheet/dyn.dart';
import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
@@ -29,6 +30,7 @@ class RepostPanel extends CommonRichTextPubPage {
this.pic, this.pic,
this.title, this.title,
this.uname, this.uname,
super.autofocus = false,
}); });
// video // video
@@ -46,16 +48,16 @@ class RepostPanel extends CommonRichTextPubPage {
State<RepostPanel> createState() => _RepostPanelState(); State<RepostPanel> createState() => _RepostPanelState();
} }
class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> { class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel>
late bool _isMax = false; with SingleTickerProviderStateMixin {
late bool _isExpanded = false; bool _expanded = false;
late final _key = GlobalKey();
late final String? _pic; late final String? _pic;
late final String _text; late final String _text;
late final String? _uname; late final String? _uname;
static const _durtion = Duration(milliseconds: 300);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -90,16 +92,15 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final theme = Theme.of(context);
Widget page([ScrollController? scrollController]) => Column( Widget page([ScrollController? scrollController]) => Column(
key: _isMax ? _key : null, crossAxisAlignment: .start,
mainAxisSize: _isMax ? MainAxisSize.max : MainAxisSize.min, mainAxisSize: _expanded ? .max : .min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (!_isMax) const SizedBox(height: 10), if (!_expanded) const SizedBox(height: 10),
_buildAppBar(theme), _buildAppBar(theme),
if (_isMax) ...[ if (_expanded) ...[
Expanded( Expanded(
child: ListView( child: ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@@ -117,32 +118,29 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
], ],
); );
Widget child() => _isMax return AnimatedHeight(
? DynDraggableScrollableSheet( vsync: this,
snap: true, expand: _expanded,
expand: false, curve: Curves.ease,
initialChildSize: 1, duration: _durtion,
minChildSize: 0, child: _expanded
maxChildSize: 1, ? DynDraggableScrollableSheet(
snapSizes: const [1], snap: true,
builder: (context, scrollController) => page(scrollController), expand: false,
) minChildSize: 0,
: page(); maxChildSize: 1,
initialChildSize: 1,
return _isExpanded snapSizes: const [1],
? child() builder: (context, scrollController) => page(scrollController),
: AnimatedSize( )
alignment: Alignment.topCenter, : page(),
curve: Curves.ease, );
duration: const Duration(milliseconds: 300),
child: child(),
);
} }
List<Widget> _buildEditPanel(ThemeData theme) => [ List<Widget> _buildEditPanel(ThemeData theme) => [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const .symmetric(horizontal: 16),
child: _isMax child: _expanded
? _buildEditWidget(theme) ? _buildEditWidget(theme)
: DecoratedBox( : DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -209,11 +207,10 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
Widget _buildEditPlaceHolder(ThemeData theme) => GestureDetector( Widget _buildEditPlaceHolder(ThemeData theme) => GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
setState(() => _isMax = true); setState(() => _expanded = true);
Future.delayed(const Duration(milliseconds: 300), () { Future.delayed(_durtion, () {
if (mounted) { if (mounted) {
focusNode.requestFocus(); focusNode.requestFocus();
setState(() => _isExpanded = true);
} }
}); });
}, },
@@ -242,6 +239,7 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
controller: editController, controller: editController,
minLines: 4, minLines: 4,
maxLines: null, maxLines: null,
autofocus: false,
focusNode: focusNode, focusNode: focusNode,
onSubmitted: onSubmitted, onSubmitted: onSubmitted,
readOnly: readOnly.value, readOnly: readOnly.value,
@@ -259,7 +257,7 @@ class _RepostPanelState extends CommonRichTextPubPageState<RepostPanel> {
), ),
); );
Widget _buildAppBar(ThemeData theme) => !_isMax Widget _buildAppBar(ThemeData theme) => !_expanded
? Row( ? Row(
children: [ children: [
const SizedBox(width: 16), const SizedBox(width: 16),

View File

@@ -1,4 +1,5 @@
import 'package:PiliPlus/common/style.dart'; 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/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart';
@@ -73,14 +74,16 @@ class _PgcIndexPageState extends State<PgcIndexPage>
if (widget.indexType != null) if (widget.indexType != null)
const SliverToBoxAdapter(child: SizedBox(height: 12)), const SliverToBoxAdapter(child: SizedBox(height: 12)),
SliverToBoxAdapter( SliverToBoxAdapter(
child: AnimatedSize( child: count > 5
curve: Curves.easeInOut, ? Obx(
alignment: Alignment.topCenter, () => AnimatedHeightWidget(
duration: const Duration(milliseconds: 200), curve: Curves.easeInOut,
child: count > 5 expand: _ctr.isExpand.value,
? Obx(() => _buildSortsWidget(theme, count, response)) duration: const Duration(milliseconds: 200),
: _buildSortsWidget(theme, count, response), child: _buildSortsWidget(theme, count, response),
), ),
)
: _buildSortsWidget(theme, count, response),
), ),
SliverPadding( SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@@ -112,42 +115,39 @@ class _PgcIndexPageState extends State<PgcIndexPage>
Object item, Object item,
Map<String, dynamic> indexParams, Map<String, dynamic> indexParams,
) { ) {
final String text;
final String key;
final String? value;
final bool isCurr;
if (item is PgcConditionOrder) { if (item is PgcConditionOrder) {
final isCurr = indexParams['order'] == item.field; text = item.name!;
return SearchText( key = 'order';
bgColor: isCurr value = item.field;
? theme.colorScheme.secondaryContainer isCurr = indexParams[key] == item.field;
: Colors.transparent, } else if (item is PgcConditionValue) {
textColor: isCurr text = item.name!;
? theme.colorScheme.onSecondaryContainer if (data.order?.isNotEmpty == true) index -= 1;
: theme.colorScheme.onSurfaceVariant, key = data.filter![index].field!;
text: item.name!, value = item.keyword;
padding: const .symmetric(horizontal: 6, vertical: 3), isCurr = indexParams[key] == item.keyword;
onTap: (_) => _ctr } else {
..indexParams['order'] = item.field throw UnsupportedError(item.toString());
..onReload(),
);
} }
if (item is PgcConditionValue) {
final hasOrder = data.order?.isNotEmpty == true; return SearchText(
if (hasOrder) index -= 1; bgColor: isCurr
final key = data.filter![index].field!; ? theme.colorScheme.secondaryContainer
final isCurr = indexParams[key] == item.keyword; : Colors.transparent,
return SearchText( textColor: isCurr
bgColor: isCurr ? theme.colorScheme.onSecondaryContainer
? theme.colorScheme.secondaryContainer : theme.colorScheme.onSurfaceVariant,
: Colors.transparent, text: text,
textColor: isCurr padding: const .symmetric(horizontal: 6, vertical: 3),
? theme.colorScheme.onSecondaryContainer onTap: (_) => _ctr
: theme.colorScheme.onSurfaceVariant, ..indexParams[key] = value
text: item.name!, ..onReload(),
padding: const .symmetric(horizontal: 6, vertical: 3), );
onTap: (_) => _ctr
..indexParams[key] = item.keyword
..onReload(),
);
}
throw UnsupportedError(item.toString());
} }
Widget _buildSortsWidget( Widget _buildSortsWidget(
@@ -205,9 +205,7 @@ class _PgcIndexPageState extends State<PgcIndexPage>
children: [ children: [
Text( Text(
_ctr.isExpand.value ? '收起' : '展开', _ctr.isExpand.value ? '收起' : '展开',
style: TextStyle( style: TextStyle(color: theme.colorScheme.outline),
color: theme.colorScheme.outline,
),
), ),
Icon( Icon(
_ctr.isExpand.value _ctr.isExpand.value

View File

@@ -19,6 +19,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart'; import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flex_seed_scheme/flex_seed_scheme.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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -50,7 +51,9 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
val ??= !ctr.dynamicColor.value; val ??= !ctr.dynamicColor.value;
if (val && !await MyApp.initPlatformState()) { if (val && !await MyApp.initPlatformState()) {
SmartDialog.showToast('设备可能不支持动态取色'); SmartDialog.showToast('设备可能不支持动态取色');
return; if (kReleaseMode) {
return;
}
} }
ctr.dynamicColor.value = val; ctr.dynamicColor.value = val;
await GStorage.setting.put(SettingBoxKey.dynamicColor, val); await GStorage.setting.put(SettingBoxKey.dynamicColor, val);
@@ -71,6 +74,9 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
return SimpleScaffold( return SimpleScaffold(
appBar: AppBar(title: const Text('选择应用主题')), appBar: AppBar(title: const Text('选择应用主题')),
body: ListView( body: ListView(
padding: .only(
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
children: [ children: [
ListTile( ListTile(
onTap: () async { onTap: () async {
@@ -138,7 +144,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
Padding( Padding(
padding: padding + const .all(12), padding: padding + const .all(12),
child: Obx( child: Obx(
() => AnimatedHeight( () => AnimatedHeightWidgetExt(
expand: !ctr.dynamicColor.value, expand: !ctr.dynamicColor.value,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Wrap( child: Wrap(

View File

@@ -199,6 +199,9 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
), ),
body: ViewSafeArea( body: ViewSafeArea(
child: ListView( child: ListView(
padding: .only(
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(

View File

@@ -141,7 +141,7 @@ class _UgcIntroPanelState extends State<UgcIntroPanel> {
..._infos(videoDetail) ..._infos(videoDetail)
else else
Obx( Obx(
() => AnimatedHeight( () => AnimatedHeightWidgetExt(
expand: introController.expand.value, expand: introController.expand.value,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: TranslucentColumn( child: TranslucentColumn(