Showing
11 changed files
with
1546 additions
and
27 deletions
| @@ -24,4 +24,64 @@ | @@ -24,4 +24,64 @@ | ||
| 24 | 24 | ||
| 25 | ## [1.2.1] | 25 | ## [1.2.1] |
| 26 | 26 | ||
| 27 | -- Fix bug currentState = null | ||
| 27 | +- Fix bug currentState = null | ||
| 28 | + | ||
| 29 | +## [1.3.0] | ||
| 30 | + | ||
| 31 | +- Update docs, readme, and add full support to flutter_web | ||
| 32 | + | ||
| 33 | +## [1.3.1] | ||
| 34 | + | ||
| 35 | +- Update docs | ||
| 36 | + | ||
| 37 | +## [1.3.2] | ||
| 38 | + | ||
| 39 | +- Improve performance | ||
| 40 | + | ||
| 41 | +## [1.3.3] | ||
| 42 | + | ||
| 43 | +- Fix Get.back arguments | ||
| 44 | + | ||
| 45 | +## [1.3.4] | ||
| 46 | + | ||
| 47 | +- Improve performance | ||
| 48 | + | ||
| 49 | +## [1.4.0] | ||
| 50 | + | ||
| 51 | +- Added Get.removeRoute // remove one route. | ||
| 52 | + Get.until // back repeatedly until the predicate returns true. | ||
| 53 | + Get.offUntil // go to next route and remove all the previous routes until the predicate returns true. | ||
| 54 | + Get.offNamedUntil // go to next named route and remove all the previous routes until the predicate returns true. | ||
| 55 | + | ||
| 56 | +## [1.4.0+6] | ||
| 57 | + | ||
| 58 | +- Improve performance and bug fix | ||
| 59 | + | ||
| 60 | + | ||
| 61 | +## [1.4.0+7] | ||
| 62 | + | ||
| 63 | + - Add more documentation | ||
| 64 | + | ||
| 65 | +## [1.5.0] | ||
| 66 | + | ||
| 67 | + - Add support to dialogs | ||
| 68 | + | ||
| 69 | +## [1.5.0+1] | ||
| 70 | + | ||
| 71 | + - Add color and opacity to dialogs | ||
| 72 | + | ||
| 73 | +## [1.6.0] | ||
| 74 | + | ||
| 75 | + - Add support to snackbars | ||
| 76 | + | ||
| 77 | +## [1.6.1] | ||
| 78 | + | ||
| 79 | + - Add docs and improve performance | ||
| 80 | + | ||
| 81 | +## [1.6.2] | ||
| 82 | + | ||
| 83 | + - Fix bugs on blurred Snackbars | ||
| 84 | + | ||
| 85 | +## [1.6.3] | ||
| 86 | + | ||
| 87 | + - Clean code. |
| 1 | # Get | 1 | # Get |
| 2 | 2 | ||
| 3 | -A consistent Flutter route navigation library that navigate with no context and not rebuild materialApp with each navigation. | ||
| 4 | - | 3 | +A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars from anywhere in your code without context. |
| 5 | ## Getting Started | 4 | ## Getting Started |
| 6 | 5 | ||
| 7 | -Flutter's conventional navigation method has a route reconstruction bug that makes it inconsistent | ||
| 8 | -for large applications with undefined routes. | ||
| 9 | -Get came to solve this problem. | ||
| 10 | -In addition, Get needs no context, also solving Flutter's biggest problem with patterns like BLoC. | ||
| 11 | -Get also makes navigation much clearer and more concise for beginners and friendly to those who came from Web programming. | 6 | +Flutter's conventional navigation has a lot of unnecessary boilerplate, requires context to navigate between screens, open dialogs, and snacking is really painful. |
| 7 | +In addition, with each route navigation, all of your screens below MaterialApp are rebuilt, often causing RAM and CPU bottlenecks. | ||
| 8 | +I worked on a pull to fix it in the framework, and seeing how things work I realized that a lot of cliche code could be avoided to get clean and concise code. | ||
| 9 | +With that in mind, I created this library that will change the way you work with the Framework and save your life from cliche code, | ||
| 10 | +increasing your productivity, and eliminating all the bugs present in Flutter's default navigation altogether. | ||
| 12 | 11 | ||
| 13 | ## How to use? | 12 | ## How to use? |
| 14 | 13 | ||
| @@ -16,7 +15,7 @@ Add this to your package's pubspec.yaml file: | @@ -16,7 +15,7 @@ Add this to your package's pubspec.yaml file: | ||
| 16 | 15 | ||
| 17 | ``` | 16 | ``` |
| 18 | dependencies: | 17 | dependencies: |
| 19 | - get: ^1.4.0 | 18 | + get: ^1.6.3 |
| 20 | ``` | 19 | ``` |
| 21 | 20 | ||
| 22 | And import it: | 21 | And import it: |
| @@ -69,6 +68,41 @@ ex: | @@ -69,6 +68,41 @@ ex: | ||
| 69 | ```dart | 68 | ```dart |
| 70 | if(data == 'sucess') madeAnything(); | 69 | if(data == 'sucess') madeAnything(); |
| 71 | ``` | 70 | ``` |
| 71 | +To open dialog: | ||
| 72 | + | ||
| 73 | +```dart | ||
| 74 | +Get.dialog(YourDialogWidget()); | ||
| 75 | +``` | ||
| 76 | + | ||
| 77 | +To open default dialog: | ||
| 78 | + | ||
| 79 | +```dart | ||
| 80 | + Get.defaultDialog( | ||
| 81 | + title: "My Title", | ||
| 82 | + content: Text("Hi, it's my dialog"), | ||
| 83 | + confirm: FlatButton( | ||
| 84 | + child: Text("Ok"), | ||
| 85 | + onPressed: () => print("OK pressed"), | ||
| 86 | + ), | ||
| 87 | + cancel: FlatButton( | ||
| 88 | + child: Text("Cancel"), | ||
| 89 | + onPressed: () => Get.back(), | ||
| 90 | + )); | ||
| 91 | +``` | ||
| 92 | + | ||
| 93 | +To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold, | ||
| 94 | +but with Get, all you have to do is call your SnackBar from anywhere in your code! No context, no cliche code! | ||
| 95 | + | ||
| 96 | +```dart | ||
| 97 | + GetBar( | ||
| 98 | + title: "Hey i'm a Get SnackBar!", | ||
| 99 | + message: | ||
| 100 | + "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", | ||
| 101 | + duration: Duration(seconds: 3), | ||
| 102 | + )..show(); | ||
| 103 | +``` | ||
| 104 | +Plus, the default SnackBar is completely inflexible, while GetBar lets you change the color, shape, opacity, and anything else you want! | ||
| 105 | + | ||
| 72 | 106 | ||
| 73 | Others methods: | 107 | Others methods: |
| 74 | Get.removeRoute // remove one route. | 108 | Get.removeRoute // remove one route. |
| @@ -154,6 +188,17 @@ class FirstRoute extends StatelessWidget { | @@ -154,6 +188,17 @@ class FirstRoute extends StatelessWidget { | ||
| 154 | Widget build(BuildContext context) { | 188 | Widget build(BuildContext context) { |
| 155 | return Scaffold( | 189 | return Scaffold( |
| 156 | appBar: AppBar( | 190 | appBar: AppBar( |
| 191 | + leading: IconButton( | ||
| 192 | + icon: Icon(Icons.add), | ||
| 193 | + onPressed: () { | ||
| 194 | + GetBar( | ||
| 195 | + title: "Hey i'm a Get SnackBar!", | ||
| 196 | + message: | ||
| 197 | + "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", | ||
| 198 | + duration: Duration(seconds: 3), | ||
| 199 | + )..show(); | ||
| 200 | + }, | ||
| 201 | + ), | ||
| 157 | title: Text('First Route'), | 202 | title: Text('First Route'), |
| 158 | ), | 203 | ), |
| 159 | body: Center( | 204 | body: Center( |
| @@ -7,7 +7,7 @@ packages: | @@ -7,7 +7,7 @@ packages: | ||
| 7 | name: archive | 7 | name: archive |
| 8 | url: "https://pub.dartlang.org" | 8 | url: "https://pub.dartlang.org" |
| 9 | source: hosted | 9 | source: hosted |
| 10 | - version: "2.0.10" | 10 | + version: "2.0.11" |
| 11 | args: | 11 | args: |
| 12 | dependency: transitive | 12 | dependency: transitive |
| 13 | description: | 13 | description: |
| @@ -21,7 +21,7 @@ packages: | @@ -21,7 +21,7 @@ packages: | ||
| 21 | name: async | 21 | name: async |
| 22 | url: "https://pub.dartlang.org" | 22 | url: "https://pub.dartlang.org" |
| 23 | source: hosted | 23 | source: hosted |
| 24 | - version: "2.3.0" | 24 | + version: "2.4.0" |
| 25 | boolean_selector: | 25 | boolean_selector: |
| 26 | dependency: transitive | 26 | dependency: transitive |
| 27 | description: | 27 | description: |
| @@ -94,14 +94,14 @@ packages: | @@ -94,14 +94,14 @@ packages: | ||
| 94 | name: matcher | 94 | name: matcher |
| 95 | url: "https://pub.dartlang.org" | 95 | url: "https://pub.dartlang.org" |
| 96 | source: hosted | 96 | source: hosted |
| 97 | - version: "0.12.5" | 97 | + version: "0.12.6" |
| 98 | meta: | 98 | meta: |
| 99 | dependency: transitive | 99 | dependency: transitive |
| 100 | description: | 100 | description: |
| 101 | name: meta | 101 | name: meta |
| 102 | url: "https://pub.dartlang.org" | 102 | url: "https://pub.dartlang.org" |
| 103 | source: hosted | 103 | source: hosted |
| 104 | - version: "1.1.7" | 104 | + version: "1.1.8" |
| 105 | path: | 105 | path: |
| 106 | dependency: transitive | 106 | dependency: transitive |
| 107 | description: | 107 | description: |
| @@ -176,7 +176,7 @@ packages: | @@ -176,7 +176,7 @@ packages: | ||
| 176 | name: test_api | 176 | name: test_api |
| 177 | url: "https://pub.dartlang.org" | 177 | url: "https://pub.dartlang.org" |
| 178 | source: hosted | 178 | source: hosted |
| 179 | - version: "0.2.5" | 179 | + version: "0.2.11" |
| 180 | typed_data: | 180 | typed_data: |
| 181 | dependency: transitive | 181 | dependency: transitive |
| 182 | description: | 182 | description: |
lib/src/dialog.dart
0 → 100644
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | +import 'package:get/src/routes.dart'; | ||
| 3 | + | ||
| 4 | +class DialogGet extends StatelessWidget { | ||
| 5 | + final Widget child; | ||
| 6 | + final color; | ||
| 7 | + final double opacity; | ||
| 8 | + | ||
| 9 | + const DialogGet({Key key, this.child, this.color, this.opacity = 0.5}) | ||
| 10 | + : super(key: key); | ||
| 11 | + | ||
| 12 | + @override | ||
| 13 | + Widget build(BuildContext context) { | ||
| 14 | + return GestureDetector( | ||
| 15 | + onTap: () => Get.back(), | ||
| 16 | + child: Container( | ||
| 17 | + color: (color == null) | ||
| 18 | + ? Theme.of(context).accentColor.withOpacity(opacity) | ||
| 19 | + : color, | ||
| 20 | + child: GestureDetector(onTap: () => null, child: child), | ||
| 21 | + ), | ||
| 22 | + ); | ||
| 23 | + } | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +class DefaultDialogGet extends StatelessWidget { | ||
| 27 | + final color; | ||
| 28 | + final double opacity; | ||
| 29 | + final String title; | ||
| 30 | + final Widget content; | ||
| 31 | + final Widget cancel; | ||
| 32 | + final Widget confirm; | ||
| 33 | + | ||
| 34 | + const DefaultDialogGet( | ||
| 35 | + {Key key, | ||
| 36 | + this.color, | ||
| 37 | + this.opacity = 0.5, | ||
| 38 | + this.title, | ||
| 39 | + this.content, | ||
| 40 | + this.cancel, | ||
| 41 | + this.confirm}) | ||
| 42 | + : super(key: key); | ||
| 43 | + | ||
| 44 | + @override | ||
| 45 | + Widget build(BuildContext context) { | ||
| 46 | + return GestureDetector( | ||
| 47 | + onTap: () => Get.back(), | ||
| 48 | + child: Container( | ||
| 49 | + color: (color == null) | ||
| 50 | + ? Theme.of(context).accentColor.withOpacity(opacity) | ||
| 51 | + : color, | ||
| 52 | + child: GestureDetector( | ||
| 53 | + onTap: () => null, | ||
| 54 | + child: AlertDialog( | ||
| 55 | + title: Text(title, textAlign: TextAlign.center), | ||
| 56 | + content: content, | ||
| 57 | + actions: <Widget>[cancel, confirm], | ||
| 58 | + ), | ||
| 59 | + ), | ||
| 60 | + ), | ||
| 61 | + ); | ||
| 62 | + } | ||
| 63 | +} |
| @@ -2,6 +2,8 @@ import 'package:flutter/cupertino.dart'; | @@ -2,6 +2,8 @@ import 'package:flutter/cupertino.dart'; | ||
| 2 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
| 3 | 3 | ||
| 4 | class GetRoute<T> extends PageRoute<T> { | 4 | class GetRoute<T> extends PageRoute<T> { |
| 5 | + /// Construct a MaterialPageRoute whose contents are defined by [builder]. | ||
| 6 | + /// | ||
| 5 | /// The values of [builder], [maintainState], and [fullScreenDialog] must not | 7 | /// The values of [builder], [maintainState], and [fullScreenDialog] must not |
| 6 | /// be null. | 8 | /// be null. |
| 7 | GetRoute({ | 9 | GetRoute({ |
| @@ -10,7 +12,7 @@ class GetRoute<T> extends PageRoute<T> { | @@ -10,7 +12,7 @@ class GetRoute<T> extends PageRoute<T> { | ||
| 10 | this.opaque = false, | 12 | this.opaque = false, |
| 11 | this.maintainState = true, | 13 | this.maintainState = true, |
| 12 | bool fullscreenDialog = false, | 14 | bool fullscreenDialog = false, |
| 13 | - }) : assert(builder != null), | 15 | + }) : assert(builder != null), |
| 14 | assert(maintainState != null), | 16 | assert(maintainState != null), |
| 15 | assert(fullscreenDialog != null), | 17 | assert(fullscreenDialog != null), |
| 16 | assert(opaque != null), | 18 | assert(opaque != null), |
| @@ -43,21 +45,22 @@ class GetRoute<T> extends PageRoute<T> { | @@ -43,21 +45,22 @@ class GetRoute<T> extends PageRoute<T> { | ||
| 43 | @override | 45 | @override |
| 44 | bool canTransitionTo(TransitionRoute<dynamic> nextRoute) { | 46 | bool canTransitionTo(TransitionRoute<dynamic> nextRoute) { |
| 45 | // Don't perform outgoing animation if the next route is a fullscreen dialog. | 47 | // Don't perform outgoing animation if the next route is a fullscreen dialog. |
| 46 | - return (nextRoute is GetRoute && !nextRoute.fullscreenDialog) | ||
| 47 | - || (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog); | 48 | + return (nextRoute is GetRoute && !nextRoute.fullscreenDialog) || |
| 49 | + (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog); | ||
| 48 | } | 50 | } |
| 49 | 51 | ||
| 50 | @override | 52 | @override |
| 51 | Widget buildPage( | 53 | Widget buildPage( |
| 52 | - BuildContext context, | ||
| 53 | - Animation<double> animation, | ||
| 54 | - Animation<double> secondaryAnimation, | ||
| 55 | - ) { | 54 | + BuildContext context, |
| 55 | + Animation<double> animation, | ||
| 56 | + Animation<double> secondaryAnimation, | ||
| 57 | + ) { | ||
| 56 | final Widget result = builder(context); | 58 | final Widget result = builder(context); |
| 57 | assert(() { | 59 | assert(() { |
| 58 | if (result == null) { | 60 | if (result == null) { |
| 59 | throw FlutterError.fromParts(<DiagnosticsNode>[ | 61 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 60 | - ErrorSummary('The builder for route "${settings.name}" returned null.'), | 62 | + ErrorSummary( |
| 63 | + 'The builder for route "${settings.name}" returned null.'), | ||
| 61 | ErrorDescription('Route builders must never return null.') | 64 | ErrorDescription('Route builders must never return null.') |
| 62 | ]); | 65 | ]); |
| 63 | } | 66 | } |
| @@ -71,9 +74,11 @@ class GetRoute<T> extends PageRoute<T> { | @@ -71,9 +74,11 @@ class GetRoute<T> extends PageRoute<T> { | ||
| 71 | } | 74 | } |
| 72 | 75 | ||
| 73 | @override | 76 | @override |
| 74 | - Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { | 77 | + Widget buildTransitions(BuildContext context, Animation<double> animation, |
| 78 | + Animation<double> secondaryAnimation, Widget child) { | ||
| 75 | final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme; | 79 | final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme; |
| 76 | - return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child); | 80 | + return theme.buildTransitions<T>( |
| 81 | + this, context, animation, secondaryAnimation, child); | ||
| 77 | } | 82 | } |
| 78 | 83 | ||
| 79 | @override | 84 | @override |
| 1 | +import 'package:flutter/material.dart'; | ||
| 1 | import 'package:flutter/widgets.dart'; | 2 | import 'package:flutter/widgets.dart'; |
| 3 | +import 'dialog.dart'; | ||
| 2 | import 'getroute.dart'; | 4 | import 'getroute.dart'; |
| 3 | 5 | ||
| 4 | class Get { | 6 | class Get { |
| @@ -87,6 +89,28 @@ class Get { | @@ -87,6 +89,28 @@ class Get { | ||
| 87 | .pushReplacement(GetRoute(opaque: rebuildRoutes, builder: (_) => page)); | 89 | .pushReplacement(GetRoute(opaque: rebuildRoutes, builder: (_) => page)); |
| 88 | } | 90 | } |
| 89 | 91 | ||
| 92 | + /// Show a dialog. You can choose color and opacity of background | ||
| 93 | + static dialog(Widget page, {Color color, double opacity = 0.5}) { | ||
| 94 | + Get.to(DialogGet(child: page, color: color, opacity: opacity)); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + static defaultDialog( | ||
| 98 | + {Color color, | ||
| 99 | + double opacity = 0.5, | ||
| 100 | + String title = "Alert dialog", | ||
| 101 | + Widget content, | ||
| 102 | + Widget cancel, | ||
| 103 | + Widget confirm}) { | ||
| 104 | + Get.to(DefaultDialogGet( | ||
| 105 | + color: color, | ||
| 106 | + opacity: opacity, | ||
| 107 | + title: title, | ||
| 108 | + content: content, | ||
| 109 | + cancel: cancel, | ||
| 110 | + confirm: confirm, | ||
| 111 | + )); | ||
| 112 | + } | ||
| 113 | + | ||
| 90 | /// It replaces Navigator.pushAndRemoveUntil, but needs no context | 114 | /// It replaces Navigator.pushAndRemoveUntil, but needs no context |
| 91 | static offAll(Widget page, RoutePredicate predicate, | 115 | static offAll(Widget page, RoutePredicate predicate, |
| 92 | {bool rebuildRoutes = false}) { | 116 | {bool rebuildRoutes = false}) { |
lib/src/snack.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | +import 'dart:ui'; | ||
| 3 | +import 'package:flutter/material.dart'; | ||
| 4 | +import 'package:flutter/scheduler.dart'; | ||
| 5 | +import 'package:get/get.dart'; | ||
| 6 | +import 'snack_route.dart' as route; | ||
| 7 | + | ||
| 8 | +typedef void SnackStatusCallback(SnackStatus status); | ||
| 9 | +typedef void OnTap(GetBar snack); | ||
| 10 | + | ||
| 11 | +// ignore: must_be_immutable | ||
| 12 | +class GetBar<T extends Object> extends StatefulWidget { | ||
| 13 | + GetBar( | ||
| 14 | + {Key key, | ||
| 15 | + String title, | ||
| 16 | + String message, | ||
| 17 | + Widget titleText, | ||
| 18 | + Widget messageText, | ||
| 19 | + Widget icon, | ||
| 20 | + bool shouldIconPulse = true, | ||
| 21 | + double maxWidth, | ||
| 22 | + EdgeInsets margin = const EdgeInsets.all(0.0), | ||
| 23 | + EdgeInsets padding = const EdgeInsets.all(16), | ||
| 24 | + double borderRadius = 0.0, | ||
| 25 | + Color borderColor, | ||
| 26 | + double borderWidth = 1.0, | ||
| 27 | + Color backgroundColor = const Color(0xFF303030), | ||
| 28 | + Color leftBarIndicatorColor, | ||
| 29 | + List<BoxShadow> boxShadows, | ||
| 30 | + Gradient backgroundGradient, | ||
| 31 | + FlatButton mainButton, | ||
| 32 | + OnTap onTap, | ||
| 33 | + Duration duration, | ||
| 34 | + bool isDismissible = true, | ||
| 35 | + SnackDismissDirection dismissDirection = SnackDismissDirection.VERTICAL, | ||
| 36 | + bool showProgressIndicator = false, | ||
| 37 | + AnimationController progressIndicatorController, | ||
| 38 | + Color progressIndicatorBackgroundColor, | ||
| 39 | + Animation<Color> progressIndicatorValueColor, | ||
| 40 | + SnackPosition snackPosition = SnackPosition.BOTTOM, | ||
| 41 | + SnackStyle snackStyle = SnackStyle.FLOATING, | ||
| 42 | + Curve forwardAnimationCurve = Curves.easeOutCirc, | ||
| 43 | + Curve reverseAnimationCurve = Curves.easeOutCirc, | ||
| 44 | + Duration animationDuration = const Duration(seconds: 1), | ||
| 45 | + SnackStatusCallback onStatusChanged, | ||
| 46 | + double barBlur = 0.0, | ||
| 47 | + double overlayBlur = 0.0, | ||
| 48 | + Color overlayColor = Colors.transparent, | ||
| 49 | + Form userInputForm}) | ||
| 50 | + : this.title = title, | ||
| 51 | + this.message = message, | ||
| 52 | + this.titleText = titleText, | ||
| 53 | + this.messageText = messageText, | ||
| 54 | + this.icon = icon, | ||
| 55 | + this.shouldIconPulse = shouldIconPulse, | ||
| 56 | + this.maxWidth = maxWidth, | ||
| 57 | + this.margin = margin, | ||
| 58 | + this.padding = padding, | ||
| 59 | + this.borderRadius = borderRadius, | ||
| 60 | + this.borderColor = borderColor, | ||
| 61 | + this.borderWidth = borderWidth, | ||
| 62 | + this.backgroundColor = backgroundColor, | ||
| 63 | + this.leftBarIndicatorColor = leftBarIndicatorColor, | ||
| 64 | + this.boxShadows = boxShadows, | ||
| 65 | + this.backgroundGradient = backgroundGradient, | ||
| 66 | + this.mainButton = mainButton, | ||
| 67 | + this.onTap = onTap, | ||
| 68 | + this.duration = duration, | ||
| 69 | + this.isDismissible = isDismissible, | ||
| 70 | + this.dismissDirection = dismissDirection, | ||
| 71 | + this.showProgressIndicator = showProgressIndicator, | ||
| 72 | + this.progressIndicatorController = progressIndicatorController, | ||
| 73 | + this.progressIndicatorBackgroundColor = | ||
| 74 | + progressIndicatorBackgroundColor, | ||
| 75 | + this.progressIndicatorValueColor = progressIndicatorValueColor, | ||
| 76 | + this.snackPosition = snackPosition, | ||
| 77 | + this.snackStyle = snackStyle, | ||
| 78 | + this.forwardAnimationCurve = forwardAnimationCurve, | ||
| 79 | + this.reverseAnimationCurve = reverseAnimationCurve, | ||
| 80 | + this.animationDuration = animationDuration, | ||
| 81 | + this.barBlur = barBlur, | ||
| 82 | + this.overlayBlur = overlayBlur, | ||
| 83 | + this.overlayColor = overlayColor, | ||
| 84 | + this.userInputForm = userInputForm, | ||
| 85 | + super(key: key) { | ||
| 86 | + this.onStatusChanged = onStatusChanged ?? (status) {}; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /// A callback for you to listen to the different Snack status | ||
| 90 | + SnackStatusCallback onStatusChanged; | ||
| 91 | + | ||
| 92 | + /// The title displayed to the user | ||
| 93 | + final String title; | ||
| 94 | + | ||
| 95 | + /// The message displayed to the user. | ||
| 96 | + final String message; | ||
| 97 | + | ||
| 98 | + /// Replaces [title]. Although this accepts a [Widget], it is meant to receive [Text] or [RichText] | ||
| 99 | + final Widget titleText; | ||
| 100 | + | ||
| 101 | + /// Replaces [message]. Although this accepts a [Widget], it is meant to receive [Text] or [RichText] | ||
| 102 | + final Widget messageText; | ||
| 103 | + | ||
| 104 | + /// Will be ignored if [backgroundGradient] is not null | ||
| 105 | + final Color backgroundColor; | ||
| 106 | + | ||
| 107 | + /// If not null, shows a left vertical colored bar on notification. | ||
| 108 | + /// It is not possible to use it with a [Form] and I do not recommend using it with [LinearProgressIndicator] | ||
| 109 | + final Color leftBarIndicatorColor; | ||
| 110 | + | ||
| 111 | + /// [boxShadows] The shadows generated by Snack. Leave it null if you don't want a shadow. | ||
| 112 | + /// You can use more than one if you feel the need. | ||
| 113 | + /// Check (this example)[https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/shadows.dart] | ||
| 114 | + final List<BoxShadow> boxShadows; | ||
| 115 | + | ||
| 116 | + /// Makes [backgroundColor] be ignored. | ||
| 117 | + final Gradient backgroundGradient; | ||
| 118 | + | ||
| 119 | + /// You can use any widget here, but I recommend [Icon] or [Image] as indication of what kind | ||
| 120 | + /// of message you are displaying. Other widgets may break the layout | ||
| 121 | + final Widget icon; | ||
| 122 | + | ||
| 123 | + /// An option to animate the icon (if present). Defaults to true. | ||
| 124 | + final bool shouldIconPulse; | ||
| 125 | + | ||
| 126 | + /// A [FlatButton] widget if you need an action from the user. | ||
| 127 | + final FlatButton mainButton; | ||
| 128 | + | ||
| 129 | + /// A callback that registers the user's click anywhere. An alternative to [mainButton] | ||
| 130 | + final OnTap onTap; | ||
| 131 | + | ||
| 132 | + /// How long until Snack will hide itself (be dismissed). To make it indefinite, leave it null. | ||
| 133 | + final Duration duration; | ||
| 134 | + | ||
| 135 | + /// True if you want to show a [LinearProgressIndicator]. | ||
| 136 | + final bool showProgressIndicator; | ||
| 137 | + | ||
| 138 | + /// An optional [AnimationController] when you want to control the progress of your [LinearProgressIndicator]. | ||
| 139 | + final AnimationController progressIndicatorController; | ||
| 140 | + | ||
| 141 | + /// A [LinearProgressIndicator] configuration parameter. | ||
| 142 | + final Color progressIndicatorBackgroundColor; | ||
| 143 | + | ||
| 144 | + /// A [LinearProgressIndicator] configuration parameter. | ||
| 145 | + final Animation<Color> progressIndicatorValueColor; | ||
| 146 | + | ||
| 147 | + /// Determines if the user can swipe or click the overlay (if [overlayBlur] > 0) to dismiss. | ||
| 148 | + /// It is recommended that you set [duration] != null if this is false. | ||
| 149 | + /// If the user swipes to dismiss or clicks the overlay, no value will be returned. | ||
| 150 | + final bool isDismissible; | ||
| 151 | + | ||
| 152 | + /// Used to limit Snack width (usually on large screens) | ||
| 153 | + final double maxWidth; | ||
| 154 | + | ||
| 155 | + /// Adds a custom margin to Snack | ||
| 156 | + final EdgeInsets margin; | ||
| 157 | + | ||
| 158 | + /// Adds a custom padding to Snack | ||
| 159 | + /// The default follows material design guide line | ||
| 160 | + final EdgeInsets padding; | ||
| 161 | + | ||
| 162 | + /// Adds a radius to all corners of Snack. Best combined with [margin]. | ||
| 163 | + /// I do not recommend using it with [showProgressIndicator] or [leftBarIndicatorColor]. | ||
| 164 | + final double borderRadius; | ||
| 165 | + | ||
| 166 | + /// Adds a border to every side of Snack | ||
| 167 | + /// I do not recommend using it with [showProgressIndicator] or [leftBarIndicatorColor]. | ||
| 168 | + final Color borderColor; | ||
| 169 | + | ||
| 170 | + /// Changes the width of the border if [borderColor] is specified | ||
| 171 | + final double borderWidth; | ||
| 172 | + | ||
| 173 | + /// Snack can be based on [SnackPosition.TOP] or on [SnackPosition.BOTTOM] of your screen. | ||
| 174 | + /// [SnackPosition.BOTTOM] is the default. | ||
| 175 | + final SnackPosition snackPosition; | ||
| 176 | + | ||
| 177 | + /// [SnackDismissDirection.VERTICAL] by default. | ||
| 178 | + /// Can also be [SnackDismissDirection.HORIZONTAL] in which case both left and right dismiss are allowed. | ||
| 179 | + final SnackDismissDirection dismissDirection; | ||
| 180 | + | ||
| 181 | + /// Snack can be floating or be grounded to the edge of the screen. | ||
| 182 | + /// If grounded, I do not recommend using [margin] or [borderRadius]. [SnackStyle.FLOATING] is the default | ||
| 183 | + /// If grounded, I do not recommend using a [backgroundColor] with transparency or [barBlur] | ||
| 184 | + final SnackStyle snackStyle; | ||
| 185 | + | ||
| 186 | + /// The [Curve] animation used when show() is called. [Curves.easeOut] is default | ||
| 187 | + final Curve forwardAnimationCurve; | ||
| 188 | + | ||
| 189 | + /// The [Curve] animation used when dismiss() is called. [Curves.fastOutSlowIn] is default | ||
| 190 | + final Curve reverseAnimationCurve; | ||
| 191 | + | ||
| 192 | + /// Use it to speed up or slow down the animation duration | ||
| 193 | + final Duration animationDuration; | ||
| 194 | + | ||
| 195 | + /// Default is 0.0. If different than 0.0, blurs only Snack's background. | ||
| 196 | + /// To take effect, make sure your [backgroundColor] has some opacity. | ||
| 197 | + /// The greater the value, the greater the blur. | ||
| 198 | + final double barBlur; | ||
| 199 | + | ||
| 200 | + /// Default is 0.0. If different than 0.0, creates a blurred | ||
| 201 | + /// overlay that prevents the user from interacting with the screen. | ||
| 202 | + /// The greater the value, the greater the blur. | ||
| 203 | + final double overlayBlur; | ||
| 204 | + | ||
| 205 | + /// Default is [Colors.transparent]. Only takes effect if [overlayBlur] > 0.0. | ||
| 206 | + /// Make sure you use a color with transparency here e.g. Colors.grey[600].withOpacity(0.2). | ||
| 207 | + final Color overlayColor; | ||
| 208 | + | ||
| 209 | + /// A [TextFormField] in case you want a simple user input. Every other widget is ignored if this is not null. | ||
| 210 | + final Form userInputForm; | ||
| 211 | + | ||
| 212 | + route.SnackRoute<T> _snackRoute; | ||
| 213 | + | ||
| 214 | + /// Show the snack. Kicks in [SnackStatus.IS_APPEARING] state followed by [SnackStatus.SHOWING] | ||
| 215 | + Future<T> show() async { | ||
| 216 | + _snackRoute = route.showSnack<T>( | ||
| 217 | + snack: this, | ||
| 218 | + ); | ||
| 219 | + | ||
| 220 | + return await Get.key.currentState.push(_snackRoute); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + /// Dismisses the snack causing is to return a future containing [result]. | ||
| 224 | + /// When this future finishes, it is guaranteed that Snack was dismissed. | ||
| 225 | + Future<T> dismiss([T result]) async { | ||
| 226 | + // If route was never initialized, do nothing | ||
| 227 | + if (_snackRoute == null) { | ||
| 228 | + return null; | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + if (_snackRoute.isCurrent) { | ||
| 232 | + _snackRoute.navigator.pop(result); | ||
| 233 | + return _snackRoute.completed; | ||
| 234 | + } else if (_snackRoute.isActive) { | ||
| 235 | + // removeRoute is called every time you dismiss a Snack that is not the top route. | ||
| 236 | + // It will not animate back and listeners will not detect SnackStatus.IS_HIDING or SnackStatus.DISMISSED | ||
| 237 | + // To avoid this, always make sure that Snack is the top route when it is being dismissed | ||
| 238 | + _snackRoute.navigator.removeRoute(_snackRoute); | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + return null; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + /// Checks if the snack is visible | ||
| 245 | + bool isShowing() { | ||
| 246 | + return _snackRoute?.currentStatus == SnackStatus.SHOWING; | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + /// Checks if the snack is dismissed | ||
| 250 | + bool isDismissed() { | ||
| 251 | + return _snackRoute?.currentStatus == SnackStatus.DISMISSED; | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + @override | ||
| 255 | + State createState() { | ||
| 256 | + return _GetBarState<T>(); | ||
| 257 | + } | ||
| 258 | +} | ||
| 259 | + | ||
| 260 | +class _GetBarState<K extends Object> extends State<GetBar> | ||
| 261 | + with TickerProviderStateMixin { | ||
| 262 | + SnackStatus currentStatus; | ||
| 263 | + | ||
| 264 | + AnimationController _fadeController; | ||
| 265 | + Animation<double> _fadeAnimation; | ||
| 266 | + | ||
| 267 | + final Widget _emptyWidget = SizedBox(width: 0.0, height: 0.0); | ||
| 268 | + final double _initialOpacity = 1.0; | ||
| 269 | + final double _finalOpacity = 0.4; | ||
| 270 | + | ||
| 271 | + final Duration _pulseAnimationDuration = Duration(seconds: 1); | ||
| 272 | + | ||
| 273 | + bool _isTitlePresent; | ||
| 274 | + double _messageTopMargin; | ||
| 275 | + | ||
| 276 | + FocusScopeNode _focusNode; | ||
| 277 | + FocusAttachment _focusAttachment; | ||
| 278 | + | ||
| 279 | + @override | ||
| 280 | + void initState() { | ||
| 281 | + super.initState(); | ||
| 282 | + | ||
| 283 | + assert( | ||
| 284 | + ((widget.userInputForm != null || | ||
| 285 | + ((widget.message != null && widget.message.isNotEmpty) || | ||
| 286 | + widget.messageText != null))), | ||
| 287 | + "A message is mandatory if you are not using userInputForm. Set either a message or messageText"); | ||
| 288 | + | ||
| 289 | + _isTitlePresent = (widget.title != null || widget.titleText != null); | ||
| 290 | + _messageTopMargin = _isTitlePresent ? 6.0 : widget.padding.top; | ||
| 291 | + | ||
| 292 | + _configureLeftBarFuture(); | ||
| 293 | + _configureProgressIndicatorAnimation(); | ||
| 294 | + | ||
| 295 | + if (widget.icon != null && widget.shouldIconPulse) { | ||
| 296 | + _configurePulseAnimation(); | ||
| 297 | + _fadeController?.forward(); | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + _focusNode = FocusScopeNode(); | ||
| 301 | + _focusAttachment = _focusNode.attach(context); | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + @override | ||
| 305 | + void dispose() { | ||
| 306 | + _fadeController?.dispose(); | ||
| 307 | + | ||
| 308 | + widget.progressIndicatorController?.removeListener(_progressListener); | ||
| 309 | + widget.progressIndicatorController?.dispose(); | ||
| 310 | + | ||
| 311 | + _focusAttachment.detach(); | ||
| 312 | + _focusNode.dispose(); | ||
| 313 | + super.dispose(); | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + final Completer<Size> _boxHeightCompleter = Completer<Size>(); | ||
| 317 | + | ||
| 318 | + void _configureLeftBarFuture() { | ||
| 319 | + SchedulerBinding.instance.addPostFrameCallback( | ||
| 320 | + (_) { | ||
| 321 | + final keyContext = backgroundBoxKey.currentContext; | ||
| 322 | + | ||
| 323 | + if (keyContext != null) { | ||
| 324 | + final RenderBox box = keyContext.findRenderObject(); | ||
| 325 | + _boxHeightCompleter.complete(box.size); | ||
| 326 | + } | ||
| 327 | + }, | ||
| 328 | + ); | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + void _configurePulseAnimation() { | ||
| 332 | + _fadeController = | ||
| 333 | + AnimationController(vsync: this, duration: _pulseAnimationDuration); | ||
| 334 | + _fadeAnimation = Tween(begin: _initialOpacity, end: _finalOpacity).animate( | ||
| 335 | + CurvedAnimation( | ||
| 336 | + parent: _fadeController, | ||
| 337 | + curve: Curves.linear, | ||
| 338 | + ), | ||
| 339 | + ); | ||
| 340 | + | ||
| 341 | + _fadeController.addStatusListener((status) { | ||
| 342 | + if (status == AnimationStatus.completed) { | ||
| 343 | + _fadeController.reverse(); | ||
| 344 | + } | ||
| 345 | + if (status == AnimationStatus.dismissed) { | ||
| 346 | + _fadeController.forward(); | ||
| 347 | + } | ||
| 348 | + }); | ||
| 349 | + | ||
| 350 | + _fadeController.forward(); | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + Function _progressListener; | ||
| 354 | + | ||
| 355 | + void _configureProgressIndicatorAnimation() { | ||
| 356 | + if (widget.showProgressIndicator && | ||
| 357 | + widget.progressIndicatorController != null) { | ||
| 358 | + _progressListener = () { | ||
| 359 | + setState(() {}); | ||
| 360 | + }; | ||
| 361 | + widget.progressIndicatorController.addListener(_progressListener); | ||
| 362 | + | ||
| 363 | + _progressAnimation = CurvedAnimation( | ||
| 364 | + curve: Curves.linear, parent: widget.progressIndicatorController); | ||
| 365 | + } | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + @override | ||
| 369 | + Widget build(BuildContext context) { | ||
| 370 | + return Align( | ||
| 371 | + heightFactor: 1.0, | ||
| 372 | + child: Material( | ||
| 373 | + color: widget.snackStyle == SnackStyle.FLOATING | ||
| 374 | + ? Colors.transparent | ||
| 375 | + : widget.backgroundColor, | ||
| 376 | + child: SafeArea( | ||
| 377 | + minimum: widget.snackPosition == SnackPosition.BOTTOM | ||
| 378 | + ? EdgeInsets.only( | ||
| 379 | + bottom: MediaQuery.of(context).viewInsets.bottom) | ||
| 380 | + : EdgeInsets.only(top: MediaQuery.of(context).viewInsets.top), | ||
| 381 | + bottom: widget.snackPosition == SnackPosition.BOTTOM, | ||
| 382 | + top: widget.snackPosition == SnackPosition.TOP, | ||
| 383 | + left: false, | ||
| 384 | + right: false, | ||
| 385 | + child: _getSnack(), | ||
| 386 | + ), | ||
| 387 | + ), | ||
| 388 | + ); | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + Widget _getSnack() { | ||
| 392 | + Widget snack; | ||
| 393 | + | ||
| 394 | + if (widget.userInputForm != null) { | ||
| 395 | + snack = _generateInputSnack(); | ||
| 396 | + } else { | ||
| 397 | + snack = _generateSnack(); | ||
| 398 | + } | ||
| 399 | + | ||
| 400 | + return Stack( | ||
| 401 | + children: [ | ||
| 402 | + FutureBuilder( | ||
| 403 | + future: _boxHeightCompleter.future, | ||
| 404 | + builder: (context, AsyncSnapshot<Size> snapshot) { | ||
| 405 | + if (snapshot.hasData) { | ||
| 406 | + return ClipRRect( | ||
| 407 | + borderRadius: BorderRadius.circular(widget.borderRadius), | ||
| 408 | + child: BackdropFilter( | ||
| 409 | + filter: ImageFilter.blur( | ||
| 410 | + sigmaX: widget.barBlur, sigmaY: widget.barBlur), | ||
| 411 | + child: Container( | ||
| 412 | + height: snapshot.data.height, | ||
| 413 | + width: snapshot.data.width, | ||
| 414 | + decoration: BoxDecoration( | ||
| 415 | + color: Colors.transparent, | ||
| 416 | + borderRadius: BorderRadius.circular(widget.borderRadius), | ||
| 417 | + ), | ||
| 418 | + ), | ||
| 419 | + ), | ||
| 420 | + ); | ||
| 421 | + } else { | ||
| 422 | + return _emptyWidget; | ||
| 423 | + } | ||
| 424 | + }, | ||
| 425 | + ), | ||
| 426 | + snack, | ||
| 427 | + ], | ||
| 428 | + ); | ||
| 429 | + } | ||
| 430 | + | ||
| 431 | + Widget _generateInputSnack() { | ||
| 432 | + return Container( | ||
| 433 | + key: backgroundBoxKey, | ||
| 434 | + constraints: widget.maxWidth != null | ||
| 435 | + ? BoxConstraints(maxWidth: widget.maxWidth) | ||
| 436 | + : null, | ||
| 437 | + decoration: BoxDecoration( | ||
| 438 | + color: widget.backgroundColor, | ||
| 439 | + gradient: widget.backgroundGradient, | ||
| 440 | + boxShadow: widget.boxShadows, | ||
| 441 | + borderRadius: BorderRadius.circular(widget.borderRadius), | ||
| 442 | + border: widget.borderColor != null | ||
| 443 | + ? Border.all(color: widget.borderColor, width: widget.borderWidth) | ||
| 444 | + : null, | ||
| 445 | + ), | ||
| 446 | + child: Padding( | ||
| 447 | + padding: const EdgeInsets.only( | ||
| 448 | + left: 8.0, right: 8.0, bottom: 8.0, top: 16.0), | ||
| 449 | + child: FocusScope( | ||
| 450 | + child: widget.userInputForm, | ||
| 451 | + node: _focusNode, | ||
| 452 | + autofocus: true, | ||
| 453 | + ), | ||
| 454 | + ), | ||
| 455 | + ); | ||
| 456 | + } | ||
| 457 | + | ||
| 458 | + CurvedAnimation _progressAnimation; | ||
| 459 | + GlobalKey backgroundBoxKey = GlobalKey(); | ||
| 460 | + | ||
| 461 | + Widget _generateSnack() { | ||
| 462 | + return Container( | ||
| 463 | + key: backgroundBoxKey, | ||
| 464 | + constraints: widget.maxWidth != null | ||
| 465 | + ? BoxConstraints(maxWidth: widget.maxWidth) | ||
| 466 | + : null, | ||
| 467 | + decoration: BoxDecoration( | ||
| 468 | + color: widget.backgroundColor, | ||
| 469 | + gradient: widget.backgroundGradient, | ||
| 470 | + boxShadow: widget.boxShadows, | ||
| 471 | + borderRadius: BorderRadius.circular(widget.borderRadius), | ||
| 472 | + border: widget.borderColor != null | ||
| 473 | + ? Border.all(color: widget.borderColor, width: widget.borderWidth) | ||
| 474 | + : null, | ||
| 475 | + ), | ||
| 476 | + child: Column( | ||
| 477 | + mainAxisSize: MainAxisSize.min, | ||
| 478 | + children: [ | ||
| 479 | + widget.showProgressIndicator | ||
| 480 | + ? LinearProgressIndicator( | ||
| 481 | + value: widget.progressIndicatorController != null | ||
| 482 | + ? _progressAnimation.value | ||
| 483 | + : null, | ||
| 484 | + backgroundColor: widget.progressIndicatorBackgroundColor, | ||
| 485 | + valueColor: widget.progressIndicatorValueColor, | ||
| 486 | + ) | ||
| 487 | + : _emptyWidget, | ||
| 488 | + Row( | ||
| 489 | + mainAxisSize: MainAxisSize.max, | ||
| 490 | + children: _getAppropriateRowLayout(), | ||
| 491 | + ), | ||
| 492 | + ], | ||
| 493 | + ), | ||
| 494 | + ); | ||
| 495 | + } | ||
| 496 | + | ||
| 497 | + List<Widget> _getAppropriateRowLayout() { | ||
| 498 | + double buttonRightPadding; | ||
| 499 | + double iconPadding = 0; | ||
| 500 | + if (widget.padding.right - 12 < 0) { | ||
| 501 | + buttonRightPadding = 4; | ||
| 502 | + } else { | ||
| 503 | + buttonRightPadding = widget.padding.right - 12; | ||
| 504 | + } | ||
| 505 | + | ||
| 506 | + if (widget.padding.left > 16.0) { | ||
| 507 | + iconPadding = widget.padding.left; | ||
| 508 | + } | ||
| 509 | + | ||
| 510 | + if (widget.icon == null && widget.mainButton == null) { | ||
| 511 | + return [ | ||
| 512 | + _buildLeftBarIndicator(), | ||
| 513 | + Expanded( | ||
| 514 | + flex: 1, | ||
| 515 | + child: Column( | ||
| 516 | + crossAxisAlignment: CrossAxisAlignment.stretch, | ||
| 517 | + mainAxisSize: MainAxisSize.min, | ||
| 518 | + children: <Widget>[ | ||
| 519 | + (_isTitlePresent) | ||
| 520 | + ? Padding( | ||
| 521 | + padding: EdgeInsets.only( | ||
| 522 | + top: widget.padding.top, | ||
| 523 | + left: widget.padding.left, | ||
| 524 | + right: widget.padding.right, | ||
| 525 | + ), | ||
| 526 | + child: _getTitleText(), | ||
| 527 | + ) | ||
| 528 | + : _emptyWidget, | ||
| 529 | + Padding( | ||
| 530 | + padding: EdgeInsets.only( | ||
| 531 | + top: _messageTopMargin, | ||
| 532 | + left: widget.padding.left, | ||
| 533 | + right: widget.padding.right, | ||
| 534 | + bottom: widget.padding.bottom, | ||
| 535 | + ), | ||
| 536 | + child: widget.messageText ?? _getDefaultNotificationText(), | ||
| 537 | + ), | ||
| 538 | + ], | ||
| 539 | + ), | ||
| 540 | + ), | ||
| 541 | + ]; | ||
| 542 | + } else if (widget.icon != null && widget.mainButton == null) { | ||
| 543 | + return <Widget>[ | ||
| 544 | + _buildLeftBarIndicator(), | ||
| 545 | + ConstrainedBox( | ||
| 546 | + constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding), | ||
| 547 | + child: _getIcon(), | ||
| 548 | + ), | ||
| 549 | + Expanded( | ||
| 550 | + flex: 1, | ||
| 551 | + child: Column( | ||
| 552 | + crossAxisAlignment: CrossAxisAlignment.stretch, | ||
| 553 | + mainAxisSize: MainAxisSize.min, | ||
| 554 | + children: <Widget>[ | ||
| 555 | + (_isTitlePresent) | ||
| 556 | + ? Padding( | ||
| 557 | + padding: EdgeInsets.only( | ||
| 558 | + top: widget.padding.top, | ||
| 559 | + left: 4.0, | ||
| 560 | + right: widget.padding.left, | ||
| 561 | + ), | ||
| 562 | + child: _getTitleText(), | ||
| 563 | + ) | ||
| 564 | + : _emptyWidget, | ||
| 565 | + Padding( | ||
| 566 | + padding: EdgeInsets.only( | ||
| 567 | + top: _messageTopMargin, | ||
| 568 | + left: 4.0, | ||
| 569 | + right: widget.padding.right, | ||
| 570 | + bottom: widget.padding.bottom, | ||
| 571 | + ), | ||
| 572 | + child: widget.messageText ?? _getDefaultNotificationText(), | ||
| 573 | + ), | ||
| 574 | + ], | ||
| 575 | + ), | ||
| 576 | + ), | ||
| 577 | + ]; | ||
| 578 | + } else if (widget.icon == null && widget.mainButton != null) { | ||
| 579 | + return <Widget>[ | ||
| 580 | + _buildLeftBarIndicator(), | ||
| 581 | + Expanded( | ||
| 582 | + flex: 1, | ||
| 583 | + child: Column( | ||
| 584 | + crossAxisAlignment: CrossAxisAlignment.stretch, | ||
| 585 | + mainAxisSize: MainAxisSize.min, | ||
| 586 | + children: <Widget>[ | ||
| 587 | + (_isTitlePresent) | ||
| 588 | + ? Padding( | ||
| 589 | + padding: EdgeInsets.only( | ||
| 590 | + top: widget.padding.top, | ||
| 591 | + left: widget.padding.left, | ||
| 592 | + right: widget.padding.right, | ||
| 593 | + ), | ||
| 594 | + child: _getTitleText(), | ||
| 595 | + ) | ||
| 596 | + : _emptyWidget, | ||
| 597 | + Padding( | ||
| 598 | + padding: EdgeInsets.only( | ||
| 599 | + top: _messageTopMargin, | ||
| 600 | + left: widget.padding.left, | ||
| 601 | + right: 8.0, | ||
| 602 | + bottom: widget.padding.bottom, | ||
| 603 | + ), | ||
| 604 | + child: widget.messageText ?? _getDefaultNotificationText(), | ||
| 605 | + ), | ||
| 606 | + ], | ||
| 607 | + ), | ||
| 608 | + ), | ||
| 609 | + Padding( | ||
| 610 | + padding: EdgeInsets.only(right: buttonRightPadding), | ||
| 611 | + child: _getMainActionButton(), | ||
| 612 | + ), | ||
| 613 | + ]; | ||
| 614 | + } else { | ||
| 615 | + return <Widget>[ | ||
| 616 | + _buildLeftBarIndicator(), | ||
| 617 | + ConstrainedBox( | ||
| 618 | + constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding), | ||
| 619 | + child: _getIcon(), | ||
| 620 | + ), | ||
| 621 | + Expanded( | ||
| 622 | + flex: 1, | ||
| 623 | + child: Column( | ||
| 624 | + crossAxisAlignment: CrossAxisAlignment.stretch, | ||
| 625 | + mainAxisSize: MainAxisSize.min, | ||
| 626 | + children: <Widget>[ | ||
| 627 | + (_isTitlePresent) | ||
| 628 | + ? Padding( | ||
| 629 | + padding: EdgeInsets.only( | ||
| 630 | + top: widget.padding.top, | ||
| 631 | + left: 4.0, | ||
| 632 | + right: 8.0, | ||
| 633 | + ), | ||
| 634 | + child: _getTitleText(), | ||
| 635 | + ) | ||
| 636 | + : _emptyWidget, | ||
| 637 | + Padding( | ||
| 638 | + padding: EdgeInsets.only( | ||
| 639 | + top: _messageTopMargin, | ||
| 640 | + left: 4.0, | ||
| 641 | + right: 8.0, | ||
| 642 | + bottom: widget.padding.bottom, | ||
| 643 | + ), | ||
| 644 | + child: widget.messageText ?? _getDefaultNotificationText(), | ||
| 645 | + ), | ||
| 646 | + ], | ||
| 647 | + ), | ||
| 648 | + ), | ||
| 649 | + Padding( | ||
| 650 | + padding: EdgeInsets.only(right: buttonRightPadding), | ||
| 651 | + child: _getMainActionButton(), | ||
| 652 | + ) ?? | ||
| 653 | + _emptyWidget, | ||
| 654 | + ]; | ||
| 655 | + } | ||
| 656 | + } | ||
| 657 | + | ||
| 658 | + Widget _buildLeftBarIndicator() { | ||
| 659 | + if (widget.leftBarIndicatorColor != null) { | ||
| 660 | + return FutureBuilder( | ||
| 661 | + future: _boxHeightCompleter.future, | ||
| 662 | + builder: (BuildContext buildContext, AsyncSnapshot<Size> snapshot) { | ||
| 663 | + if (snapshot.hasData) { | ||
| 664 | + return Container( | ||
| 665 | + color: widget.leftBarIndicatorColor, | ||
| 666 | + width: 5.0, | ||
| 667 | + height: snapshot.data.height, | ||
| 668 | + ); | ||
| 669 | + } else { | ||
| 670 | + return _emptyWidget; | ||
| 671 | + } | ||
| 672 | + }, | ||
| 673 | + ); | ||
| 674 | + } else { | ||
| 675 | + return _emptyWidget; | ||
| 676 | + } | ||
| 677 | + } | ||
| 678 | + | ||
| 679 | + Widget _getIcon() { | ||
| 680 | + if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) { | ||
| 681 | + return FadeTransition( | ||
| 682 | + opacity: _fadeAnimation, | ||
| 683 | + child: widget.icon, | ||
| 684 | + ); | ||
| 685 | + } else if (widget.icon != null) { | ||
| 686 | + return widget.icon; | ||
| 687 | + } else { | ||
| 688 | + return _emptyWidget; | ||
| 689 | + } | ||
| 690 | + } | ||
| 691 | + | ||
| 692 | + Widget _getTitleText() { | ||
| 693 | + return widget.titleText != null | ||
| 694 | + ? widget.titleText | ||
| 695 | + : Text( | ||
| 696 | + widget.title ?? "", | ||
| 697 | + style: TextStyle( | ||
| 698 | + fontSize: 16.0, | ||
| 699 | + color: Colors.white, | ||
| 700 | + fontWeight: FontWeight.bold), | ||
| 701 | + ); | ||
| 702 | + } | ||
| 703 | + | ||
| 704 | + Text _getDefaultNotificationText() { | ||
| 705 | + return Text( | ||
| 706 | + widget.message ?? "", | ||
| 707 | + style: TextStyle(fontSize: 14.0, color: Colors.white), | ||
| 708 | + ); | ||
| 709 | + } | ||
| 710 | + | ||
| 711 | + FlatButton _getMainActionButton() { | ||
| 712 | + if (widget.mainButton != null) { | ||
| 713 | + return widget.mainButton; | ||
| 714 | + } else { | ||
| 715 | + return null; | ||
| 716 | + } | ||
| 717 | + } | ||
| 718 | +} | ||
| 719 | + | ||
| 720 | +/// Indicates if snack is going to start at the [TOP] or at the [BOTTOM] | ||
| 721 | +enum SnackPosition { TOP, BOTTOM } | ||
| 722 | + | ||
| 723 | +/// Indicates if snack will be attached to the edge of the screen or not | ||
| 724 | +enum SnackStyle { FLOATING, GROUNDED } | ||
| 725 | + | ||
| 726 | +/// Indicates the direction in which it is possible to dismiss | ||
| 727 | +/// If vertical, dismiss up will be allowed if [SnackPosition.TOP] | ||
| 728 | +/// If vertical, dismiss down will be allowed if [SnackPosition.BOTTOM] | ||
| 729 | +enum SnackDismissDirection { HORIZONTAL, VERTICAL } | ||
| 730 | + | ||
| 731 | +/// Indicates the animation status | ||
| 732 | +/// [SnackStatus.SHOWING] Snack has stopped and the user can see it | ||
| 733 | +/// [SnackStatus.DISMISSED] Snack has finished its mission and returned any pending values | ||
| 734 | +/// [SnackStatus.IS_APPEARING] Snack is moving towards [SnackStatus.SHOWING] | ||
| 735 | +/// [SnackStatus.IS_HIDING] Snack is moving towards [] [SnackStatus.DISMISSED] | ||
| 736 | +enum SnackStatus { SHOWING, DISMISSED, IS_APPEARING, IS_HIDING } |
lib/src/snack_route.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | +import 'dart:ui'; | ||
| 3 | +import 'snack.dart'; | ||
| 4 | +import 'package:flutter/material.dart'; | ||
| 5 | +import 'package:flutter/scheduler.dart'; | ||
| 6 | + | ||
| 7 | +class SnackRoute<T> extends OverlayRoute<T> { | ||
| 8 | + Animation<double> _filterBlurAnimation; | ||
| 9 | + Animation<Color> _filterColorAnimation; | ||
| 10 | + | ||
| 11 | + SnackRoute({ | ||
| 12 | + @required this.snack, | ||
| 13 | + RouteSettings settings, | ||
| 14 | + }) : super(settings: settings) { | ||
| 15 | + this._builder = Builder(builder: (BuildContext innerContext) { | ||
| 16 | + return GestureDetector( | ||
| 17 | + child: snack, | ||
| 18 | + onTap: snack.onTap != null | ||
| 19 | + ? () { | ||
| 20 | + snack.onTap(snack); | ||
| 21 | + } | ||
| 22 | + : null, | ||
| 23 | + ); | ||
| 24 | + }); | ||
| 25 | + | ||
| 26 | + _configureAlignment(this.snack.snackPosition); | ||
| 27 | + _onStatusChanged = snack.onStatusChanged; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + _configureAlignment(SnackPosition snackPosition) { | ||
| 31 | + switch (snack.snackPosition) { | ||
| 32 | + case SnackPosition.TOP: | ||
| 33 | + { | ||
| 34 | + _initialAlignment = Alignment(-1.0, -2.0); | ||
| 35 | + _endAlignment = Alignment(-1.0, -1.0); | ||
| 36 | + break; | ||
| 37 | + } | ||
| 38 | + case SnackPosition.BOTTOM: | ||
| 39 | + { | ||
| 40 | + _initialAlignment = Alignment(-1.0, 2.0); | ||
| 41 | + _endAlignment = Alignment(-1.0, 1.0); | ||
| 42 | + break; | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + GetBar snack; | ||
| 48 | + Builder _builder; | ||
| 49 | + | ||
| 50 | + Future<T> get completed => _transitionCompleter.future; | ||
| 51 | + final Completer<T> _transitionCompleter = Completer<T>(); | ||
| 52 | + | ||
| 53 | + SnackStatusCallback _onStatusChanged; | ||
| 54 | + Alignment _initialAlignment; | ||
| 55 | + Alignment _endAlignment; | ||
| 56 | + bool _wasDismissedBySwipe = false; | ||
| 57 | + | ||
| 58 | + Timer _timer; | ||
| 59 | + | ||
| 60 | + bool get opaque => false; | ||
| 61 | + | ||
| 62 | + @override | ||
| 63 | + Iterable<OverlayEntry> createOverlayEntries() { | ||
| 64 | + List<OverlayEntry> overlays = []; | ||
| 65 | + | ||
| 66 | + if (snack.overlayBlur > 0.0) { | ||
| 67 | + overlays.add( | ||
| 68 | + OverlayEntry( | ||
| 69 | + builder: (BuildContext context) { | ||
| 70 | + return GestureDetector( | ||
| 71 | + onTap: snack.isDismissible ? () => snack.dismiss() : null, | ||
| 72 | + child: AnimatedBuilder( | ||
| 73 | + animation: _filterBlurAnimation, | ||
| 74 | + builder: (context, child) { | ||
| 75 | + return BackdropFilter( | ||
| 76 | + filter: ImageFilter.blur( | ||
| 77 | + sigmaX: _filterBlurAnimation.value, | ||
| 78 | + sigmaY: _filterBlurAnimation.value), | ||
| 79 | + child: Container( | ||
| 80 | + constraints: BoxConstraints.expand(), | ||
| 81 | + color: _filterColorAnimation.value, | ||
| 82 | + ), | ||
| 83 | + ); | ||
| 84 | + }, | ||
| 85 | + ), | ||
| 86 | + ); | ||
| 87 | + }, | ||
| 88 | + maintainState: false, | ||
| 89 | + opaque: opaque), | ||
| 90 | + ); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + overlays.add( | ||
| 94 | + OverlayEntry( | ||
| 95 | + builder: (BuildContext context) { | ||
| 96 | + final Widget annotatedChild = Semantics( | ||
| 97 | + child: AlignTransition( | ||
| 98 | + alignment: _animation, | ||
| 99 | + child: snack.isDismissible | ||
| 100 | + ? _getDismissibleSnack(_builder) | ||
| 101 | + : _getSnack(), | ||
| 102 | + ), | ||
| 103 | + focused: false, | ||
| 104 | + container: true, | ||
| 105 | + explicitChildNodes: true, | ||
| 106 | + ); | ||
| 107 | + return annotatedChild; | ||
| 108 | + }, | ||
| 109 | + maintainState: false, | ||
| 110 | + opaque: opaque), | ||
| 111 | + ); | ||
| 112 | + | ||
| 113 | + return overlays; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /// This string is a workaround until Dismissible supports a returning item | ||
| 117 | + String dismissibleKeyGen = ""; | ||
| 118 | + | ||
| 119 | + Widget _getDismissibleSnack(Widget child) { | ||
| 120 | + return Dismissible( | ||
| 121 | + direction: _getDismissDirection(), | ||
| 122 | + resizeDuration: null, | ||
| 123 | + confirmDismiss: (_) { | ||
| 124 | + if (currentStatus == SnackStatus.IS_APPEARING || | ||
| 125 | + currentStatus == SnackStatus.IS_HIDING) { | ||
| 126 | + return Future.value(false); | ||
| 127 | + } | ||
| 128 | + return Future.value(true); | ||
| 129 | + }, | ||
| 130 | + key: Key(dismissibleKeyGen), | ||
| 131 | + onDismissed: (_) { | ||
| 132 | + dismissibleKeyGen += "1"; | ||
| 133 | + _cancelTimer(); | ||
| 134 | + _wasDismissedBySwipe = true; | ||
| 135 | + | ||
| 136 | + if (isCurrent) { | ||
| 137 | + navigator.pop(); | ||
| 138 | + } else { | ||
| 139 | + navigator.removeRoute(this); | ||
| 140 | + } | ||
| 141 | + }, | ||
| 142 | + child: _getSnack(), | ||
| 143 | + ); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + Widget _getSnack() { | ||
| 147 | + return Container( | ||
| 148 | + margin: snack.margin, | ||
| 149 | + child: _builder, | ||
| 150 | + ); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + DismissDirection _getDismissDirection() { | ||
| 154 | + if (snack.dismissDirection == SnackDismissDirection.HORIZONTAL) { | ||
| 155 | + return DismissDirection.horizontal; | ||
| 156 | + } else { | ||
| 157 | + if (snack.snackPosition == SnackPosition.TOP) { | ||
| 158 | + return DismissDirection.up; | ||
| 159 | + } else { | ||
| 160 | + return DismissDirection.down; | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + @override | ||
| 166 | + bool get finishedWhenPopped => | ||
| 167 | + _controller.status == AnimationStatus.dismissed; | ||
| 168 | + | ||
| 169 | + /// The animation that drives the route's transition and the previous route's | ||
| 170 | + /// forward transition. | ||
| 171 | + Animation<Alignment> get animation => _animation; | ||
| 172 | + Animation<Alignment> _animation; | ||
| 173 | + | ||
| 174 | + /// The animation controller that the route uses to drive the transitions. | ||
| 175 | + /// | ||
| 176 | + /// The animation itself is exposed by the [animation] property. | ||
| 177 | + @protected | ||
| 178 | + AnimationController get controller => _controller; | ||
| 179 | + AnimationController _controller; | ||
| 180 | + | ||
| 181 | + /// Called to create the animation controller that will drive the transitions to | ||
| 182 | + /// this route from the previous one, and back to the previous route from this | ||
| 183 | + /// one. | ||
| 184 | + AnimationController createAnimationController() { | ||
| 185 | + assert(!_transitionCompleter.isCompleted, | ||
| 186 | + 'Cannot reuse a $runtimeType after disposing it.'); | ||
| 187 | + assert(snack.animationDuration != null && | ||
| 188 | + snack.animationDuration >= Duration.zero); | ||
| 189 | + return AnimationController( | ||
| 190 | + duration: snack.animationDuration, | ||
| 191 | + debugLabel: debugLabel, | ||
| 192 | + vsync: navigator, | ||
| 193 | + ); | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + /// Called to create the animation that exposes the current progress of | ||
| 197 | + /// the transition controlled by the animation controller created by | ||
| 198 | + /// [createAnimationController()]. | ||
| 199 | + Animation<Alignment> createAnimation() { | ||
| 200 | + assert(!_transitionCompleter.isCompleted, | ||
| 201 | + 'Cannot reuse a $runtimeType after disposing it.'); | ||
| 202 | + assert(_controller != null); | ||
| 203 | + return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate( | ||
| 204 | + CurvedAnimation( | ||
| 205 | + parent: _controller, | ||
| 206 | + curve: snack.forwardAnimationCurve, | ||
| 207 | + reverseCurve: snack.reverseAnimationCurve, | ||
| 208 | + ), | ||
| 209 | + ); | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + Animation<double> createBlurFilterAnimation() { | ||
| 213 | + return Tween(begin: 0.0, end: snack.overlayBlur).animate( | ||
| 214 | + CurvedAnimation( | ||
| 215 | + parent: _controller, | ||
| 216 | + curve: Interval( | ||
| 217 | + 0.0, | ||
| 218 | + 0.35, | ||
| 219 | + curve: Curves.easeInOutCirc, | ||
| 220 | + ), | ||
| 221 | + ), | ||
| 222 | + ); | ||
| 223 | + } | ||
| 224 | + | ||
| 225 | + Animation<Color> createColorFilterAnimation() { | ||
| 226 | + return ColorTween(begin: Colors.transparent, end: snack.overlayColor) | ||
| 227 | + .animate( | ||
| 228 | + CurvedAnimation( | ||
| 229 | + parent: _controller, | ||
| 230 | + curve: Interval( | ||
| 231 | + 0.0, | ||
| 232 | + 0.35, | ||
| 233 | + curve: Curves.easeInOutCirc, | ||
| 234 | + ), | ||
| 235 | + ), | ||
| 236 | + ); | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + T _result; | ||
| 240 | + SnackStatus currentStatus; | ||
| 241 | + | ||
| 242 | + //copy of `routes.dart` | ||
| 243 | + void _handleStatusChanged(AnimationStatus status) { | ||
| 244 | + switch (status) { | ||
| 245 | + case AnimationStatus.completed: | ||
| 246 | + currentStatus = SnackStatus.SHOWING; | ||
| 247 | + _onStatusChanged(currentStatus); | ||
| 248 | + if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = opaque; | ||
| 249 | + | ||
| 250 | + break; | ||
| 251 | + case AnimationStatus.forward: | ||
| 252 | + currentStatus = SnackStatus.IS_APPEARING; | ||
| 253 | + _onStatusChanged(currentStatus); | ||
| 254 | + break; | ||
| 255 | + case AnimationStatus.reverse: | ||
| 256 | + currentStatus = SnackStatus.IS_HIDING; | ||
| 257 | + _onStatusChanged(currentStatus); | ||
| 258 | + if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = false; | ||
| 259 | + break; | ||
| 260 | + case AnimationStatus.dismissed: | ||
| 261 | + assert(!overlayEntries.first.opaque); | ||
| 262 | + // We might still be the current route if a subclass is controlling the | ||
| 263 | + // the transition and hits the dismissed status. For example, the iOS | ||
| 264 | + // back gesture drives this animation to the dismissed status before | ||
| 265 | + // popping the navigator. | ||
| 266 | + currentStatus = SnackStatus.DISMISSED; | ||
| 267 | + _onStatusChanged(currentStatus); | ||
| 268 | + | ||
| 269 | + if (!isCurrent) { | ||
| 270 | + navigator.finalizeRoute(this); | ||
| 271 | + assert(overlayEntries.isEmpty); | ||
| 272 | + } | ||
| 273 | + break; | ||
| 274 | + } | ||
| 275 | + changedInternalState(); | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + @override | ||
| 279 | + void install(OverlayEntry insertionPoint) { | ||
| 280 | + assert(!_transitionCompleter.isCompleted, | ||
| 281 | + 'Cannot install a $runtimeType after disposing it.'); | ||
| 282 | + _controller = createAnimationController(); | ||
| 283 | + assert(_controller != null, | ||
| 284 | + '$runtimeType.createAnimationController() returned null.'); | ||
| 285 | + _filterBlurAnimation = createBlurFilterAnimation(); | ||
| 286 | + _filterColorAnimation = createColorFilterAnimation(); | ||
| 287 | + _animation = createAnimation(); | ||
| 288 | + assert(_animation != null, '$runtimeType.createAnimation() returned null.'); | ||
| 289 | + super.install(insertionPoint); | ||
| 290 | + } | ||
| 291 | + | ||
| 292 | + @override | ||
| 293 | + TickerFuture didPush() { | ||
| 294 | + super.didPush(); | ||
| 295 | + assert(_controller != null, | ||
| 296 | + '$runtimeType.didPush called before calling install() or after calling dispose().'); | ||
| 297 | + assert(!_transitionCompleter.isCompleted, | ||
| 298 | + 'Cannot reuse a $runtimeType after disposing it.'); | ||
| 299 | + _animation.addStatusListener(_handleStatusChanged); | ||
| 300 | + _configureTimer(); | ||
| 301 | + return _controller.forward(); | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + @override | ||
| 305 | + void didReplace(Route<dynamic> oldRoute) { | ||
| 306 | + assert(_controller != null, | ||
| 307 | + '$runtimeType.didReplace called before calling install() or after calling dispose().'); | ||
| 308 | + assert(!_transitionCompleter.isCompleted, | ||
| 309 | + 'Cannot reuse a $runtimeType after disposing it.'); | ||
| 310 | + if (oldRoute is SnackRoute) _controller.value = oldRoute._controller.value; | ||
| 311 | + _animation.addStatusListener(_handleStatusChanged); | ||
| 312 | + super.didReplace(oldRoute); | ||
| 313 | + } | ||
| 314 | + | ||
| 315 | + @override | ||
| 316 | + bool didPop(T result) { | ||
| 317 | + assert(_controller != null, | ||
| 318 | + '$runtimeType.didPop called before calling install() or after calling dispose().'); | ||
| 319 | + assert(!_transitionCompleter.isCompleted, | ||
| 320 | + 'Cannot reuse a $runtimeType after disposing it.'); | ||
| 321 | + | ||
| 322 | + _result = result; | ||
| 323 | + _cancelTimer(); | ||
| 324 | + | ||
| 325 | + if (_wasDismissedBySwipe) { | ||
| 326 | + Timer(Duration(milliseconds: 200), () { | ||
| 327 | + _controller.reset(); | ||
| 328 | + }); | ||
| 329 | + | ||
| 330 | + _wasDismissedBySwipe = false; | ||
| 331 | + } else { | ||
| 332 | + _controller.reverse(); | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + return super.didPop(result); | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + void _configureTimer() { | ||
| 339 | + if (snack.duration != null) { | ||
| 340 | + if (_timer != null && _timer.isActive) { | ||
| 341 | + _timer.cancel(); | ||
| 342 | + } | ||
| 343 | + _timer = Timer(snack.duration, () { | ||
| 344 | + if (this.isCurrent) { | ||
| 345 | + navigator.pop(); | ||
| 346 | + } else if (this.isActive) { | ||
| 347 | + navigator.removeRoute(this); | ||
| 348 | + } | ||
| 349 | + }); | ||
| 350 | + } else { | ||
| 351 | + if (_timer != null) { | ||
| 352 | + _timer.cancel(); | ||
| 353 | + } | ||
| 354 | + } | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + void _cancelTimer() { | ||
| 358 | + if (_timer != null && _timer.isActive) { | ||
| 359 | + _timer.cancel(); | ||
| 360 | + } | ||
| 361 | + } | ||
| 362 | + | ||
| 363 | + /// Whether this route can perform a transition to the given route. | ||
| 364 | + /// Subclasses can override this method to restrict the set of routes they | ||
| 365 | + /// need to coordinate transitions with. | ||
| 366 | + bool canTransitionTo(SnackRoute<dynamic> nextRoute) => true; | ||
| 367 | + | ||
| 368 | + /// Whether this route can perform a transition from the given route. | ||
| 369 | + /// | ||
| 370 | + /// Subclasses can override this method to restrict the set of routes they | ||
| 371 | + /// need to coordinate transitions with. | ||
| 372 | + bool canTransitionFrom(SnackRoute<dynamic> previousRoute) => true; | ||
| 373 | + | ||
| 374 | + @override | ||
| 375 | + void dispose() { | ||
| 376 | + assert(!_transitionCompleter.isCompleted, | ||
| 377 | + 'Cannot dispose a $runtimeType twice.'); | ||
| 378 | + _controller?.dispose(); | ||
| 379 | + _transitionCompleter.complete(_result); | ||
| 380 | + super.dispose(); | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + /// A short description of this route useful for debugging. | ||
| 384 | + String get debugLabel => '$runtimeType'; | ||
| 385 | + | ||
| 386 | + @override | ||
| 387 | + String toString() => '$runtimeType(animation: $_controller)'; | ||
| 388 | +} | ||
| 389 | + | ||
| 390 | +SnackRoute showSnack<T>({@required GetBar snack}) { | ||
| 391 | + assert(snack != null); | ||
| 392 | + | ||
| 393 | + return SnackRoute<T>( | ||
| 394 | + snack: snack, | ||
| 395 | + settings: RouteSettings(name: "getroute"), | ||
| 396 | + ); | ||
| 397 | +} |
pubspec.lock
0 → 100644
| 1 | +# Generated by pub | ||
| 2 | +# See https://dart.dev/tools/pub/glossary#lockfile | ||
| 3 | +packages: | ||
| 4 | + archive: | ||
| 5 | + dependency: transitive | ||
| 6 | + description: | ||
| 7 | + name: archive | ||
| 8 | + url: "https://pub.dartlang.org" | ||
| 9 | + source: hosted | ||
| 10 | + version: "2.0.11" | ||
| 11 | + args: | ||
| 12 | + dependency: transitive | ||
| 13 | + description: | ||
| 14 | + name: args | ||
| 15 | + url: "https://pub.dartlang.org" | ||
| 16 | + source: hosted | ||
| 17 | + version: "1.5.2" | ||
| 18 | + async: | ||
| 19 | + dependency: transitive | ||
| 20 | + description: | ||
| 21 | + name: async | ||
| 22 | + url: "https://pub.dartlang.org" | ||
| 23 | + source: hosted | ||
| 24 | + version: "2.4.0" | ||
| 25 | + boolean_selector: | ||
| 26 | + dependency: transitive | ||
| 27 | + description: | ||
| 28 | + name: boolean_selector | ||
| 29 | + url: "https://pub.dartlang.org" | ||
| 30 | + source: hosted | ||
| 31 | + version: "1.0.5" | ||
| 32 | + charcode: | ||
| 33 | + dependency: transitive | ||
| 34 | + description: | ||
| 35 | + name: charcode | ||
| 36 | + url: "https://pub.dartlang.org" | ||
| 37 | + source: hosted | ||
| 38 | + version: "1.1.2" | ||
| 39 | + collection: | ||
| 40 | + dependency: transitive | ||
| 41 | + description: | ||
| 42 | + name: collection | ||
| 43 | + url: "https://pub.dartlang.org" | ||
| 44 | + source: hosted | ||
| 45 | + version: "1.14.11" | ||
| 46 | + convert: | ||
| 47 | + dependency: transitive | ||
| 48 | + description: | ||
| 49 | + name: convert | ||
| 50 | + url: "https://pub.dartlang.org" | ||
| 51 | + source: hosted | ||
| 52 | + version: "2.1.1" | ||
| 53 | + crypto: | ||
| 54 | + dependency: transitive | ||
| 55 | + description: | ||
| 56 | + name: crypto | ||
| 57 | + url: "https://pub.dartlang.org" | ||
| 58 | + source: hosted | ||
| 59 | + version: "2.1.3" | ||
| 60 | + flutter: | ||
| 61 | + dependency: "direct main" | ||
| 62 | + description: flutter | ||
| 63 | + source: sdk | ||
| 64 | + version: "0.0.0" | ||
| 65 | + flutter_test: | ||
| 66 | + dependency: "direct dev" | ||
| 67 | + description: flutter | ||
| 68 | + source: sdk | ||
| 69 | + version: "0.0.0" | ||
| 70 | + image: | ||
| 71 | + dependency: transitive | ||
| 72 | + description: | ||
| 73 | + name: image | ||
| 74 | + url: "https://pub.dartlang.org" | ||
| 75 | + source: hosted | ||
| 76 | + version: "2.1.4" | ||
| 77 | + matcher: | ||
| 78 | + dependency: transitive | ||
| 79 | + description: | ||
| 80 | + name: matcher | ||
| 81 | + url: "https://pub.dartlang.org" | ||
| 82 | + source: hosted | ||
| 83 | + version: "0.12.6" | ||
| 84 | + meta: | ||
| 85 | + dependency: transitive | ||
| 86 | + description: | ||
| 87 | + name: meta | ||
| 88 | + url: "https://pub.dartlang.org" | ||
| 89 | + source: hosted | ||
| 90 | + version: "1.1.8" | ||
| 91 | + path: | ||
| 92 | + dependency: transitive | ||
| 93 | + description: | ||
| 94 | + name: path | ||
| 95 | + url: "https://pub.dartlang.org" | ||
| 96 | + source: hosted | ||
| 97 | + version: "1.6.4" | ||
| 98 | + pedantic: | ||
| 99 | + dependency: transitive | ||
| 100 | + description: | ||
| 101 | + name: pedantic | ||
| 102 | + url: "https://pub.dartlang.org" | ||
| 103 | + source: hosted | ||
| 104 | + version: "1.8.0+1" | ||
| 105 | + petitparser: | ||
| 106 | + dependency: transitive | ||
| 107 | + description: | ||
| 108 | + name: petitparser | ||
| 109 | + url: "https://pub.dartlang.org" | ||
| 110 | + source: hosted | ||
| 111 | + version: "2.4.0" | ||
| 112 | + quiver: | ||
| 113 | + dependency: transitive | ||
| 114 | + description: | ||
| 115 | + name: quiver | ||
| 116 | + url: "https://pub.dartlang.org" | ||
| 117 | + source: hosted | ||
| 118 | + version: "2.0.5" | ||
| 119 | + sky_engine: | ||
| 120 | + dependency: transitive | ||
| 121 | + description: flutter | ||
| 122 | + source: sdk | ||
| 123 | + version: "0.0.99" | ||
| 124 | + source_span: | ||
| 125 | + dependency: transitive | ||
| 126 | + description: | ||
| 127 | + name: source_span | ||
| 128 | + url: "https://pub.dartlang.org" | ||
| 129 | + source: hosted | ||
| 130 | + version: "1.5.5" | ||
| 131 | + stack_trace: | ||
| 132 | + dependency: transitive | ||
| 133 | + description: | ||
| 134 | + name: stack_trace | ||
| 135 | + url: "https://pub.dartlang.org" | ||
| 136 | + source: hosted | ||
| 137 | + version: "1.9.3" | ||
| 138 | + stream_channel: | ||
| 139 | + dependency: transitive | ||
| 140 | + description: | ||
| 141 | + name: stream_channel | ||
| 142 | + url: "https://pub.dartlang.org" | ||
| 143 | + source: hosted | ||
| 144 | + version: "2.0.0" | ||
| 145 | + string_scanner: | ||
| 146 | + dependency: transitive | ||
| 147 | + description: | ||
| 148 | + name: string_scanner | ||
| 149 | + url: "https://pub.dartlang.org" | ||
| 150 | + source: hosted | ||
| 151 | + version: "1.0.5" | ||
| 152 | + term_glyph: | ||
| 153 | + dependency: transitive | ||
| 154 | + description: | ||
| 155 | + name: term_glyph | ||
| 156 | + url: "https://pub.dartlang.org" | ||
| 157 | + source: hosted | ||
| 158 | + version: "1.1.0" | ||
| 159 | + test_api: | ||
| 160 | + dependency: transitive | ||
| 161 | + description: | ||
| 162 | + name: test_api | ||
| 163 | + url: "https://pub.dartlang.org" | ||
| 164 | + source: hosted | ||
| 165 | + version: "0.2.11" | ||
| 166 | + typed_data: | ||
| 167 | + dependency: transitive | ||
| 168 | + description: | ||
| 169 | + name: typed_data | ||
| 170 | + url: "https://pub.dartlang.org" | ||
| 171 | + source: hosted | ||
| 172 | + version: "1.1.6" | ||
| 173 | + vector_math: | ||
| 174 | + dependency: transitive | ||
| 175 | + description: | ||
| 176 | + name: vector_math | ||
| 177 | + url: "https://pub.dartlang.org" | ||
| 178 | + source: hosted | ||
| 179 | + version: "2.0.8" | ||
| 180 | + xml: | ||
| 181 | + dependency: transitive | ||
| 182 | + description: | ||
| 183 | + name: xml | ||
| 184 | + url: "https://pub.dartlang.org" | ||
| 185 | + source: hosted | ||
| 186 | + version: "3.5.0" | ||
| 187 | +sdks: | ||
| 188 | + dart: ">=2.4.0 <3.0.0" |
| 1 | name: get | 1 | name: get |
| 2 | -description: A consistent Flutter route navigation library that navigate with no context and not rebuild materialApp with each navigation. | ||
| 3 | -version: 1.2.1 | 2 | +description: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars with no context. |
| 3 | +version: 1.6.3 | ||
| 4 | author: Jonny Borges <jonataborges01@gmail.com> | 4 | author: Jonny Borges <jonataborges01@gmail.com> |
| 5 | homepage: https://github.com/jonataslaw/get | 5 | homepage: https://github.com/jonataslaw/get |
| 6 | 6 |
-
Please register or login to post a comment