Skip to content

Commit

Permalink
Adds go router push animations (#231)
Browse files Browse the repository at this point in the history
These assets will be used in go_router api doc
  • Loading branch information
chunhtai authored Dec 19, 2023
1 parent ee07afa commit 008a2dd
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 31 deletions.
Binary file added assets/go_router/push_different_shell.mp4
Binary file not shown.
Binary file added assets/go_router/push_regular_route.mp4
Binary file not shown.
Binary file added assets/go_router/push_same_shell.mp4
Binary file not shown.
36 changes: 7 additions & 29 deletions packages/diagram_capture/lib/diagram_capture.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,47 +75,30 @@ class _Diagram extends StatelessWidget {
}
}

const double _kDefaultDiagramViewportWidth = 1280;
const double _kDefaultDiagramViewportHeight = 1024;
const Size _kDefaultDiagramViewportSize = Size(
_kDefaultDiagramViewportWidth,
_kDefaultDiagramViewportHeight,
);
const ui.ViewConstraints _kDefaultDiagramViewportConstraints =
ui.ViewConstraints(
minWidth: _kDefaultDiagramViewportWidth,
minHeight: _kDefaultDiagramViewportHeight,
maxWidth: _kDefaultDiagramViewportWidth,
maxHeight: _kDefaultDiagramViewportHeight,
);
const Size _kDefaultDiagramViewportSize = Size(1280.0, 1024.0);

