From a725424cd0d75802ba5d5aab8de62c85604122b6 Mon Sep 17 00:00:00 2001 From: Ali Yigit Bireroglu Date: Sat, 7 Sep 2019 12:15:46 +0100 Subject: [PATCH] Cool Changes --- README.md | 21 +- peek_and_pop/CHANGELOG.md | 7 + peek_and_pop/README.md | 21 +- peek_and_pop/example/CHANGELOG.md | 7 + peek_and_pop/example/README.md | 21 +- peek_and_pop/example/lib/main.dart | 114 +++--- peek_and_pop/example/pubspec.lock | 6 +- peek_and_pop/example/pubspec.yaml | 2 +- peek_and_pop/lib/animated_cross_fade.dart | 365 ++++++++++++++++++ peek_and_pop/lib/misc.dart | 13 - peek_and_pop/lib/peek_and_pop_child.dart | 314 +++++++++++---- peek_and_pop/lib/peek_and_pop_controller.dart | 79 +++- peek_and_pop/pretty_example/CHANGELOG.md | 7 + peek_and_pop/pretty_example/README.md | 21 +- peek_and_pop/pretty_example/lib/main.dart | 52 +-- peek_and_pop/pretty_example/pubspec.lock | 6 +- peek_and_pop/pretty_example/pubspec.yaml | 2 +- peek_and_pop/pubspec.lock | 4 +- peek_and_pop/pubspec.yaml | 4 +- 19 files changed, 822 insertions(+), 244 deletions(-) create mode 100644 peek_and_pop/lib/animated_cross_fade.dart diff --git a/README.md b/README.md index 437c0c1..69f1de6 100644 --- a/README.md +++ b/README.md @@ -201,9 +201,11 @@ Then, create a PeekAndPopController such as: PeekAndPopController( uiChild(), //Widget uiChild false, //bool uiChildUseCache - peekAndPopBuilder, //PeekAndPopBuilder peekAndPopBuilder - false, //bool peekAndPopBuilderUseCache {Key key, + peekAndPopBuilder, + peekAndPopBuilderUseCache, + peekAndPopBuilderAtPeek : peekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop : peekAndPopBuilderAtPop, quickActionsBuilder : quickActionsBuilder, sigma : 10, backdropColor : Colors.black, @@ -234,15 +236,17 @@ PeekAndPopController( peakPressure : 1.0, peekScale : 0.5, peekCoefficient : 0.05, - popTransition}) + popTransition, + useHaptics : true}) Widget uiChild() {} -Widget peekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPeek(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPop(BuildContext context, PeekAndPopControllerState _peekAndPopController); QuickActionsData quickActionsBuilder(PeekAndPopControllerState _peekAndPopController); -Widget overlayBuiler(); +WidgetBuilder overlayBuiler(BuildContext context); bool _willPeekAndPopComplete(PeekAndPopControllerState _peekAndPopController); bool _willPushPeekAndPop(PeekAndPopControllerState _peekAndPopController); @@ -270,6 +274,9 @@ void _onPressEnd(dynamic dragDetails); * Set [uiChildUseCache] to true if your [uiChild] doesn't change during the Peek & Pop process. * Set [peekAndPopBuilderUseCache] to true if your [peekAndPopBuilder] doesn't change during the Peek & Pop process. +* If [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] are set, [peekAndPopBuilder] and [peekAndPopBuilderUseCache] are ignored. +* If one of [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] is set, the other one must be set too. +* If [quickActionsBuilder] is set, it is recommended that [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPop] are set too. * [overlayBuilder] is an optional second view to be displayed during the Peek & Pop process. This entire widget is built after everything else. * For all [PeekAndPopProcessNotifier] callbacks such as [willPeekAndPopComplete], you can return false to prevent the default action. * All [PeekAndPopProcessNotifier] and [PeekAndPopProcessCallback] callbacks will return a reference to the created [PeekAndPopController] state. @@ -279,10 +286,6 @@ You can save this instance for further actions. directly. * Use [PeekAndPopControllerState]'s [stage] variable to get enumeration for the stage of the Peek & Pop process. If you want to only know when the Peek & Pop process will be or is completed, you can also use [willBeDone] or [isDone] variables. -* I realised that when an [AppBar] or a [CupertinoNavigationBar] is built with full transparency, their height is not included in the layout of a -[Scaffold] or a [CupertinoPageScaffold]. Therefore, moving from a Peek stage with a transparent header to a Pop stage with a non-transparent header -causes visual conflicts. Use this [PeekAndPopChildState]'s [Size get headerSize] and [double getHeaderOffset(HeaderOffset headerOffset)] methods to -overcome this problem. [comment]: <> (Notes) diff --git a/peek_and_pop/CHANGELOG.md b/peek_and_pop/CHANGELOG.md index 105ae61..8be0814 100644 --- a/peek_and_pop/CHANGELOG.md +++ b/peek_and_pop/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.0.2] - 07.09.2019 + +* Two new PeekAndPopBuilders are added to [PeekAndPopController]. Use [PeekAndPopController.peekAndPopBuilderAtPeek] and +[PeekAndPopController.peekAndPopBuilderAtPop] for both convenience and improved performance. + +* Improved performance. + ## [1.0.1] - 03.09.2019 * Minor changes. diff --git a/peek_and_pop/README.md b/peek_and_pop/README.md index 437c0c1..69f1de6 100644 --- a/peek_and_pop/README.md +++ b/peek_and_pop/README.md @@ -201,9 +201,11 @@ Then, create a PeekAndPopController such as: PeekAndPopController( uiChild(), //Widget uiChild false, //bool uiChildUseCache - peekAndPopBuilder, //PeekAndPopBuilder peekAndPopBuilder - false, //bool peekAndPopBuilderUseCache {Key key, + peekAndPopBuilder, + peekAndPopBuilderUseCache, + peekAndPopBuilderAtPeek : peekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop : peekAndPopBuilderAtPop, quickActionsBuilder : quickActionsBuilder, sigma : 10, backdropColor : Colors.black, @@ -234,15 +236,17 @@ PeekAndPopController( peakPressure : 1.0, peekScale : 0.5, peekCoefficient : 0.05, - popTransition}) + popTransition, + useHaptics : true}) Widget uiChild() {} -Widget peekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPeek(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPop(BuildContext context, PeekAndPopControllerState _peekAndPopController); QuickActionsData quickActionsBuilder(PeekAndPopControllerState _peekAndPopController); -Widget overlayBuiler(); +WidgetBuilder overlayBuiler(BuildContext context); bool _willPeekAndPopComplete(PeekAndPopControllerState _peekAndPopController); bool _willPushPeekAndPop(PeekAndPopControllerState _peekAndPopController); @@ -270,6 +274,9 @@ void _onPressEnd(dynamic dragDetails); * Set [uiChildUseCache] to true if your [uiChild] doesn't change during the Peek & Pop process. * Set [peekAndPopBuilderUseCache] to true if your [peekAndPopBuilder] doesn't change during the Peek & Pop process. +* If [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] are set, [peekAndPopBuilder] and [peekAndPopBuilderUseCache] are ignored. +* If one of [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] is set, the other one must be set too. +* If [quickActionsBuilder] is set, it is recommended that [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPop] are set too. * [overlayBuilder] is an optional second view to be displayed during the Peek & Pop process. This entire widget is built after everything else. * For all [PeekAndPopProcessNotifier] callbacks such as [willPeekAndPopComplete], you can return false to prevent the default action. * All [PeekAndPopProcessNotifier] and [PeekAndPopProcessCallback] callbacks will return a reference to the created [PeekAndPopController] state. @@ -279,10 +286,6 @@ You can save this instance for further actions. directly. * Use [PeekAndPopControllerState]'s [stage] variable to get enumeration for the stage of the Peek & Pop process. If you want to only know when the Peek & Pop process will be or is completed, you can also use [willBeDone] or [isDone] variables. -* I realised that when an [AppBar] or a [CupertinoNavigationBar] is built with full transparency, their height is not included in the layout of a -[Scaffold] or a [CupertinoPageScaffold]. Therefore, moving from a Peek stage with a transparent header to a Pop stage with a non-transparent header -causes visual conflicts. Use this [PeekAndPopChildState]'s [Size get headerSize] and [double getHeaderOffset(HeaderOffset headerOffset)] methods to -overcome this problem. [comment]: <> (Notes) diff --git a/peek_and_pop/example/CHANGELOG.md b/peek_and_pop/example/CHANGELOG.md index 105ae61..8be0814 100644 --- a/peek_and_pop/example/CHANGELOG.md +++ b/peek_and_pop/example/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.0.2] - 07.09.2019 + +* Two new PeekAndPopBuilders are added to [PeekAndPopController]. Use [PeekAndPopController.peekAndPopBuilderAtPeek] and +[PeekAndPopController.peekAndPopBuilderAtPop] for both convenience and improved performance. + +* Improved performance. + ## [1.0.1] - 03.09.2019 * Minor changes. diff --git a/peek_and_pop/example/README.md b/peek_and_pop/example/README.md index 8bf3829..28a4108 100644 --- a/peek_and_pop/example/README.md +++ b/peek_and_pop/example/README.md @@ -209,9 +209,11 @@ Then, create a PeekAndPopController such as: PeekAndPopController( uiChild(), //Widget uiChild false, //bool uiChildUseCache - peekAndPopBuilder, //PeekAndPopBuilder peekAndPopBuilder - false, //bool peekAndPopBuilderUseCache {Key key, + peekAndPopBuilder, + peekAndPopBuilderUseCache, + peekAndPopBuilderAtPeek : peekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop : peekAndPopBuilderAtPop, quickActionsBuilder : quickActionsBuilder, sigma : 10, backdropColor : Colors.black, @@ -242,15 +244,17 @@ PeekAndPopController( peakPressure : 1.0, peekScale : 0.5, peekCoefficient : 0.05, - popTransition}) + popTransition, + useHaptics : true}) Widget uiChild() {} -Widget peekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPeek(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPop(BuildContext context, PeekAndPopControllerState _peekAndPopController); QuickActionsData quickActionsBuilder(PeekAndPopControllerState _peekAndPopController); -Widget overlayBuiler(); +WidgetBuilder overlayBuiler(BuildContext context); bool _willPeekAndPopComplete(PeekAndPopControllerState _peekAndPopController); bool _willPushPeekAndPop(PeekAndPopControllerState _peekAndPopController); @@ -278,6 +282,9 @@ void _onPressEnd(dynamic dragDetails); * Set [uiChildUseCache] to true if your [uiChild] doesn't change during the Peek & Pop process. * Set [peekAndPopBuilderUseCache] to true if your [peekAndPopBuilder] doesn't change during the Peek & Pop process. +* If [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] are set, [peekAndPopBuilder] and [peekAndPopBuilderUseCache] are ignored. +* If one of [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] is set, the other one must be set too. +* If [quickActionsBuilder] is set, it is recommended that [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPop] are set too. * [overlayBuilder] is an optional second view to be displayed during the Peek & Pop process. This entire widget is built after everything else. * For all [PeekAndPopProcessNotifier] callbacks such as [willPeekAndPopComplete], you can return false to prevent the default action. * All [PeekAndPopProcessNotifier] and [PeekAndPopProcessCallback] callbacks will return a reference to the created [PeekAndPopController] state. @@ -287,10 +294,6 @@ You can save this instance for further actions. directly. * Use [PeekAndPopControllerState]'s [stage] variable to get enumeration for the stage of the Peek & Pop process. If you want to only know when the Peek & Pop process will be or is completed, you can also use [willBeDone] or [isDone] variables. -* I realised that when an [AppBar] or a [CupertinoNavigationBar] is built with full transparency, their height is not included in the layout of a -[Scaffold] or a [CupertinoPageScaffold]. Therefore, moving from a Peek stage with a transparent header to a Pop stage with a non-transparent header -causes visual conflicts. Use this [PeekAndPopChildState]'s [Size get headerSize] and [double getHeaderOffset(HeaderOffset headerOffset)] methods to -overcome this problem. [comment]: <> (Notes) diff --git a/peek_and_pop/example/lib/main.dart b/peek_and_pop/example/lib/main.dart index 3207f36..1ee8d07 100644 --- a/peek_and_pop/example/lib/main.dart +++ b/peek_and_pop/example/lib/main.dart @@ -24,6 +24,7 @@ import 'package:peek_and_pop/misc.dart' as PeekAndPopMisc; PeekAndPopControllerState peekAndPopController; final GlobalKey scaffold = GlobalKey(); +final GlobalKey header = GlobalKey(); void main() => runApp(MyApp()); @@ -70,7 +71,7 @@ class _MyHomePageState extends State { } Widget atPeekWrapper(Widget child, PeekAndPopControllerState _peekAndPopController) { - return Container( + return Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(const Radius.circular(10.0)), boxShadow: [ @@ -128,6 +129,7 @@ class _MyHomePageState extends State { color: const Color(0xffFF9500), ), ), + transitionBetweenRoutes: false, ); } @@ -183,28 +185,29 @@ class _MyHomePageState extends State { ); } - Widget normalPeekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController) { - if (_peekAndPopController.willBeDone || _peekAndPopController.isDone) - return atPopWrapper( - Transform.translate( - offset: Offset(0, _peekAndPopController.peekAndPopChild.getHeaderOffset(PeekAndPopMisc.HeaderOffset.NegativeHalf)), - child: Image.asset( - "assets/Scenery.jpeg", - fit: BoxFit.contain, - key: Key("Image"), - ), - ), - _peekAndPopController, - ); - else - return atPeekWrapper( - Image.asset( + Widget normalPeekAndPopBuilderAtPeek(BuildContext context, PeekAndPopControllerState _peekAndPopController) { + return atPeekWrapper( + Image.asset( + "assets/Scenery.jpeg", + fit: BoxFit.contain, + key: Key("Image"), + ), + _peekAndPopController, + ); + } + + Widget normalPeekAndPopBuilderAtPop(BuildContext context, PeekAndPopControllerState _peekAndPopController) { + return atPopWrapper( + Transform.translate( + offset: Offset(0, -50), + child: Image.asset( "assets/Scenery.jpeg", fit: BoxFit.contain, key: Key("Image"), ), - _peekAndPopController, - ); + ), + _peekAndPopController, + ); } Widget platformViewPeekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController) { @@ -229,7 +232,8 @@ class _MyHomePageState extends State { key: scaffold, appBar: (_peekAndPopController.willBeDone || _peekAndPopController.isDone) ? appBar(_peekAndPopController) : null, body: SizedBox.expand( - child: peekAndPopController.stage == Stage.IsPeeking || _peekAndPopController.willBeDone || peekAndPopController.isDone + child: (peekAndPopController.stage == Stage.IsPeeking || _peekAndPopController.willBeDone || peekAndPopController.isDone) && + DateTime.now().difference(_peekAndPopController.pushTime).inSeconds > 1 ? InAppBrowser("https://flutter.dev") : peekAndPopController.stage == Stage.WillCancel || peekAndPopController.stage == Stage.IsCancelled ? Container() @@ -244,7 +248,7 @@ class _MyHomePageState extends State { if (_peekAndPopController.willBeDone || _peekAndPopController.isDone) return atPopWrapper( Transform.translate( - offset: Offset(0, _peekAndPopController.peekAndPopChild.getHeaderOffset(PeekAndPopMisc.HeaderOffset.NegativeHalf)), + offset: Offset(0, -50), child: Hero( tag: "Hero", child: Image.asset( @@ -264,32 +268,34 @@ class _MyHomePageState extends State { ); } - Widget gridPeekAndPopBuilder(int index, BuildContext context, PeekAndPopControllerState _peekAndPopController) { - if (_peekAndPopController.willBeDone || _peekAndPopController.isDone) - return atPopWrapper( - Transform.translate( - offset: Offset(0, _peekAndPopController.peekAndPopChild.getHeaderOffset(PeekAndPopMisc.HeaderOffset.NegativeHalf)), - child: Image.asset( - "assets/" + index.toString() + ".jpeg", - fit: BoxFit.contain, - key: Key("Image"), - ), - ), - _peekAndPopController, - ); - else - return atPeekWrapper( - Image.asset( + Widget gridPeekAndPopBuilderAtPeek(int index, BuildContext context, PeekAndPopControllerState _peekAndPopController) { + return atPeekWrapper( + Image.asset( + "assets/" + index.toString() + ".jpeg", + fit: BoxFit.contain, + key: Key("Image"), + ), + _peekAndPopController, + ); + } + + Widget gridPeekAndPopBuilderAtPop(int index, BuildContext context, PeekAndPopControllerState _peekAndPopController) { + return atPopWrapper( + Transform.translate( + offset: Offset(0, -50), + child: Image.asset( "assets/" + index.toString() + ".jpeg", fit: BoxFit.contain, key: Key("Image"), ), - _peekAndPopController, - ); + ), + _peekAndPopController, + ); } QuickActionsData moveableQuickActionsBuilder(PeekAndPopControllerState _peekAndPopController) { return QuickActionsData( + const EdgeInsets.only(left: 12.5, top: 25, right: 12.5, bottom: 25), const BorderRadius.all(const Radius.circular(10.0)), [ QuickAction( @@ -383,6 +389,7 @@ class _MyHomePageState extends State { QuickActionsData gridQuickActionsBuilder(PeekAndPopControllerState _peekAndPopController) { return QuickActionsData( + const EdgeInsets.only(left: 12.5, top: 25, right: 12.5, bottom: 25), const BorderRadius.all(const Radius.circular(10.0)), [ QuickAction( @@ -517,8 +524,8 @@ class _MyHomePageState extends State { Colors.redAccent, ), true, - normalPeekAndPopBuilder, - false, + peekAndPopBuilderAtPeek: normalPeekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop: normalPeekAndPopBuilderAtPop, sigma: 10, backdropColor: Colors.white, useOverlap: true, @@ -538,8 +545,8 @@ class _MyHomePageState extends State { Colors.deepPurpleAccent, ), true, - normalPeekAndPopBuilder, - false, + peekAndPopBuilderAtPeek: normalPeekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop: normalPeekAndPopBuilderAtPop, quickActionsBuilder: moveableQuickActionsBuilder, sigma: 10, backdropColor: Colors.white, @@ -560,15 +567,15 @@ class _MyHomePageState extends State { Colors.cyan, ), true, - platformViewPeekAndPopBuilder, - false, + peekAndPopBuilder: platformViewPeekAndPopBuilder, + peekAndPopBuilderUseCache: false, sigma: 10, backdropColor: Colors.white, useOverlap: true, customOverlapRect: Rect.fromLTRB( - MediaQuery.of(context).size.width * 0.5, - MediaQuery.of(context).size.height * 0.75, - MediaQuery.of(context).size.width * 0.5, + MediaQuery.of(context).size.width * 0.25, + MediaQuery.of(context).size.height * 0.25, + MediaQuery.of(context).size.width * 0.25, MediaQuery.of(context).size.height * 0.25, ), useAlignment: true, @@ -587,8 +594,8 @@ class _MyHomePageState extends State { Colors.greenAccent, ), true, - specialPeekAndPopBuilder, - false, + peekAndPopBuilder: specialPeekAndPopBuilder, + peekAndPopBuilderUseCache: false, sigma: 10, backdropColor: Colors.white, useOverlap: true, @@ -621,9 +628,10 @@ class _MyHomePageState extends State { ), ), true, - (BuildContext context, PeekAndPopControllerState _peekAndPopController) => - gridPeekAndPopBuilder(index, context, _peekAndPopController), - false, + peekAndPopBuilderAtPeek: (BuildContext context, PeekAndPopControllerState _peekAndPopController) => + gridPeekAndPopBuilderAtPeek(index, context, _peekAndPopController), + peekAndPopBuilderAtPop: (BuildContext context, PeekAndPopControllerState _peekAndPopController) => + gridPeekAndPopBuilderAtPop(index, context, _peekAndPopController), quickActionsBuilder: gridQuickActionsBuilder, sigma: 10, backdropColor: Colors.white, diff --git a/peek_and_pop/example/pubspec.lock b/peek_and_pop/example/pubspec.lock index 71e2c8e..bcf4f76 100644 --- a/peek_and_pop/example/pubspec.lock +++ b/peek_and_pop/example/pubspec.lock @@ -77,7 +77,7 @@ packages: name: flick url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" flutter: dependency: "direct main" description: flutter @@ -129,7 +129,7 @@ packages: path: ".." relative: true source: path - version: "1.0.1" + version: "1.0.2" petitparser: dependency: transitive description: @@ -162,7 +162,7 @@ packages: name: snap url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" source_span: dependency: transitive description: diff --git a/peek_and_pop/example/pubspec.yaml b/peek_and_pop/example/pubspec.yaml index b014c7e..662eeba 100644 --- a/peek_and_pop/example/pubspec.yaml +++ b/peek_and_pop/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: Example Project for peek_and_pop. -version: 1.0.1 +version: 1.0.2 author: Ali Yigit Bireroglu repository: https://github.com/aliyigitbireroglu/flutter-peek-and-pop homepage: https://www.cosmossoftware.coffee diff --git a/peek_and_pop/lib/animated_cross_fade.dart b/peek_and_pop/lib/animated_cross_fade.dart new file mode 100644 index 0000000..c3a98c6 --- /dev/null +++ b/peek_and_pop/lib/animated_cross_fade.dart @@ -0,0 +1,365 @@ +// Copyright 2016 The Chromium 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:flutter/rendering.dart'; + +import 'package:flutter/widgets.dart'; + +// Examples can assume: +// bool _first; + +/// Specifies which of two children to show. See [AnimatedCrossFade]. +/// +/// The child that is shown will fade in, while the other will fade out. +enum CrossFadeState { + /// Show the first child ([AnimatedCrossFade.firstChild]) and hide the second + /// ([AnimatedCrossFade.secondChild]]). + showFirst, + + /// Show the second child ([AnimatedCrossFade.secondChild]) and hide the first + /// ([AnimatedCrossFade.firstChild]). + showSecond, +} + +/// Signature for the [AnimatedCrossFade.layoutBuilder] callback. +/// +/// The `topChild` is the child fading in, which is normally drawn on top. The +/// `bottomChild` is the child fading out, normally drawn on the bottom. +/// +/// For good performance, the returned widget tree should contain both the +/// `topChild` and the `bottomChild`; the depth of the tree, and the types of +/// the widgets in the tree, from the returned widget to each of the children +/// should be the same; and where there is a widget with multiple children, the +/// top child and the bottom child should be keyed using the provided +/// `topChildKey` and `bottomChildKey` keys respectively. +/// +/// {@tool sample} +/// +/// ```dart +/// Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) { +/// return Stack( +/// fit: StackFit.loose, +/// children: [ +/// Positioned( +/// key: bottomChildKey, +/// left: 0.0, +/// top: 0.0, +/// right: 0.0, +/// child: bottomChild, +/// ), +/// Positioned( +/// key: topChildKey, +/// child: topChild, +/// ) +/// ], +/// ); +/// } +/// ``` +/// {@end-tool} +typedef AnimatedCrossFadeBuilder = Widget Function(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey); + +/// A widget that cross-fades between two given children and animates itself +/// between their sizes. +/// +/// The animation is controlled through the [crossFadeState] parameter. +/// [firstCurve] and [secondCurve] represent the opacity curves of the two +/// children. The [firstCurve] is inverted, i.e. it fades out when providing a +/// growing curve like [Curves.linear]. The [sizeCurve] is the curve used to +/// animate between the size of the fading-out child and the size of the +/// fading-in child. +/// +/// This widget is intended to be used to fade a pair of widgets with the same +/// width. In the case where the two children have different heights, the +/// animation crops overflowing children during the animation by aligning their +/// top edge, which means that the bottom will be clipped. +/// +/// The animation is automatically triggered when an existing +/// [AnimatedCrossFade] is rebuilt with a different value for the +/// [crossFadeState] property. +/// +/// {@tool sample} +/// +/// This code fades between two representations of the Flutter logo. It depends +/// on a boolean field `_first`; when `_first` is true, the first logo is shown, +/// otherwise the second logo is shown. When the field changes state, the +/// [AnimatedCrossFade] widget cross-fades between the two forms of the logo +/// over three seconds. +/// +/// ```dart +/// AnimatedCrossFade( +/// duration: const Duration(seconds: 3), +/// firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0), +/// secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0), +/// crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond, +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [AnimatedSize], the lower-level widget which [AnimatedCrossFade] uses to +/// automatically change size. +/// * [AnimatedSwitcher], which switches out a child for a new one with a +/// customizable transition. +class AnimatedCrossFade extends StatefulWidget { + /// Creates a cross-fade animation widget. + /// + /// The [duration] of the animation is the same for all components (fade in, + /// fade out, and size), and you can pass [Interval]s instead of [Curve]s in + /// order to have finer control, e.g., creating an overlap between the fades. + /// + /// All the arguments other than [key] must be non-null. + const AnimatedCrossFade({ + Key key, + @required this.firstChild, + @required this.secondChild, + this.firstCurve = Curves.linear, + this.secondCurve = Curves.linear, + this.sizeCurve = Curves.linear, + this.alignment = Alignment.topCenter, + @required this.crossFadeState, + @required this.duration, + this.reverseDuration, + this.layoutBuilder = defaultLayoutBuilder, + }) : assert(firstChild != null), + assert(secondChild != null), + assert(firstCurve != null), + assert(secondCurve != null), + assert(sizeCurve != null), + assert(alignment != null), + assert(crossFadeState != null), + assert(duration != null), + assert(layoutBuilder != null), + super(key: key); + + /// The child that is visible when [crossFadeState] is + /// [CrossFadeState.showFirst]. It fades out when transitioning + /// [crossFadeState] from [CrossFadeState.showFirst] to + /// [CrossFadeState.showSecond] and vice versa. + final Widget firstChild; + + /// The child that is visible when [crossFadeState] is + /// [CrossFadeState.showSecond]. It fades in when transitioning + /// [crossFadeState] from [CrossFadeState.showFirst] to + /// [CrossFadeState.showSecond] and vice versa. + final Widget secondChild; + + /// The child that will be shown when the animation has completed. + final CrossFadeState crossFadeState; + + /// The duration of the whole orchestrated animation. + final Duration duration; + + /// The duration of the whole orchestrated animation when running in reverse. + /// + /// If not supplied, this defaults to [duration]. + final Duration reverseDuration; + + /// The fade curve of the first child. + /// + /// Defaults to [Curves.linear]. + final Curve firstCurve; + + /// The fade curve of the second child. + /// + /// Defaults to [Curves.linear]. + final Curve secondCurve; + + /// The curve of the animation between the two children's sizes. + /// + /// Defaults to [Curves.linear]. + final Curve sizeCurve; + + /// How the children should be aligned while the size is animating. + /// + /// Defaults to [Alignment.topCenter]. + /// + /// See also: + /// + /// * [Alignment], a class with convenient constants typically used to + /// specify an [AlignmentGeometry]. + /// * [AlignmentDirectional], like [Alignment] for specifying alignments + /// relative to text direction. + final AlignmentGeometry alignment; + + /// A builder that positions the [firstChild] and [secondChild] widgets. + /// + /// The widget returned by this method is wrapped in an [AnimatedSize]. + /// + /// By default, this uses [AnimatedCrossFade.defaultLayoutBuilder], which uses + /// a [Stack] and aligns the `bottomChild` to the top of the stack while + /// providing the `topChild` as the non-positioned child to fill the provided + /// constraints. This works well when the [AnimatedCrossFade] is in a position + /// to change size and when the children are not flexible. However, if the + /// children are less fussy about their sizes (for example a + /// [CircularProgressIndicator] inside a [Center]), or if the + /// [AnimatedCrossFade] is being forced to a particular size, then it can + /// result in the widgets jumping about when the cross-fade state is changed. + final AnimatedCrossFadeBuilder layoutBuilder; + + /// The default layout algorithm used by [AnimatedCrossFade]. + /// + /// The top child is placed in a stack that sizes itself to match the top + /// child. The bottom child is positioned at the top of the same stack, sized + /// to fit its width but without forcing the height. The stack is then + /// clipped. + /// + /// This is the default value for [layoutBuilder]. It implements + /// [AnimatedCrossFadeBuilder]. + static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) { + return Stack( + overflow: Overflow.visible, + children: [ + Positioned( + key: bottomChildKey, + left: 0.0, + top: 0.0, + right: 0.0, + child: bottomChild, + ), + Positioned( + key: topChildKey, + child: topChild, + ), + ], + ); + } + + @override + _AnimatedCrossFadeState createState() => _AnimatedCrossFadeState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('crossFadeState', crossFadeState)); + properties.add(DiagnosticsProperty('alignment', alignment, defaultValue: Alignment.topCenter)); + properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms')); + properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null)); + } +} + +class _AnimatedCrossFadeState extends State with TickerProviderStateMixin { + AnimationController _controller; + Animation _firstAnimation; + Animation _secondAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: widget.duration, + reverseDuration: widget.reverseDuration, + vsync: this, + ); + if (widget.crossFadeState == CrossFadeState.showSecond) _controller.value = 1.0; + _firstAnimation = _initAnimation(widget.firstCurve, true); + _secondAnimation = _initAnimation(widget.secondCurve, false); + _controller.addStatusListener((AnimationStatus status) { + setState(() { + // Trigger a rebuild because it depends on _isTransitioning, which + // changes its value together with animation status. + }); + }); + } + + Animation _initAnimation(Curve curve, bool inverted) { + Animation result = _controller.drive(CurveTween(curve: curve)); + if (inverted) result = result.drive(Tween(begin: 1.0, end: 0.0)); + return result; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(AnimatedCrossFade oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.duration != oldWidget.duration) _controller.duration = widget.duration; + if (widget.reverseDuration != oldWidget.reverseDuration) _controller.reverseDuration = widget.reverseDuration; + if (widget.firstCurve != oldWidget.firstCurve) _firstAnimation = _initAnimation(widget.firstCurve, true); + if (widget.secondCurve != oldWidget.secondCurve) _secondAnimation = _initAnimation(widget.secondCurve, false); + if (widget.crossFadeState != oldWidget.crossFadeState) { + switch (widget.crossFadeState) { + case CrossFadeState.showFirst: + _controller.reverse(); + break; + case CrossFadeState.showSecond: + _controller.forward(); + break; + } + } + } + + /// Whether we're in the middle of cross-fading this frame. + bool get _isTransitioning => _controller.status == AnimationStatus.forward || _controller.status == AnimationStatus.reverse; + + @override + Widget build(BuildContext context) { + const Key kFirstChildKey = ValueKey(CrossFadeState.showFirst); + const Key kSecondChildKey = ValueKey(CrossFadeState.showSecond); + final bool transitioningForwards = _controller.status == AnimationStatus.completed || _controller.status == AnimationStatus.forward; + Key topKey; + Widget topChild; + Animation topAnimation; + Key bottomKey; + Widget bottomChild; + Animation bottomAnimation; + if (transitioningForwards) { + topKey = kSecondChildKey; + topChild = widget.secondChild; + topAnimation = _secondAnimation; + bottomKey = kFirstChildKey; + bottomChild = widget.firstChild; + bottomAnimation = _firstAnimation; + } else { + topKey = kFirstChildKey; + topChild = widget.firstChild; + topAnimation = _firstAnimation; + bottomKey = kSecondChildKey; + bottomChild = widget.secondChild; + bottomAnimation = _secondAnimation; + } + + bottomChild = TickerMode( + key: bottomKey, + enabled: _isTransitioning, + child: ExcludeSemantics( + excluding: true, // Always exclude the semantics of the widget that's fading out. + child: FadeTransition( + opacity: bottomAnimation, + child: bottomChild, + ), + ), + ); + topChild = TickerMode( + key: topKey, enabled: true, // Top widget always has its animations enabled. + child: ExcludeSemantics( + excluding: false, // Always publish semantics for the widget that's fading in. + child: FadeTransition( + opacity: topAnimation, + child: topChild, + ), + ), + ); + return AnimatedSize( + alignment: widget.alignment, + duration: widget.duration, + reverseDuration: widget.reverseDuration, + curve: widget.sizeCurve, + vsync: this, + child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add(EnumProperty('crossFadeState', widget.crossFadeState)); + description.add(DiagnosticsProperty('controller', _controller, showName: false)); + description.add(DiagnosticsProperty('alignment', widget.alignment, defaultValue: Alignment.topCenter)); + } +} diff --git a/peek_and_pop/lib/misc.dart b/peek_and_pop/lib/misc.dart index ef599c6..e69d1cb 100644 --- a/peek_and_pop/lib/misc.dart +++ b/peek_and_pop/lib/misc.dart @@ -36,18 +36,6 @@ enum Stage { IsClosed, } -///See [PeekAndPopChildState.headerSize] and [PeekAndPopChildState.getHeaderOffset]. -enum HeaderOffset { - Zero, - NegativeHalf, - NegativeFull, - PositiveHalf, - PositiveFull, -} - -///See [PeekAndPopChildState.headerSize] and [PeekAndPopChildState.getHeaderOffset]. -final GlobalKey header = GlobalKey(); - ///The new optimised blur effect algorithm during the Peek & Pop process requires your root CupertinoApp/MaterialApp to be wrapped in a ///[RepaintBoundary] widget which uses this key. See README, [PeekAndPopChildState.blurSnapshot] or [PeekAndPopChildState.blurTrackerNotifier] for more ///info. @@ -155,7 +143,6 @@ class PeekAndPopRoute extends PageRoute { @override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { if (!_peekAndPopController.isDirect && !_peekAndPopController.ignoreAnimation && _peekAndPopController.stage != Stage.IsComplete) return child; - if (pageTransition == null) return SlideTransition( position: Tween( diff --git a/peek_and_pop/lib/peek_and_pop_child.dart b/peek_and_pop/lib/peek_and_pop_child.dart index 8cb2314..2efd0cb 100644 --- a/peek_and_pop/lib/peek_and_pop_child.dart +++ b/peek_and_pop/lib/peek_and_pop_child.dart @@ -12,6 +12,7 @@ import 'dart:ui' as ui; import 'dart:math'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; @@ -20,6 +21,7 @@ import 'package:flutter/services.dart'; import 'package:transparent_image/transparent_image.dart'; import 'package:snap/snap.dart'; +import 'animated_cross_fade.dart' as MyAnimatedCrossFade; import 'gesture_detector.dart' as MyGestureDetector; import 'Export.dart'; @@ -54,6 +56,8 @@ class PeekAndPopChildState extends State with SingleTickerProvi final Rect overlapRect; final Alignment alignment; + final GlobalKey helper = GlobalKey(); + final GlobalKey quickActions = GlobalKey(); final GlobalKey snapController = GlobalKey(); final GlobalKey view = GlobalKey(); @@ -89,7 +93,13 @@ class PeekAndPopChildState extends State with SingleTickerProvi return !_peekAndPopController.isDone && _peekAndPopController.stage != Stage.None; } - bool isReady = false; + bool blurIsReady = false; + bool viewIsReady = false; + bool helperIsReady = false; + bool get isReady { + return blurIsReady && viewIsReady && helperIsReady; + } + bool get canPeek { return isReady && animationController.value == 0 && !willPeek && !isPeeking; } @@ -121,11 +131,20 @@ class PeekAndPopChildState extends State with SingleTickerProvi if (backdropFilterIsVisible != showBackdropFilter || blurSnapshotIsVisible != showBlurSnapshot) blurTrackerNotifier.value++; } - void increaseFramecount(Duration duration) async { - isReady = true; + void blurReady(Duration duration) { + blurIsReady = true; frameCount++; } + void viewReady(Duration duration) { + viewIsReady = true; + if (helper.currentState == null) helperIsReady = true; + } + + void helperReady(Duration duration) { + helperIsReady = true; + } + bool willUpdatePeekAndPop(PeekAndPopControllerState _peekAndPopController) { if (animationController.isAnimating || animationController.value != 1.0) return true; if (snapController.currentState == null) return true; @@ -140,10 +159,19 @@ class PeekAndPopChildState extends State with SingleTickerProvi if (quickActionsLowerLimit == -1) return true; if (snapController.currentState.isMoved(quickActionsLowerLimit)) return false; - snapController.currentState.move(Offset.zero); + if (snapController.currentState.isMoved(quickActionsLowerLimit / 2.0)) { + moveAndCancel(); + return false; + } return true; } + void moveAndCancel() { + Future.wait([snapController.currentState.move(const Offset(0.0, 0.0))]).then((_) { + _peekAndPopController.cancelPeekAndPop(null); + }); + } + bool willFinishPeekAndPop(PeekAndPopControllerState _peekAndPopController) { if (animationController.isAnimating || animationController.value != 1.0) return true; if (snapController.currentState == null) return true; @@ -169,10 +197,11 @@ class PeekAndPopChildState extends State with SingleTickerProvi double quickActionsHeight = quickActions.currentState.getHeight(); RenderBox viewRenderBox = view.currentContext.findRenderObject(); double viewHeight = viewRenderBox.size.height; - double totalHeight = viewHeight + quickActionsHeight + _peekAndPopController.quickActionsData.padding.top * 2.0; + double paddingHeight = _peekAndPopController.quickActionsData.padding.top + _peekAndPopController.quickActionsData.padding.bottom; + double totalHeight = viewHeight + quickActionsHeight + paddingHeight; double limitBase = max(-0.1, (totalHeight - height) / viewHeight); - quickActionsUpperLimit = max(100, viewHeight * limitBase + _peekAndPopController.quickActionsData.padding.top * 2.0); + quickActionsUpperLimit = max(100, quickActionsHeight - paddingHeight); quickActionsLowerLimit = max(100, quickActionsUpperLimit / 2.0); snapController.currentState.snapTargets.add(SnapTarget(Offset(0.0, limitBase), Pivot.topLeft)); @@ -270,7 +299,6 @@ class PeekAndPopChildState extends State with SingleTickerProvi void dispose() { reset(); - animationController.removeListener(() {}); animationController.dispose(); super.dispose(); @@ -289,40 +317,7 @@ class PeekAndPopChildState extends State with SingleTickerProvi frameCount = 0; _peekAndPopController.stage = Stage.None; - print(_peekAndPopController.stage); - } - - ///Tests conducted by Swiss scientists have shown that when an [AppBar] or a [CupertinoNavigationBar] is built with full transparency, their height - ///is not included in the layout of a [Scaffold] or a [CupertinoPageScaffold]. Therefore, moving from a Peek stage with a transparent header to a - ///Pop stage with a non-transparent header causes visual conflicts. Use this function with [getHeaderOffset] to prevent such problems. See the - ///provided example for further clarification. - ///IMPORTANT: It is essential that you use the provided [header] key for the header for this function to work. - Size get headerSize { - if (header.currentContext == null) return const Size(0, 0); - - RenderBox renderBox = header.currentContext.findRenderObject(); - return renderBox.size; - } - - ///Tests conducted by Swiss scientists have shown that when an [AppBar] or a [CupertinoNavigationBar] is built with full transparency, their height - ///is not included in the layout of a [Scaffold] or a [CupertinoPageScaffold]. Therefore, moving from a Peek stage with a transparent header to a - ///Pop stage with a non-transparent header causes visual conflicts. Use this function with [headerSize] to prevent such problems. See the - ///provided example for further clarification. - ///IMPORTANT: It is essential that you use the provided [header] key for the header for this function to work. - double getHeaderOffset(HeaderOffset headerOffset) { - switch (headerOffset) { - case HeaderOffset.NegativeHalf: - return -headerSize.height / 2; - case HeaderOffset.NegativeFull: - return -headerSize.height; - case HeaderOffset.PositiveHalf: - return headerSize.height / 2; - case HeaderOffset.PositiveFull: - return headerSize.height; - case HeaderOffset.Zero: - return 0; - } - return 0; + //print(_peekAndPopController.stage); } //TODO: Improve. @@ -354,7 +349,7 @@ class PeekAndPopChildState extends State with SingleTickerProvi isPeeking = false; willPeek = true; _peekAndPopController.stage = Stage.WillPeek; - print(_peekAndPopController.stage); + //print(_peekAndPopController.stage); transformBloc.dispatch(1.0); int currentFramecount = 0; @@ -396,7 +391,7 @@ class PeekAndPopChildState extends State with SingleTickerProvi isPeeking = true; willPeek = false; _peekAndPopController.stage = Stage.IsPeeking; - print(_peekAndPopController.stage); + //print(_peekAndPopController.stage); currentFramecount = frameCount; blurTrackerNotifier.value++; @@ -407,46 +402,145 @@ class PeekAndPopChildState extends State with SingleTickerProvi while (currentFramecount == frameCount) await Future.delayed(Duration(milliseconds: secondaryDelay)); } + while (!isReady) await Future.delayed(const Duration(milliseconds: 1)); + animationController.forward(); - HapticFeedback.lightImpact(); + if (_peekAndPopController.useHaptics) HapticFeedback.lightImpact(); } } + void pop() { + if (helper.currentState == null) return; + + helper.currentState.setState(() { + helper.currentState.stage = 1; + }); + } + Widget wrapper() { - print("Wrapping."); - if (_peekAndPopController.hasQuickActions && !_peekAndPopController.willBeDone && !_peekAndPopController.isDone) - return Container( - key: bound, - constraints: BoxConstraints.expand(), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SnapController( - Container( - key: view, - child: Center( - child: _peekAndPopController.peekAndPopBuilder( - context, - _peekAndPopController, + if (_peekAndPopController.isDirect) { + if (_peekAndPopController.hasSeparatedPeekAndPop) + return Center( + child: _peekAndPopController.peekAndPopBuilderAtPop( + context, + _peekAndPopController, + ), + ); + return Center( + child: _peekAndPopController.peekAndPopBuilder( + context, + _peekAndPopController, + ), + ); + } + + if (_peekAndPopController.hasQuickActions) { + if (_peekAndPopController.hasSeparatedPeekAndPop) + return Helper( + helper, + this, + Container( + key: bound, + constraints: BoxConstraints.expand(), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SnapController( + Container( + key: view, + child: Center( + child: _peekAndPopController.peekAndPopBuilderAtPeek( + context, + _peekAndPopController, + ), + ), ), + true, + view, + bound, + const Offset(0.0, 0.0), + const Offset(1.0, 1.0), + const Offset(0.0, 0.75), + const Offset(0.0, 0.75), + snapTargets: [ + const SnapTarget(Pivot.center, Pivot.center), + ], + useFlick: false, + onMove: onMove, + onSnap: onSnap, + key: snapController, ), - ), - _peekAndPopController.peekAndPopBuilderUseCache, - view, - bound, - const Offset(0.0, 0.0), - const Offset(1.0, 1.0), - const Offset(0.0, 0.75), - const Offset(0.0, 0.75), - snapTargets: [ - const SnapTarget(Pivot.center, Pivot.center), ], - useFlick: false, - onMove: onMove, - onSnap: onSnap, - key: snapController, ), - ], + ), + Center( + child: _peekAndPopController.peekAndPopBuilderAtPop( + context, + _peekAndPopController, + ), + ), + ); + return Helper( + helper, + this, + Container( + key: bound, + constraints: BoxConstraints.expand(), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SnapController( + Container( + key: view, + child: Center( + child: _peekAndPopController.peekAndPopBuilder( + context, + _peekAndPopController, + ), + ), + ), + _peekAndPopController.useCache, + view, + bound, + const Offset(0.0, 0.0), + const Offset(1.0, 1.0), + const Offset(0.0, 0.75), + const Offset(0.0, 0.75), + snapTargets: [ + const SnapTarget(Pivot.center, Pivot.center), + ], + useFlick: false, + onMove: onMove, + onSnap: onSnap, + key: snapController, + ), + ], + ), + ), + Center( + child: _peekAndPopController.peekAndPopBuilder( + context, + _peekAndPopController, + ), + ), + ); + } + + if (_peekAndPopController.hasSeparatedPeekAndPop) + return Helper( + helper, + this, + Center( + child: _peekAndPopController.peekAndPopBuilderAtPeek( + context, + _peekAndPopController, + ), + ), + Center( + child: _peekAndPopController.peekAndPopBuilderAtPop( + context, + _peekAndPopController, + ), ), ); return Center( @@ -461,11 +555,9 @@ class PeekAndPopChildState extends State with SingleTickerProvi ///I) The new optimised blur effect algorithm (see [blurSnapshot]), for obvious reasons. The sigma values are controlled by the ///[PeekAndPopControllerState.primaryAnimationController]. ///II) The view provided by your [PeekAndPopController.peekAndPopBuilder]. This entire widget is continuously rescaled by three different values: - /// a) [animation] controls the scaling of the widget when it is initially pushed to the Navigator. + /// a) [animationController] controls the scaling of the widget when it is initially pushed to the Navigator. /// b) [PeekAndPopControllerState.primaryAnimationController] controls the scaling of the widget during the Peek stage. /// c) [PeekAndPopControllerState.secondaryAnimationController] controls the scaling of the widget during the Pop stage. - /// ([PeekAndPopControllerState.tertiaryAnimationController]) controls the position of the widget during the Peek stage - /// if [PeekAndPopController.moveControler] is set.) ///III) A [MyGestureDetector.GestureDetector] widget, again, for obvious reasons. ///IV) An optional [QuickActions] widget. ///V) An optional second view provided by your [PeekAndPopController.overlayBuiler]. This entire widget is built after everything else so it avoids @@ -481,10 +573,8 @@ class PeekAndPopChildState extends State with SingleTickerProvi children: [ ValueListenableBuilder( builder: (BuildContext context, int blurTracker, Widget cachedChild) { - SchedulerBinding.instance.addPostFrameCallback(increaseFramecount); - print("Rebuilding Blur."); - print("BackdropFilter: " + (showBackdropFilter).toString()); - print("BlurSnapshot:" + (showBlurSnapshot).toString()); + SchedulerBinding.instance.addPostFrameCallback(blurReady); + backdropFilterIsVisible = showBackdropFilter; blurSnapshotIsVisible = showBlurSnapshot; return Stack( @@ -563,9 +653,10 @@ class PeekAndPopChildState extends State with SingleTickerProvi valueListenable: blurTrackerNotifier, ), ValueListenableBuilder( - child: _peekAndPopController.peekAndPopBuilderUseCache ? wrapper() : null, + child: _peekAndPopController.useCache ? wrapper() : null, builder: (BuildContext context, int animationTracker, Widget cachedChild) { - print("Rebuilding View."); + SchedulerBinding.instance.addPostFrameCallback(viewReady); + double scale = _peekAndPopController.peekScale + _peekAndPopController.peekCoefficient * _peekAndPopController.primaryAnimationController.value + _peekAndPopController.secondaryAnimation.value; @@ -595,7 +686,7 @@ class PeekAndPopChildState extends State with SingleTickerProvi bottom: _overlapRect.bottom, child: Opacity( opacity: opacity, - child: _peekAndPopController.peekAndPopBuilderUseCache ? cachedChild : wrapper(), + child: !_peekAndPopController.useCache ? wrapper() : cachedChild, ), ), ], @@ -657,12 +748,65 @@ class PeekAndPopChildState extends State with SingleTickerProvi quickActions, _peekAndPopController.quickActionsData, ), - if (_peekAndPopController.overlayBuilder != null) _peekAndPopController.overlayBuilder, + if (_peekAndPopController.overlayBuilder != null) + _peekAndPopController.overlayBuilder( + context, + ), ], ); } } +class Helper extends StatefulWidget { + final PeekAndPopChildState _peekAndPopChild; + + final Widget firstChild; + final Widget secondChild; + + const Helper( + Key key, + this._peekAndPopChild, + this.firstChild, + this.secondChild, + ) : super(key: key); + + @override + HelperState createState() { + return HelperState( + _peekAndPopChild, + firstChild, + secondChild, + ); + } +} + +class HelperState extends State with SingleTickerProviderStateMixin { + final PeekAndPopChildState _peekAndPopChild; + + final Widget firstChild; + final Widget secondChild; + + int stage = 0; + + HelperState( + this._peekAndPopChild, + this.firstChild, + this.secondChild, + ); + + @override + Widget build(BuildContext context) { + SchedulerBinding.instance.addPostFrameCallback(_peekAndPopChild.helperReady); + + return MyAnimatedCrossFade.AnimatedCrossFade( + duration: const Duration(milliseconds: 111), + firstChild: firstChild, + secondChild: secondChild, + crossFadeState: stage == 0 ? MyAnimatedCrossFade.CrossFadeState.showFirst : MyAnimatedCrossFade.CrossFadeState.showSecond, + ); + } +} + class QuickActions extends StatefulWidget { final QuickActionsData quickActionsData; diff --git a/peek_and_pop/lib/peek_and_pop_controller.dart b/peek_and_pop/lib/peek_and_pop_controller.dart index 77576de..6fc214d 100644 --- a/peek_and_pop/lib/peek_and_pop_controller.dart +++ b/peek_and_pop/lib/peek_and_pop_controller.dart @@ -24,13 +24,23 @@ class PeekAndPopController extends StatefulWidget { ///Set this to true if your [uiChild] doesn't change during the Peek & Pop process. final bool uiChildUseCache; - ///The view to be displayed during the Peek & Pop process. + ///The view to be displayed during the Peek & Pop process. If [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] are set, this value is ignored. final PeekAndPopBuilder peekAndPopBuilder; - ///Set this to true if your [peekAndPopBuilder] doesn't change during the Peek & Pop process. + ///Set this to true if your [peekAndPopBuilder] doesn't change during the Peek & Pop process. If [peekAndPopBuilderAtPop] and + ///[peekAndPopBuilderAtPeek] are set, this value is ignored. final bool peekAndPopBuilderUseCache; - ///The Quick Actions to be displayed if the view is dragged and snapped. Leave null if you do not wish to use Quick Actions. + ///The view to be displayed during the Peek & Pop process at the Peek stage. If this value is set, [peekAndPopBuilderAtPop] must be set too. If + ///this value is set, [peekAndPopBuilder] and [peekAndPopBuilderUseCache] are ignored. + final PeekAndPopBuilder peekAndPopBuilderAtPeek; + + ///The view to be displayed during the Peek & Pop process at the Pop stage. If this value is set, [peekAndPopBuilderAtPeek] must be set too. If + ///this value is set, [peekAndPopBuilder] and [peekAndPopBuilderUseCache] are ignored. + final PeekAndPopBuilder peekAndPopBuilderAtPop; + + ///The Quick Actions to be displayed if the view is dragged and snapped. If this value is set, it is recommended that [peekAndPopBuilderAtPop] + ///and [peekAndPopBuilderAtPop] are set too. final QuickActionsBuilder quickActionsBuilder; ///The maximum [BackdropFilter.sigmaX] and [BackdropFilter.sigmaY] to be applied to the [Blur] widget. @@ -44,7 +54,7 @@ class PeekAndPopController extends StatefulWidget { final int alpha; ///An optional second view to be displayed during the Peek & Pop process. See [PeekAndPopChildState.build] for more. - final Widget overlayBuiler; + final WidgetBuilder overlayBuiler; ///Set this to true if you want your [peekAndPopBuilder] to appear based on the Rect of your [uiChild]. If [customOverlapRect] is provided, it ///will override the Rect of your [uiChild]. Not recommended to use at the same time as [useOverlap] but it won't cause problems as can be seen @@ -136,12 +146,16 @@ class PeekAndPopController extends StatefulWidget { /// (A default [SlideTransition] is provided.) final Function pageTransition; + final bool useHaptics; + const PeekAndPopController( this.uiChild, - this.uiChildUseCache, - this.peekAndPopBuilder, - this.peekAndPopBuilderUseCache, { + this.uiChildUseCache, { Key key, + this.peekAndPopBuilder, + this.peekAndPopBuilderUseCache, + this.peekAndPopBuilderAtPeek, + this.peekAndPopBuilderAtPop, this.quickActionsBuilder, this.sigma: 10, this.backdropColor: Colors.black, @@ -173,6 +187,7 @@ class PeekAndPopController extends StatefulWidget { this.peekScale: 0.5, this.peekCoefficient: 0.05, this.pageTransition, + this.useHaptics: true, }) : super(key: key); @override @@ -182,6 +197,8 @@ class PeekAndPopController extends StatefulWidget { uiChildUseCache, peekAndPopBuilder, peekAndPopBuilderUseCache, + peekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop, quickActionsBuilder, sigma, backdropColor, @@ -213,6 +230,7 @@ class PeekAndPopController extends StatefulWidget { peekScale, peekCoefficient, pageTransition, + useHaptics, ); } } @@ -222,6 +240,16 @@ class PeekAndPopControllerState extends State with TickerP final bool uiChildUseCache; final PeekAndPopBuilder peekAndPopBuilder; final bool peekAndPopBuilderUseCache; + bool get useCache { + return hasSeparatedPeekAndPop || peekAndPopBuilderUseCache; + } + + final PeekAndPopBuilder peekAndPopBuilderAtPeek; + final PeekAndPopBuilder peekAndPopBuilderAtPop; + bool get hasSeparatedPeekAndPop { + return peekAndPopBuilderAtPeek != null && peekAndPopBuilderAtPop != null; + } + final QuickActionsBuilder quickActionsBuilder; QuickActionsData quickActionsData; bool get hasQuickActions { @@ -231,10 +259,10 @@ class PeekAndPopControllerState extends State with TickerP final double sigma; final Color backdropColor; final int alpha; + final WidgetBuilder overlayBuilder; final bool useOverlap; final Rect customOverlapRect; final bool useAlignment; - final Widget overlayBuilder; final bool useIndicator; final double indicatorScaleUpCoefficient; OverlayEntry indicator; @@ -266,6 +294,8 @@ class PeekAndPopControllerState extends State with TickerP final Function pageTransition; + final bool useHaptics; + PeekAndPopChildState peekAndPopChild; ///A required precaution for preventing consecutive Peek & Pop processes without sufficient time. @@ -323,13 +353,15 @@ class PeekAndPopControllerState extends State with TickerP double minimumLengthInBytes = 5000; ///Use this value to determine the depth of debug logging that is actually only here for myself and the Swiss scientists. - final int _debugLevel = 10; + final int _debugLevel = 0; PeekAndPopControllerState( this.uiChild, this.uiChildUseCache, this.peekAndPopBuilder, this.peekAndPopBuilderUseCache, + this.peekAndPopBuilderAtPeek, + this.peekAndPopBuilderAtPop, this.quickActionsBuilder, this.sigma, this.backdropColor, @@ -361,6 +393,7 @@ class PeekAndPopControllerState extends State with TickerP this.peekScale, this.peekCoefficient, this.pageTransition, + this.useHaptics, ); void updateTrackerNotifiers() { @@ -406,7 +439,7 @@ class PeekAndPopControllerState extends State with TickerP break; case AnimationStatus.dismissed: stage = Stage.IsFinished; - print(stage); + //print(stage); if (peekAndPopChild != null) peekAndPopChild.updateBlurTrackerNotifier(); @@ -430,7 +463,7 @@ class PeekAndPopControllerState extends State with TickerP if (!isDirect) { stage = Stage.IsPushed; - print(stage); + //print(stage); } } @@ -713,7 +746,7 @@ class PeekAndPopControllerState extends State with TickerP if (_debugLevel > 0) print("OnPeekAndPopComplete"); stage = Stage.WillComplete; - print(stage); + //print(stage); if (peekAndPopChild != null) peekAndPopChild.updateBlurTrackerNotifier(); @@ -742,7 +775,7 @@ class PeekAndPopControllerState extends State with TickerP pushTime = DateTime.now(); stage = Stage.IsComplete; - print(stage); + //print(stage); if (peekAndPopChild != null) peekAndPopChild.updateBlurTrackerNotifier(); @@ -760,7 +793,7 @@ class PeekAndPopControllerState extends State with TickerP if (_debugLevel > 0) print("PushPeekAndPop"); stage = Stage.WillPush; - print(stage); + //print(stage); if (quickActionsBuilder != null) quickActionsData = quickActionsBuilder(this); @@ -865,7 +898,7 @@ class PeekAndPopControllerState extends State with TickerP if (willCancelPeekAndPop != null && !willCancelPeekAndPop(this)) return; stage = Stage.WillCancel; - print(stage); + //print(stage); if (_debugLevel > 0) { if (!isFromOverlayEntry) @@ -881,7 +914,6 @@ class PeekAndPopControllerState extends State with TickerP } drivePeekAndPop(false); - if (peekAndPopChild != null) peekAndPopChild.animationController.reverse(); popPeekAndPop(); } @@ -898,7 +930,7 @@ class PeekAndPopControllerState extends State with TickerP if (_debugLevel > 0) print("finishPeekAndPop"); stage = Stage.WillFinish; - print(stage); + //print(stage); if (peekAndPopChild != null) peekAndPopChild.updateBlurTrackerNotifier(); @@ -920,7 +952,7 @@ class PeekAndPopControllerState extends State with TickerP if (_debugLevel > 0) print("ClosePeekAndPop"); stage = Stage.WillClose; - print(stage); + //print(stage); if (peekAndPopChild != null) peekAndPopChild.updateBlurTrackerNotifier(); @@ -935,7 +967,7 @@ class PeekAndPopControllerState extends State with TickerP if (stage == Stage.WillCancel) { stage = Stage.IsCancelled; - print(stage); + //print(stage); if (onCancelPeekAndPop != null) onCancelPeekAndPop(this); if (hasQuickActions && peekAndPopChild != null) peekAndPopChild.onCancelPeekAndPop(this); @@ -943,7 +975,7 @@ class PeekAndPopControllerState extends State with TickerP if (stage == Stage.WillClose) { stage = Stage.IsClosed; - print(stage); + //print(stage); if (peekAndPopChild != null) peekAndPopChild.updateBlurTrackerNotifier(); @@ -959,10 +991,15 @@ class PeekAndPopControllerState extends State with TickerP if (_debugLevel > 0) print("DrivePeekAndPop: $forward"); if (forward) { - HapticFeedback.mediumImpact(); + if (useHaptics) HapticFeedback.mediumImpact(); + + if (peekAndPopChild != null) peekAndPopChild.pop(); + primaryAnimationController.forward(); secondaryAnimationController.forward(); } else { + if (peekAndPopChild != null) peekAndPopChild.animationController.reverse(); + primaryAnimationController.reverse(); secondaryAnimationController.reverse(); } diff --git a/peek_and_pop/pretty_example/CHANGELOG.md b/peek_and_pop/pretty_example/CHANGELOG.md index 105ae61..8be0814 100644 --- a/peek_and_pop/pretty_example/CHANGELOG.md +++ b/peek_and_pop/pretty_example/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.0.2] - 07.09.2019 + +* Two new PeekAndPopBuilders are added to [PeekAndPopController]. Use [PeekAndPopController.peekAndPopBuilderAtPeek] and +[PeekAndPopController.peekAndPopBuilderAtPop] for both convenience and improved performance. + +* Improved performance. + ## [1.0.1] - 03.09.2019 * Minor changes. diff --git a/peek_and_pop/pretty_example/README.md b/peek_and_pop/pretty_example/README.md index 8bf3829..28a4108 100644 --- a/peek_and_pop/pretty_example/README.md +++ b/peek_and_pop/pretty_example/README.md @@ -209,9 +209,11 @@ Then, create a PeekAndPopController such as: PeekAndPopController( uiChild(), //Widget uiChild false, //bool uiChildUseCache - peekAndPopBuilder, //PeekAndPopBuilder peekAndPopBuilder - false, //bool peekAndPopBuilderUseCache {Key key, + peekAndPopBuilder, + peekAndPopBuilderUseCache, + peekAndPopBuilderAtPeek : peekAndPopBuilderAtPeek, + peekAndPopBuilderAtPop : peekAndPopBuilderAtPop, quickActionsBuilder : quickActionsBuilder, sigma : 10, backdropColor : Colors.black, @@ -242,15 +244,17 @@ PeekAndPopController( peakPressure : 1.0, peekScale : 0.5, peekCoefficient : 0.05, - popTransition}) + popTransition, + useHaptics : true}) Widget uiChild() {} -Widget peekAndPopBuilder(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPeek(BuildContext context, PeekAndPopControllerState _peekAndPopController); +Widget peekAndPopBuilderAtPop(BuildContext context, PeekAndPopControllerState _peekAndPopController); QuickActionsData quickActionsBuilder(PeekAndPopControllerState _peekAndPopController); -Widget overlayBuiler(); +WidgetBuilder overlayBuiler(BuildContext context); bool _willPeekAndPopComplete(PeekAndPopControllerState _peekAndPopController); bool _willPushPeekAndPop(PeekAndPopControllerState _peekAndPopController); @@ -278,6 +282,9 @@ void _onPressEnd(dynamic dragDetails); * Set [uiChildUseCache] to true if your [uiChild] doesn't change during the Peek & Pop process. * Set [peekAndPopBuilderUseCache] to true if your [peekAndPopBuilder] doesn't change during the Peek & Pop process. +* If [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] are set, [peekAndPopBuilder] and [peekAndPopBuilderUseCache] are ignored. +* If one of [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPeek] is set, the other one must be set too. +* If [quickActionsBuilder] is set, it is recommended that [peekAndPopBuilderAtPop] and [peekAndPopBuilderAtPop] are set too. * [overlayBuilder] is an optional second view to be displayed during the Peek & Pop process. This entire widget is built after everything else. * For all [PeekAndPopProcessNotifier] callbacks such as [willPeekAndPopComplete], you can return false to prevent the default action. * All [PeekAndPopProcessNotifier] and [PeekAndPopProcessCallback] callbacks will return a reference to the created [PeekAndPopController] state. @@ -287,10 +294,6 @@ You can save this instance for further actions. directly. * Use [PeekAndPopControllerState]'s [stage] variable to get enumeration for the stage of the Peek & Pop process. If you want to only know when the Peek & Pop process will be or is completed, you can also use [willBeDone] or [isDone] variables. -* I realised that when an [AppBar] or a [CupertinoNavigationBar] is built with full transparency, their height is not included in the layout of a -[Scaffold] or a [CupertinoPageScaffold]. Therefore, moving from a Peek stage with a transparent header to a Pop stage with a non-transparent header -causes visual conflicts. Use this [PeekAndPopChildState]'s [Size get headerSize] and [double getHeaderOffset(HeaderOffset headerOffset)] methods to -overcome this problem. [comment]: <> (Notes) diff --git a/peek_and_pop/pretty_example/lib/main.dart b/peek_and_pop/pretty_example/lib/main.dart index 7eab757..89a8a95 100644 --- a/peek_and_pop/pretty_example/lib/main.dart +++ b/peek_and_pop/pretty_example/lib/main.dart @@ -20,6 +20,7 @@ import 'package:peek_and_pop/misc.dart' as PeekAndPopMisc; PeekAndPopControllerState peekAndPopController; final GlobalKey scaffold = GlobalKey(); +final GlobalKey header = GlobalKey(); void main() => runApp(MyApp()); @@ -68,7 +69,7 @@ class _MyHomePageState extends State { } Widget atPeekWrapper(Widget child, PeekAndPopControllerState _peekAndPopController) { - return Container( + return Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(const Radius.circular(10.0)), boxShadow: [ @@ -126,6 +127,7 @@ class _MyHomePageState extends State { color: const Color(0xffFF9500), ), ), + transitionBetweenRoutes: false, ); } @@ -138,30 +140,30 @@ class _MyHomePageState extends State { ); } - Widget gridPeekAndPopBuilder(int index, BuildContext context, PeekAndPopControllerState _peekAndPopController) { - if (_peekAndPopController.willBeDone || _peekAndPopController.isDone) - return atPopWrapper( - Transform.translate( - offset: Offset(0, - verticalImages.contains(index) ? 0.0 : _peekAndPopController.peekAndPopChild.getHeaderOffset(PeekAndPopMisc.HeaderOffset.NegativeHalf)), - child: Image.asset( - "assets/" + index.toString() + ".jpeg", - fit: BoxFit.contain, - key: Key("Image"), - ), - ), - _peekAndPopController, - ); - else - return atPeekWrapper( - Image.asset( + Widget gridPeekAndPopBuilderAtPeek(int index, BuildContext context, PeekAndPopControllerState _peekAndPopController) { + return atPeekWrapper( + Image.asset( + "assets/" + index.toString() + ".jpeg", + fit: BoxFit.contain, + key: Key("Image"), + scale: verticalImages.contains(index) ? 0.5 : 1.0, + ), + _peekAndPopController, + ); + } + + Widget gridPeekAndPopBuilderAtPop(int index, BuildContext context, PeekAndPopControllerState _peekAndPopController) { + return atPopWrapper( + Transform.translate( + offset: Offset(0, verticalImages.contains(index) ? 0.0 : -50), + child: Image.asset( "assets/" + index.toString() + ".jpeg", fit: BoxFit.contain, key: Key("Image"), - scale: verticalImages.contains(index) ? 0.5 : 1.0, ), - _peekAndPopController, - ); + ), + _peekAndPopController, + ); } QuickActionsData gridQuickActionsBuilder(PeekAndPopControllerState _peekAndPopController) { @@ -297,6 +299,7 @@ class _MyHomePageState extends State { style: BorderStyle.solid, ), ), + transitionBetweenRoutes: false, ), SliverPadding( padding: EdgeInsets.all(10), @@ -328,9 +331,10 @@ class _MyHomePageState extends State { valueListenable: scrollControllerNotifier, ), true, - (BuildContext context, PeekAndPopControllerState _peekAndPopController) => - gridPeekAndPopBuilder(index, context, _peekAndPopController), - false, + peekAndPopBuilderAtPeek: (BuildContext context, PeekAndPopControllerState _peekAndPopController) => + gridPeekAndPopBuilderAtPeek(index, context, _peekAndPopController), + peekAndPopBuilderAtPop: (BuildContext context, PeekAndPopControllerState _peekAndPopController) => + gridPeekAndPopBuilderAtPop(index, context, _peekAndPopController), quickActionsBuilder: gridQuickActionsBuilder, sigma: 10, backdropColor: Colors.white, diff --git a/peek_and_pop/pretty_example/pubspec.lock b/peek_and_pop/pretty_example/pubspec.lock index 71e2c8e..bcf4f76 100644 --- a/peek_and_pop/pretty_example/pubspec.lock +++ b/peek_and_pop/pretty_example/pubspec.lock @@ -77,7 +77,7 @@ packages: name: flick url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" flutter: dependency: "direct main" description: flutter @@ -129,7 +129,7 @@ packages: path: ".." relative: true source: path - version: "1.0.1" + version: "1.0.2" petitparser: dependency: transitive description: @@ -162,7 +162,7 @@ packages: name: snap url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" source_span: dependency: transitive description: diff --git a/peek_and_pop/pretty_example/pubspec.yaml b/peek_and_pop/pretty_example/pubspec.yaml index 1c86514..8e43b55 100644 --- a/peek_and_pop/pretty_example/pubspec.yaml +++ b/peek_and_pop/pretty_example/pubspec.yaml @@ -1,6 +1,6 @@ name: pretty_example description: Pretty Example Project for peek_and_pop. -version: 1.0.1 +version: 1.0.2 author: Ali Yigit Bireroglu repository: https://github.com/aliyigitbireroglu/flutter-peek-and-pop homepage: https://www.cosmossoftware.coffee diff --git a/peek_and_pop/pubspec.lock b/peek_and_pop/pubspec.lock index 114963a..fe973a8 100644 --- a/peek_and_pop/pubspec.lock +++ b/peek_and_pop/pubspec.lock @@ -70,7 +70,7 @@ packages: name: flick url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" flutter: dependency: "direct main" description: flutter @@ -148,7 +148,7 @@ packages: name: snap url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" source_span: dependency: transitive description: diff --git a/peek_and_pop/pubspec.yaml b/peek_and_pop/pubspec.yaml index 7ae05c6..b045cda 100644 --- a/peek_and_pop/pubspec.yaml +++ b/peek_and_pop/pubspec.yaml @@ -1,6 +1,6 @@ name: peek_and_pop description: Peek & Pop implementation for Flutter based on the iOS functionality of the same name. -version: 1.0.1 +version: 1.0.2 author: Ali Yigit Bireroglu repository: https://github.com/aliyigitbireroglu/flutter-peek-and-pop homepage: https://www.cosmossoftware.coffee @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - snap: ^1.0.3 + snap: ^1.0.4 transparent_image: ^1.0.0 bloc: ^0.15.0