diff --git a/lib/src/widgets/sidekick.dart b/lib/src/widgets/sidekick.dart index bd5479d..0aab452 100644 --- a/lib/src/widgets/sidekick.dart +++ b/lib/src/widgets/sidekick.dart @@ -84,6 +84,7 @@ class Sidekick extends StatefulWidget { this.flightShuttleBuilder, this.placeholderBuilder, SidekickAnimationBuilder animationBuilder, + this.keepShowingWidget, @required this.child, }) : assert(tag != null), assert(child != null), @@ -137,6 +138,9 @@ class Sidekick extends StatefulWidget { /// Optional override to specified the animation used while flying. final SidekickAnimationBuilder animationBuilder; + /// Keep showing the source "from" widget after it has flown + final bool keepShowingWidget; + // Returns a map of all of the sidekicks in context, indexed by sidekick tag. static Map _allSidekicksFor(BuildContext context) { assert(context != null); @@ -221,16 +225,17 @@ class _SidekickState extends State with TickerProviderStateMixin { /// Everything known about a sidekick flight that's to be started or diverted. class _SidekickFlightManifest { - _SidekickFlightManifest({ - @required this.type, - @required this.overlay, - @required this.rect, - @required this.fromSidekick, - @required this.toSidekick, - @required this.createRectTween, - @required this.shuttleBuilder, - @required this.animationController, - }) : assert((type == SidekickFlightDirection.toTarget && + _SidekickFlightManifest( + {@required this.type, + @required this.overlay, + @required this.rect, + @required this.fromSidekick, + @required this.toSidekick, + @required this.createRectTween, + @required this.shuttleBuilder, + @required this.animationController, + @required this.keepShowingFromWidget}) + : assert((type == SidekickFlightDirection.toTarget && fromSidekick.widget.targetTag == toSidekick.widget.tag) || (type == SidekickFlightDirection.toSource && toSidekick.widget.targetTag == fromSidekick.widget.tag)); @@ -243,6 +248,7 @@ class _SidekickFlightManifest { final CreateRectTween createRectTween; final SidekickFlightShuttleBuilder shuttleBuilder; final Animation animationController; + final bool keepShowingFromWidget; Object get tag => fromSidekick.widget.tag; @@ -357,6 +363,7 @@ class _SidekickFlight { overlayEntry.remove(); overlayEntry = null; + manifest.keepShowingFromWidget ? manifest.fromSidekick.endFlight() : null; manifest.toSidekick.endFlight(); onFlightEnded(this); } @@ -612,19 +619,20 @@ class SidekickController extends Animation { fromSidekick.flightShuttleBuilder; final SidekickFlightShuttleBuilder toShuttleBuilder = toSidekick.flightShuttleBuilder; + final bool keepShowingFromWidget = fromSidekick?.keepShowingWidget; final _SidekickFlightManifest manifest = _SidekickFlightManifest( - type: flightType, - overlay: Overlay.of(context), - rect: rect, - fromSidekick: sidekicks[tag], - toSidekick: sidekicks[targetTag], - createRectTween: createRectTween, - shuttleBuilder: toShuttleBuilder ?? - fromShuttleBuilder ?? - _defaultSidekickFlightShuttleBuilder, - animationController: _controller.view, - ); + type: flightType, + overlay: Overlay.of(context), + rect: rect, + fromSidekick: sidekicks[tag], + toSidekick: sidekicks[targetTag], + createRectTween: createRectTween, + shuttleBuilder: toShuttleBuilder ?? + fromShuttleBuilder ?? + _defaultSidekickFlightShuttleBuilder, + animationController: _controller.view, + keepShowingFromWidget: keepShowingFromWidget ?? false); if (_flights[tag] != null) { _flights[tag].divert(manifest); diff --git a/test/flutter_sidekick_test.dart b/test/flutter_sidekick_test.dart index 39e3f46..83777c6 100644 --- a/test/flutter_sidekick_test.dart +++ b/test/flutter_sidekick_test.dart @@ -10,9 +10,11 @@ class SimpleExample extends StatefulWidget { SimpleExample([ this.sourceTag = 'source', this.targetTag = 'target', + this.keepShowingSource = false, ]); final String sourceTag; final String targetTag; + final bool keepShowingSource; @override _SimpleExampleState createState() => _SimpleExampleState(); @@ -51,6 +53,7 @@ class _SimpleExampleState extends State child: Sidekick( tag: widget.sourceTag, targetTag: widget.targetTag, + keepShowingWidget: widget.keepShowingSource, child: Container( key: simpleSource, color: Colors.blue, @@ -249,6 +252,39 @@ void main() { expect(find.byKey(simpleTarget), isInCard); }); + testWidgets('Animate to target with keepShowing source', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp(home: SimpleExample('source', 'target', true))); + + // the initial setup. + expect(find.byKey(simpleSource), isInCard); + expect(find.byKey(simpleTarget), isInCard); + + await tester.tap(find.byKey(simpleSource)); + await tester.pump(); // the animation will start at the next frame. + await tester.pump(frameDuration); + + // at this stage, the sidekick just gone on its journey, we are + // seeing them at t=16ms. + + expect(find.byKey(simpleSource), findsNothing); + expect(find.byKey(simpleTarget), isNotInCard); + + await tester.pump(frameDuration); + + // t=32ms for the journey. Surely they are still at it. + expect(find.byKey(simpleSource), findsNothing); + expect(find.byKey(simpleTarget), isNotInCard); + + await tester.pump(const Duration(seconds: 1)); + + // t=1.033s for the journey. The journey has ended (it ends this frame, in + // fact). The sidekicks should be back now. + expect(find.byKey(simpleSource), isInCard); + expect(find.byKey(simpleTarget), isInCard); + }); + testWidgets('Animate to source', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(home: SimpleExample()));