Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flutter_adaptive_scaffold] Allows for the animation duration to be adjusted using SlotLayout.from() #6510

Merged
merged 9 commits into from
Jun 24, 2024
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
Contributor

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
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
2 changes: 1 addition & 1 deletion packages/flutter_adaptive_scaffold/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_adaptive_scaffold
description: Widgets to easily build adaptive layouts, including navigation elements.
version: 0.1.11
version: 0.1.11+1
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold

Expand Down
54 changes: 40 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, const Duration(seconds: 1), 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, const Duration(seconds: 1), 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, const Duration(seconds: 1), 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, const Duration(seconds: 1), tester));
expect(begin, findsOneWidget);
expect(end, findsNothing);

await tester.pumpWidget(slot(500, tester));
await tester.pumpWidget(slot(500, const Duration(seconds: 1), 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, const Duration(seconds: 1), tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
Expand Down Expand Up @@ -243,11 +245,35 @@ 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, const Duration(milliseconds: 100), tester));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);

// expand in 1/5 second.
await tester
.pumpWidget(slot(500, const Duration(milliseconds: 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 +283,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 +329,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 +439,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 +449,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