mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-12 12:20:13 +08:00
245
lib/common/widgets/animated_height.dart
Normal file
245
lib/common/widgets/animated_height.dart
Normal file
@@ -0,0 +1,245 @@
|
||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart'
|
||||
show ClipRectLayer, LayerHandle, RenderAnimatedSize, RenderProxyBox;
|
||||
|
||||
typedef Heights = ({double from, double to});
|
||||
|
||||
/// ref [AnimatedSize]
|
||||
class AnimatedHeight extends StatefulWidget {
|
||||
const AnimatedHeight({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.curve = Curves.linear,
|
||||
required this.duration,
|
||||
this.reverseDuration,
|
||||
this.clipBehavior = .hardEdge,
|
||||
required this.expand,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Curve curve;
|
||||
final Duration duration;
|
||||
final Duration? reverseDuration;
|
||||
final Clip clipBehavior;
|
||||
final bool expand;
|
||||
|
||||
@override
|
||||
State<AnimatedHeight> createState() => _AnimatedHeightState();
|
||||
}
|
||||
|
||||
class _AnimatedHeightState extends State<AnimatedHeight>
|
||||
with SingleTickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _AnimatedHeight(
|
||||
curve: widget.curve,
|
||||
duration: widget.duration,
|
||||
reverseDuration: widget.reverseDuration,
|
||||
vsync: this,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
expand: widget.expand,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedHeight extends SingleChildRenderObjectWidget {
|
||||
const _AnimatedHeight({
|
||||
required Widget super.child,
|
||||
this.curve = Curves.linear,
|
||||
required this.duration,
|
||||
this.reverseDuration,
|
||||
required this.vsync,
|
||||
this.clipBehavior = .hardEdge,
|
||||
required this.expand,
|
||||
});
|
||||
|
||||
final Curve curve;
|
||||
final Duration duration;
|
||||
final Duration? reverseDuration;
|
||||
final TickerProvider vsync;
|
||||
final Clip clipBehavior;
|
||||
final bool expand;
|
||||
|
||||
@override
|
||||
RenderAnimatedHeight createRenderObject(BuildContext context) {
|
||||
return RenderAnimatedHeight(
|
||||
duration: duration,
|
||||
reverseDuration: reverseDuration,
|
||||
curve: curve,
|
||||
vsync: vsync,
|
||||
clipBehavior: clipBehavior,
|
||||
expand: expand,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderAnimatedHeight renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..duration = duration
|
||||
..reverseDuration = reverseDuration
|
||||
..curve = curve
|
||||
..vsync = vsync
|
||||
..clipBehavior = clipBehavior
|
||||
..expand = expand;
|
||||
}
|
||||
}
|
||||
|
||||
/// ref [RenderAnimatedSize]
|
||||
class RenderAnimatedHeight extends RenderProxyBox {
|
||||
RenderAnimatedHeight({
|
||||
required this._vsync,
|
||||
required Duration duration,
|
||||
Duration? reverseDuration,
|
||||
this._curve = Curves.linear,
|
||||
this._clipBehavior = .hardEdge,
|
||||
required this._expand,
|
||||
}) {
|
||||
_controller =
|
||||
AnimationController(
|
||||
vsync: vsync,
|
||||
value: expand ? 1.0 : 0.0,
|
||||
duration: duration,
|
||||
reverseDuration: reverseDuration,
|
||||
)..addListener(() {
|
||||
if (_controller.value != _lastValue) {
|
||||
markNeedsLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _expand;
|
||||
bool get expand => _expand;
|
||||
set expand(bool value) {
|
||||
if (_expand == value) return;
|
||||
_expand = value;
|
||||
_lastValue = 0.0;
|
||||
_controller.forward(from: 0);
|
||||
}
|
||||
|
||||
late final AnimationController _controller;
|
||||
bool get isAnimating => _controller.isAnimating;
|
||||
bool get _isInvisible => !isAnimating && !expand;
|
||||
|
||||
double? _lastValue;
|
||||
Heights? _heights;
|
||||
|
||||
Duration get duration => _controller.duration!;
|
||||
set duration(Duration value) {
|
||||
if (value == _controller.duration) {
|
||||
return;
|
||||
}
|
||||
_controller.duration = value;
|
||||
}
|
||||
|
||||
Duration? get reverseDuration => _controller.reverseDuration;
|
||||
set reverseDuration(Duration? value) {
|
||||
if (value == _controller.reverseDuration) {
|
||||
return;
|
||||
}
|
||||
_controller.reverseDuration = value;
|
||||
}
|
||||
|
||||
Curve _curve;
|
||||
Curve get curve => _curve;
|
||||
set curve(Curve value) {
|
||||
if (value == _curve) {
|
||||
return;
|
||||
}
|
||||
_curve = value;
|
||||
}
|
||||
|
||||
Clip get clipBehavior => _clipBehavior;
|
||||
Clip _clipBehavior = .hardEdge;
|
||||
set clipBehavior(Clip value) {
|
||||
if (value != _clipBehavior) {
|
||||
_clipBehavior = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
TickerProvider get vsync => _vsync;
|
||||
TickerProvider _vsync;
|
||||
set vsync(TickerProvider value) {
|
||||
if (value == _vsync) {
|
||||
return;
|
||||
}
|
||||
_vsync = value;
|
||||
_controller.resync(vsync);
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
_controller.stop();
|
||||
super.detach();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
if (_isInvisible) {
|
||||
_heights = const (from: 0, to: 0);
|
||||
size = .zero;
|
||||
return;
|
||||
}
|
||||
|
||||
_lastValue = _controller.value;
|
||||
|
||||
final BoxConstraints constraints = this.constraints;
|
||||
final childSize = (child!..layout(constraints, parentUsesSize: true)).size;
|
||||
|
||||
final Size animatedSize;
|
||||
|
||||
if (isAnimating && _heights != null) {
|
||||
final to = expand ? childSize.height : 0.0;
|
||||
if (_heights!.to != to) {
|
||||
_heights = (from: size.height, to: to);
|
||||
}
|
||||
animatedSize = Size(
|
||||
childSize.width,
|
||||
curve.transform(_controller.value).lerp(_heights!.from, _heights!.to),
|
||||
);
|
||||
} else {
|
||||
animatedSize = childSize;
|
||||
_heights = (from: childSize.height, to: childSize.height);
|
||||
}
|
||||
|
||||
size = constraints.constrain(animatedSize);
|
||||
}
|
||||
|
||||
@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(
|
||||
needsCompositing,
|
||||
offset,
|
||||
rect,
|
||||
super.paint,
|
||||
clipBehavior: clipBehavior,
|
||||
oldLayer: _clipRectLayer.layer,
|
||||
);
|
||||
} else {
|
||||
_clipRectLayer.layer = null;
|
||||
super.paint(context, offset);
|
||||
}
|
||||
}
|
||||
|
||||
final LayerHandle<ClipRectLayer> _clipRectLayer =
|
||||
LayerHandle<ClipRectLayer>();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_clipRectLayer.layer = null;
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
260
lib/common/widgets/animated_multi_height.dart
Normal file
260
lib/common/widgets/animated_multi_height.dart
Normal file
@@ -0,0 +1,260 @@
|
||||
import 'package:PiliPlus/common/widgets/animated_height.dart' show Heights;
|
||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart'
|
||||
show ClipRectLayer, LayerHandle, PipelineOwner, RenderProxyBox;
|
||||
|
||||
class AnimatedMultiHeight extends StatefulWidget {
|
||||
const AnimatedMultiHeight({
|
||||
super.key,
|
||||
required Widget this.child,
|
||||
this.curve = Curves.linear,
|
||||
required this.duration,
|
||||
this.reverseDuration,
|
||||
this.clipBehavior = .hardEdge,
|
||||
this.onEnd,
|
||||
required this.expand,
|
||||
});
|
||||
|
||||
final Widget? child;
|
||||
final Curve curve;
|
||||
final Duration duration;
|
||||
final Duration? reverseDuration;
|
||||
final Clip clipBehavior;
|
||||
final VoidCallback? onEnd;
|
||||
final bool expand;
|
||||
|
||||
@override
|
||||
State<AnimatedMultiHeight> createState() => _AnimatedMultiHeightState();
|
||||
}
|
||||
|
||||
class _AnimatedMultiHeightState extends State<AnimatedMultiHeight>
|
||||
with SingleTickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _AnimatedMultiHeight(
|
||||
curve: widget.curve,
|
||||
duration: widget.duration,
|
||||
reverseDuration: widget.reverseDuration,
|
||||
vsync: this,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
onEnd: widget.onEnd,
|
||||
expand: widget.expand,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedMultiHeight extends SingleChildRenderObjectWidget {
|
||||
const _AnimatedMultiHeight({
|
||||
super.child,
|
||||
this.curve = Curves.linear,
|
||||
required this.duration,
|
||||
this.reverseDuration,
|
||||
required this.vsync,
|
||||
this.clipBehavior = .hardEdge,
|
||||
this.onEnd,
|
||||
required this.expand,
|
||||
});
|
||||
|
||||
final Curve curve;
|
||||
final Duration duration;
|
||||
final Duration? reverseDuration;
|
||||
final TickerProvider vsync;
|
||||
final Clip clipBehavior;
|
||||
final VoidCallback? onEnd;
|
||||
final bool expand;
|
||||
|
||||
@override
|
||||
RenderAnimatedMultiHeight createRenderObject(BuildContext context) {
|
||||
return RenderAnimatedMultiHeight(
|
||||
duration: duration,
|
||||
reverseDuration: reverseDuration,
|
||||
curve: curve,
|
||||
vsync: vsync,
|
||||
clipBehavior: clipBehavior,
|
||||
onEnd: onEnd,
|
||||
expand: expand,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderAnimatedMultiHeight renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..duration = duration
|
||||
..reverseDuration = reverseDuration
|
||||
..curve = curve
|
||||
..vsync = vsync
|
||||
..clipBehavior = clipBehavior
|
||||
..onEnd = onEnd
|
||||
..expand = expand;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderAnimatedMultiHeight extends RenderProxyBox {
|
||||
RenderAnimatedMultiHeight({
|
||||
required TickerProvider vsync,
|
||||
required Duration duration,
|
||||
Duration? reverseDuration,
|
||||
this._curve = Curves.linear,
|
||||
this._clipBehavior = .hardEdge,
|
||||
this._onEnd,
|
||||
required this._expand,
|
||||
}) : _vsync = vsync {
|
||||
_controller =
|
||||
AnimationController(
|
||||
vsync: vsync,
|
||||
value: _expand ? 1.0 : 0.0,
|
||||
duration: duration,
|
||||
reverseDuration: reverseDuration,
|
||||
)..addListener(() {
|
||||
if (_controller.value != _lastValue) {
|
||||
markNeedsLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _expand;
|
||||
bool get expand => _expand;
|
||||
set expand(bool value) {
|
||||
if (_expand == value) return;
|
||||
_expand = value;
|
||||
_lastValue = 0.0;
|
||||
_controller.forward(from: 0.0);
|
||||
}
|
||||
|
||||
late final AnimationController _controller;
|
||||
bool get isAnimating => _controller.isAnimating;
|
||||
|
||||
double? _lastValue;
|
||||
Heights? _heights;
|
||||
|
||||
Duration get duration => _controller.duration!;
|
||||
set duration(Duration value) {
|
||||
if (value == _controller.duration) {
|
||||
return;
|
||||
}
|
||||
_controller.duration = value;
|
||||
}
|
||||
|
||||
Duration? get reverseDuration => _controller.reverseDuration;
|
||||
set reverseDuration(Duration? value) {
|
||||
if (value == _controller.reverseDuration) {
|
||||
return;
|
||||
}
|
||||
_controller.reverseDuration = value;
|
||||
}
|
||||
|
||||
Curve _curve;
|
||||
Curve get curve => _curve;
|
||||
set curve(Curve value) {
|
||||
if (value == _curve) {
|
||||
return;
|
||||
}
|
||||
_curve = value;
|
||||
}
|
||||
|
||||
Clip get clipBehavior => _clipBehavior;
|
||||
Clip _clipBehavior = .hardEdge;
|
||||
set clipBehavior(Clip value) {
|
||||
if (value != _clipBehavior) {
|
||||
_clipBehavior = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
TickerProvider get vsync => _vsync;
|
||||
TickerProvider _vsync;
|
||||
set vsync(TickerProvider value) {
|
||||
if (value == _vsync) {
|
||||
return;
|
||||
}
|
||||
_vsync = value;
|
||||
_controller.resync(vsync);
|
||||
}
|
||||
|
||||
VoidCallback? get onEnd => _onEnd;
|
||||
VoidCallback? _onEnd;
|
||||
set onEnd(VoidCallback? value) {
|
||||
if (value == _onEnd) {
|
||||
return;
|
||||
}
|
||||
_onEnd = value;
|
||||
}
|
||||
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
_controller.addStatusListener(_animationStatusListener);
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
_controller
|
||||
..stop()
|
||||
..removeStatusListener(_animationStatusListener);
|
||||
super.detach();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
_lastValue = _controller.value;
|
||||
|
||||
final BoxConstraints constraints = this.constraints;
|
||||
final childSize = (child!..layout(constraints, parentUsesSize: true)).size;
|
||||
|
||||
final Size animatedSize;
|
||||
if (isAnimating && _heights != null) {
|
||||
final to = childSize.height;
|
||||
if (_heights!.to != to) {
|
||||
_heights = (from: size.height, to: to);
|
||||
}
|
||||
animatedSize = Size(
|
||||
childSize.width,
|
||||
curve.transform(_controller.value).lerp(_heights!.from, _heights!.to),
|
||||
);
|
||||
} else {
|
||||
animatedSize = childSize;
|
||||
_heights = (from: childSize.height, to: childSize.height);
|
||||
}
|
||||
|
||||
size = constraints.constrain(animatedSize);
|
||||
}
|
||||
|
||||
void _animationStatusListener(AnimationStatus status) {
|
||||
if (status.isCompleted) {
|
||||
_onEnd?.call();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (isAnimating && clipBehavior != .none) {
|
||||
final Rect rect = Offset.zero & size;
|
||||
_clipRectLayer.layer = context.pushClipRect(
|
||||
needsCompositing,
|
||||
offset,
|
||||
rect,
|
||||
super.paint,
|
||||
clipBehavior: clipBehavior,
|
||||
oldLayer: _clipRectLayer.layer,
|
||||
);
|
||||
} else {
|
||||
_clipRectLayer.layer = null;
|
||||
super.paint(context, offset);
|
||||
}
|
||||
}
|
||||
|
||||
final LayerHandle<ClipRectLayer> _clipRectLayer =
|
||||
LayerHandle<ClipRectLayer>();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_clipRectLayer.layer = null;
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPlus/common/widgets/animated_multi_height.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExpandablePanel extends StatelessWidget {
|
||||
@@ -102,6 +103,7 @@ class _AnimatedCrossState extends State<_AnimatedCross> {
|
||||
const Key kFirstChildKey = ValueKey<CrossFadeState>(.showFirst);
|
||||
const Key kSecondChildKey = ValueKey<CrossFadeState>(.showSecond);
|
||||
|
||||
final bool expand;
|
||||
final Key topKey;
|
||||
Widget topChild;
|
||||
final Key bottomKey;
|
||||
@@ -109,30 +111,29 @@ class _AnimatedCrossState extends State<_AnimatedCross> {
|
||||
|
||||
switch (widget.crossFadeState) {
|
||||
case .showFirst:
|
||||
expand = false;
|
||||
topKey = kFirstChildKey;
|
||||
topChild = firstChild;
|
||||
bottomKey = kSecondChildKey;
|
||||
bottomChild = secondChild;
|
||||
case .showSecond:
|
||||
expand = true;
|
||||
topKey = kSecondChildKey;
|
||||
topChild = secondChild;
|
||||
bottomKey = kFirstChildKey;
|
||||
bottomChild = firstChild;
|
||||
}
|
||||
|
||||
return ClipRect(
|
||||
child: AnimatedSize(
|
||||
clipBehavior: .none,
|
||||
alignment: widget.alignment,
|
||||
duration: widget.duration,
|
||||
curve: widget.sizeCurve,
|
||||
onEnd: _onEnd,
|
||||
child: AnimatedCrossFade.defaultLayoutBuilder(
|
||||
topChild,
|
||||
topKey,
|
||||
bottomChild,
|
||||
bottomKey,
|
||||
),
|
||||
return AnimatedMultiHeight(
|
||||
duration: widget.duration,
|
||||
curve: widget.sizeCurve,
|
||||
onEnd: _onEnd,
|
||||
expand: expand,
|
||||
child: AnimatedCrossFade.defaultLayoutBuilder(
|
||||
topChild,
|
||||
topKey,
|
||||
bottomChild,
|
||||
bottomKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/animated_height.dart';
|
||||
import 'package:PiliPlus/common/widgets/color_palette.dart';
|
||||
import 'package:PiliPlus/main.dart' show MyApp;
|
||||
import 'package:PiliPlus/models/common/nav_bar_config.dart';
|
||||
@@ -135,56 +136,50 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: padding,
|
||||
child: AnimatedSize(
|
||||
curve: Curves.easeInOut,
|
||||
alignment: Alignment.topCenter,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Obx(
|
||||
() => ctr.dynamicColor.value
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 22,
|
||||
runSpacing: 18,
|
||||
children: colorThemeTypes.mapIndexed(
|
||||
(index, item) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
ctr.currentColor.value = index;
|
||||
GStorage.setting
|
||||
.put(SettingBoxKey.customColor, index)
|
||||
.whenComplete(Get.updateMyAppTheme);
|
||||
},
|
||||
child: Column(
|
||||
spacing: 3,
|
||||
children: [
|
||||
ColorPalette(
|
||||
colorScheme: item.color.asColorSchemeSeed(
|
||||
_dynamicSchemeVariant,
|
||||
theme.brightness,
|
||||
),
|
||||
selected: ctr.currentColor.value == index,
|
||||
),
|
||||
Text(
|
||||
item.label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: ctr.currentColor.value != index
|
||||
? theme.colorScheme.outline
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
padding: padding + const .all(12),
|
||||
child: Obx(
|
||||
() => AnimatedHeight(
|
||||
expand: ctr.dynamicColor.value,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Wrap(
|
||||
alignment: .center,
|
||||
spacing: 22,
|
||||
runSpacing: 18,
|
||||
children: colorThemeTypes.mapIndexed(
|
||||
(i, e) {
|
||||
return GestureDetector(
|
||||
behavior: .opaque,
|
||||
onTap: () {
|
||||
ctr.currentColor.value = i;
|
||||
GStorage.setting
|
||||
.put(SettingBoxKey.customColor, i)
|
||||
.whenComplete(Get.updateMyAppTheme);
|
||||
},
|
||||
child: Column(
|
||||
spacing: 3,
|
||||
children: [
|
||||
ColorPalette(
|
||||
colorScheme: e.color.asColorSchemeSeed(
|
||||
_dynamicSchemeVariant,
|
||||
theme.brightness,
|
||||
),
|
||||
selected: ctr.currentColor.value == i,
|
||||
),
|
||||
Text(
|
||||
e.label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: ctr.currentColor.value != i
|
||||
? theme.colorScheme.outline
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:PiliPlus/common/assets.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/animated_height.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
import 'package:PiliPlus/common/widgets/expandable.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
|
||||
@@ -235,14 +236,14 @@ class _UgcIntroPanelState extends State<UgcIntroPanel> {
|
||||
..._infos(theme, videoDetail)
|
||||
else
|
||||
Obx(
|
||||
() => ExpandablePanel(
|
||||
collapsed: const SizedBox(width: .infinity, height: 0),
|
||||
expanded: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
() => AnimatedHeight(
|
||||
expand: introController.expand.value,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
crossAxisAlignment: .start,
|
||||
children: _infos(theme, videoDetail),
|
||||
),
|
||||
expand: introController.expand.value,
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
|
||||
Reference in New Issue
Block a user