Showing
11 changed files
with
1540 additions
and
21 deletions
@@ -25,3 +25,63 @@ | @@ -25,3 +25,63 @@ | ||
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({ |
@@ -43,8 +45,8 @@ class GetRoute<T> extends PageRoute<T> { | @@ -43,8 +45,8 @@ 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 |
@@ -57,7 +59,8 @@ class GetRoute<T> extends PageRoute<T> { | @@ -57,7 +59,8 @@ class GetRoute<T> extends PageRoute<T> { | ||
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