Skip to content
5 changes: 5 additions & 0 deletions packages/flutter_adaptive_scaffold/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.1.11+1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that the version change itself was lost from the PR, which is part of why CI is failing.


* Allows custom animation duration for the NavigationRail and
BottomNavigationBar transitions. [flutter/flutter#112938](https://github.com/flutter/flutter/issues/112938)

## 0.1.11

* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
Expand Down
8 changes: 7 additions & 1 deletion packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,14 @@ class SlotLayout extends StatefulWidget {
WidgetBuilder? builder,
Widget Function(Widget, Animation<double>)? inAnimation,
Widget Function(Widget, Animation<double>)? outAnimation,
Duration? duration,
required Key key,
}) =>
SlotLayoutConfig._(
builder: builder,
inAnimation: inAnimation,
outAnimation: outAnimation,
duration: duration,
key: key,
);

Expand All @@ -96,7 +98,7 @@ class _SlotLayoutState extends State<SlotLayout>
chosenWidget = SlotLayout.pickWidget(context, widget.config);
bool hasAnimation = false;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
duration: chosenWidget?.duration ?? const Duration(milliseconds: 1000),
layoutBuilder: (Widget? currentChild, List<Widget> previousChildren) {
final Stack elements = Stack(
children: <Widget>[
Expand Down Expand Up @@ -137,6 +139,7 @@ class SlotLayoutConfig extends StatelessWidget {
required this.builder,
this.inAnimation,
this.outAnimation,
this.duration,
});

/// The child Widget that [SlotLayout] eventually returns with an animation.
Expand All @@ -160,6 +163,9 @@ class SlotLayoutConfig extends StatelessWidget {
/// as the returned widget.
final Widget Function(Widget, Animation<double>)? outAnimation;

/// The amount of time taken by the execution of the in and out animations.
final Duration? duration;

/// An empty [SlotLayoutConfig] to be placed in a slot to indicate that the slot
/// should show nothing.
static SlotLayoutConfig empty() {
Expand Down
52 changes: 38 additions & 14 deletions packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,13 @@ void main() {
testWidgets(
'slot layout properly switches between items with the appropriate animation',
(WidgetTester tester) async {
await tester.pumpWidget(slot(300, tester));
await tester
.pumpWidget(slot(300, const Duration(milliseconds: 1000), tester));
expect(begin, findsOneWidget);
expect(end, findsNothing);

await tester.pumpWidget(slot(500, tester));
await tester
.pumpWidget(slot(500, const Duration(milliseconds: 1000), tester));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
Expand All @@ -146,7 +148,7 @@ void main() {
testWidgets('AnimatedSwitcher does not spawn duplicate keys on rapid resize',
(WidgetTester tester) async {
// Populate the smaller slot layout and let the animation settle.
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, 1000, tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
Expand All @@ -157,12 +159,12 @@ void main() {
for (int i = 0; i < 2; i++) {
// Resize between the two slot layouts, but do not pump the animation
// until completion.
await tester.pumpWidget(slot(500, tester));
await tester.pumpWidget(slot(500, 1000, tester));
await tester.pump(const Duration(milliseconds: 100));
expect(begin, findsOneWidget);
expect(end, findsOneWidget);

await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, 1000, tester));
await tester.pump(const Duration(milliseconds: 100));
expect(begin, findsOneWidget);
expect(end, findsOneWidget);
Expand All @@ -171,18 +173,18 @@ void main() {

testWidgets('slot layout can tolerate rapid changes in breakpoints',
(WidgetTester tester) async {
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, 1000, tester));
expect(begin, findsOneWidget);
expect(end, findsNothing);

await tester.pumpWidget(slot(500, tester));
await tester.pumpWidget(slot(500, 1000, tester));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
offsetMoreOrLessEquals(const Offset(-0.1, 0), epsilon: 0.05));
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
offsetMoreOrLessEquals(const Offset(-0.9, 0), epsilon: 0.05));
await tester.pumpWidget(slot(300, tester));
await tester.pumpWidget(slot(300, 1000, tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
Expand Down Expand Up @@ -243,11 +245,33 @@ void main() {
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
});

testWidgets('adaptive layout can adjust animation duration',
(WidgetTester tester) async {
// Populate the smaller slot layout and let the animation settle.
await tester.pumpWidget(slot(300, 100, tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);

// expand in 1/5 second.
await tester.pumpWidget(slot(500, 200, tester));

// after 100ms, we expect both widgets to be present.
await tester.pump(const Duration(milliseconds: 50));
expect(begin, findsOneWidget);
expect(end, findsOneWidget);

// After 1/5 second, all animations should be done.
await tester.pump(const Duration(milliseconds: 200));
expect(begin, findsNothing);
expect(end, findsOneWidget);

await tester.pumpAndSettle();
});

testWidgets('adaptive layout does not animate when animations off',
(WidgetTester tester) async {
final Finder testBreakpoint = find.byKey(const Key('Test Breakpoint'));
final Finder secondaryTestBreakpoint =
find.byKey(const Key('Secondary Test Breakpoint'));

await tester.pumpWidget(
await layout(width: 400, tester: tester, animations: false));
Expand All @@ -257,9 +281,6 @@ void main() {

expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10));
expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790));
expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10));
expect(
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
});
}

Expand Down Expand Up @@ -306,6 +327,7 @@ Future<MediaQuery> layout({
TextDirection directionality = TextDirection.ltr,
double? bodyRatio,
bool animations = true,
int durationMs = 1000,
}) async {
await tester.binding.setSurfaceSize(Size(width, 800));
return MediaQuery(
Expand Down Expand Up @@ -415,7 +437,7 @@ AnimatedWidget leftInOut(Widget child, Animation<double> animation) {
);
}

MediaQuery slot(double width, WidgetTester tester) {
MediaQuery slot(double width, Duration duration, WidgetTester tester) {
return MediaQuery(
data: MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 800)),
child: Directionality(
Expand All @@ -425,12 +447,14 @@ MediaQuery slot(double width, WidgetTester tester) {
TestBreakpoint0(): SlotLayout.from(
inAnimation: leftOutIn,
outAnimation: leftInOut,
duration: duration,
key: const Key('0'),
builder: (_) => const SizedBox(width: 10, height: 10),
),
TestBreakpoint400(): SlotLayout.from(
inAnimation: leftOutIn,
outAnimation: leftInOut,
duration: duration,
key: const Key('400'),
builder: (_) => const SizedBox(width: 10, height: 10),
),
Expand Down