// View configuration that allows diagrams to not match the physical dimensions
// of the device. This will change the view used to display the flutter surface
// so that the diagram surface fits on the device, but it doesn't affect the
// captured image pixels.
class _DiagramViewConfiguration extends ViewConfiguration {
_DiagramViewConfiguration({
super.constraints = _kDefaultDiagramViewportConstraints,
super.size = _kDefaultDiagramViewportSize,
}) : _paintMatrix = _getMatrix(
constraints,
size,
ui.PlatformDispatcher.instance.implicitView?.devicePixelRatio ??
1.0);

static Matrix4 _getMatrix(
ui.ViewConstraints constraints, double devicePixelRatio) {
static Matrix4 _getMatrix(Size size, double devicePixelRatio) {
final double baseRatio =
ui.PlatformDispatcher.instance.implicitView?.devicePixelRatio ?? 1.0;
final double inverseRatio = devicePixelRatio / baseRatio;
final Size implicitSize =
ui.PlatformDispatcher.instance.implicitView?.physicalSize ?? Size.zero;
final double actualWidth = implicitSize.width * inverseRatio;
final double actualHeight = implicitSize.height * inverseRatio;
final double desiredWidth = constraints.maxWidth == double.infinity
? constraints.minWidth
: constraints.maxWidth;
final double desiredHeight = constraints.maxHeight == double.infinity
? constraints.minHeight
: constraints.maxHeight;
final double desiredWidth = size.width;
final double desiredHeight = size.height;
double scale, shiftX, shiftY;
if ((actualWidth / actualHeight) > (desiredWidth / desiredHeight)) {
scale = actualHeight / desiredHeight;
Expand Down Expand Up @@ -292,12 +275,7 @@ class DiagramFlutterBinding extends BindingBase
@override
ViewConfiguration createViewConfigurationFor(RenderView renderView) {
return _DiagramViewConfiguration(
constraints: ui.ViewConstraints(
minWidth: screenDimensions.width,
minHeight: screenDimensions.height,
maxWidth: screenDimensions.width,
maxHeight: screenDimensions.height,
),
size: screenDimensions,
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/diagrams/lib/diagrams.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export 'src/floating_action_button_location.dart';
export 'src/font_feature.dart';
export 'src/form.dart';
export 'src/gesture_detector.dart';
export 'src/go_router_push_animation.dart';
export 'src/grid_view.dart';
export 'src/heroes.dart';
export 'src/icon.dart';
Expand Down
1 change: 0 additions & 1 deletion packages/diagrams/lib/src/font_feature.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:ui' show FontFeature;

import 'package:flutter/material.dart';

Expand Down
287 changes: 287 additions & 0 deletions packages/diagrams/lib/src/go_router_push_animation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:diagram_capture/diagram_capture.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'diagram_step.dart';
import 'utils.dart';

const Duration _pauseDuration = Duration(seconds: 1);
const Duration _openDuration = Duration(milliseconds: 300);
const Duration _closeDuration = Duration(milliseconds: 300);
final Duration _totalDuration = _pauseDuration +
_pauseDuration +
_openDuration +
_pauseDuration +
_closeDuration +
_pauseDuration;

final GlobalKey _pushShell1 = GlobalKey();
final GlobalKey _pushShell2 = GlobalKey();
final GlobalKey _pushRegularRoute = GlobalKey();

final GlobalKey<NavigatorState> _innerNavigator = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> _outerNavigator = GlobalKey<NavigatorState>();

class PushRegularRouteDiagram extends StatefulWidget with DiagramMetadata {
const PushRegularRouteDiagram({super.key});

@override
String get name => 'push_regular_route';

@override
State<PushRegularRouteDiagram> createState() =>
_PushRegularRouteDiagramState();

@override
Duration? get duration => _totalDuration;
}

class _PushRegularRouteDiagramState extends State<PushRegularRouteDiagram>
with TickerProviderStateMixin, LockstepStateMixin {
Future<void> _tap(GlobalKey key) async {
final RenderBox target =
key.currentContext!.findRenderObject()! as RenderBox;
final Offset targetOffset =
target.localToGlobal(target.size.center(Offset.zero));
final WidgetController controller = DiagramWidgetController.of(context);
final TestGesture gesture = await controller.startGesture(targetOffset);
await waitLockstep(_pauseDuration);
await gesture.up();
await waitLockstep(_openDuration);
}

Future<void> _pause() async {
await waitLockstep(_pauseDuration);
}

Future<void> startAnimation() async {
await _pause();
await _tap(_pushRegularRoute);
await _pause();
_outerNavigator.currentState!.pop();
await waitLockstep(_closeDuration);
await _pause();
}

@override
void initState() {
super.initState();
startAnimation();
}

@override
Widget build(BuildContext context) {
return _MainApp();
}
}

class PushSameShellDiagram extends StatefulWidget with DiagramMetadata {
const PushSameShellDiagram({super.key});

@override
String get name => 'push_same_shell';

@override
State<PushSameShellDiagram> createState() => _PushSameShellDiagramState();

@override
Duration? get duration => _totalDuration;
}

class _PushSameShellDiagramState extends State<PushSameShellDiagram>
with TickerProviderStateMixin, LockstepStateMixin {
Future<void> _tap(GlobalKey key) async {
final RenderBox target =
key.currentContext!.findRenderObject()! as RenderBox;
final Offset targetOffset =
target.localToGlobal(target.size.center(Offset.zero));
final WidgetController controller = DiagramWidgetController.of(context);
final TestGesture gesture = await controller.startGesture(targetOffset);
await waitLockstep(_pauseDuration);
await gesture.up();
await waitLockstep(_openDuration);
}

Future<void> _pause() async {
await waitLockstep(_pauseDuration);
}

Future<void> startAnimation() async {
await _pause();
await _tap(_pushShell1);
await _pause();
_innerNavigator.currentState!.pop();
await waitLockstep(_closeDuration);
await _pause();
}

@override
void initState() {
super.initState();
startAnimation();
}

@override
Widget build(BuildContext context) {
return _MainApp();
}
}

class PushDifferentShellDiagram extends StatefulWidget with DiagramMetadata {
const PushDifferentShellDiagram({super.key});

@override
String get name => 'push_different_shell';

@override
State<PushDifferentShellDiagram> createState() =>
_PushDifferentShellDiagramState();

@override
Duration? get duration => _totalDuration;
}

class _PushDifferentShellDiagramState extends State<PushDifferentShellDiagram>
with TickerProviderStateMixin, LockstepStateMixin {
Future<void> _tap(GlobalKey key) async {
final RenderBox target =
key.currentContext!.findRenderObject()! as RenderBox;
final Offset targetOffset =
target.localToGlobal(target.size.center(Offset.zero));
final WidgetController controller = DiagramWidgetController.of(context);
final TestGesture gesture = await controller.startGesture(targetOffset);
await waitLockstep(_pauseDuration);
await gesture.up();
await waitLockstep(_openDuration);
}

Future<void> _pause() async {
await waitLockstep(_pauseDuration);
}

Future<void> startAnimation() async {
await _pause();
await _tap(_pushShell2);
await _pause();
_outerNavigator.currentState!.pop();
await waitLockstep(_closeDuration);
await _pause();
}

@override
void initState() {
super.initState();
startAnimation();
}

@override
Widget build(BuildContext context) {
return _MainApp();
}
}

class _MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
key: UniqueKey(),
constraints: BoxConstraints.tight(const Size(350, 622)),
child: Navigator(
key: _outerNavigator,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<void>(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return Scaffold(
appBar: AppBar(
title: const Text('Shell1'),
),
body: _Shell1(),
);
},
);
},
),
);
}
}

class _Shell1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Navigator(
key: _innerNavigator,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<void>(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextButton(
key: _pushShell1,
onPressed: () {
_innerNavigator.currentState!
.push(MaterialPageRoute<void>(builder: (_) {
return const Center(child: Text('shell1 body'));
}));
},
child: const Text('push the same shell route /shell1'),
),
TextButton(
key: _pushShell2,
onPressed: () {
_outerNavigator.currentState!
.push(MaterialPageRoute<void>(builder: (_) {
return Scaffold(
appBar: AppBar(
title: const Text('shell2'),
),
body: const Center(child: Text('shell2 body')));
}));
},
child: const Text('push the different shell route /shell2'),
),
TextButton(
key: _pushRegularRoute,
onPressed: () {
_outerNavigator.currentState!
.push(MaterialPageRoute<void>(builder: (_) {
return const Scaffold(
body: Center(child: Text('Regular Route')));
}));
},
child: const Text('push the regular route /regular-route'),
),
],
);
},
);
},
);
}
}

class GoRouterDiagramStep extends DiagramStep {
@override
final String category = 'go_router';

@override
Future<List<DiagramMetadata>> get diagrams async => <DiagramMetadata>[
const PushRegularRouteDiagram(),
const PushSameShellDiagram(),
const PushDifferentShellDiagram(),
];
}
2 changes: 1 addition & 1 deletion packages/diagrams/lib/src/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class DiagramImage extends ImageProvider<DiagramImage>
}

@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
Expand Down
1 change: 1 addition & 0 deletions packages/diagrams/lib/src/steps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final List<DiagramStep> allDiagramSteps = <DiagramStep>[
FormDiagramStep(),
FontFeatureDiagramStep(),
GestureDetectorDiagramStep(),
GoRouterDiagramStep(),
GridViewDiagramStep(),
HeroesDiagramStep(),
IconButtonDiagramStep(),
Expand Down

0 comments on commit 008a2dd

Please sign in to comment.