Jonny Borges
Committed by GitHub

Add files via upload

... ... @@ -25,3 +25,63 @@
## [1.2.1]
- Fix bug currentState = null
## [1.3.0]
- Update docs, readme, and add full support to flutter_web
## [1.3.1]
- Update docs
## [1.3.2]
- Improve performance
## [1.3.3]
- Fix Get.back arguments
## [1.3.4]
- Improve performance
## [1.4.0]
- Added Get.removeRoute // remove one route.
Get.until // back repeatedly until the predicate returns true.
Get.offUntil // go to next route and remove all the previous routes until the predicate returns true.
Get.offNamedUntil // go to next named route and remove all the previous routes until the predicate returns true.
## [1.4.0+6]
- Improve performance and bug fix
## [1.4.0+7]
- Add more documentation
## [1.5.0]
- Add support to dialogs
## [1.5.0+1]
- Add color and opacity to dialogs
## [1.6.0]
- Add support to snackbars
## [1.6.1]
- Add docs and improve performance
## [1.6.2]
- Fix bugs on blurred Snackbars
## [1.6.3]
- Clean code.
\ No newline at end of file
... ...
# Get
A consistent Flutter route navigation library that navigate with no context and not rebuild materialApp with each navigation.
A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars from anywhere in your code without context.
## Getting Started
Flutter's conventional navigation method has a route reconstruction bug that makes it inconsistent
for large applications with undefined routes.
Get came to solve this problem.
In addition, Get needs no context, also solving Flutter's biggest problem with patterns like BLoC.
Get also makes navigation much clearer and more concise for beginners and friendly to those who came from Web programming.
Flutter's conventional navigation has a lot of unnecessary boilerplate, requires context to navigate between screens, open dialogs, and snacking is really painful.
In addition, with each route navigation, all of your screens below MaterialApp are rebuilt, often causing RAM and CPU bottlenecks.
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.
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,
increasing your productivity, and eliminating all the bugs present in Flutter's default navigation altogether.
## How to use?
... ... @@ -16,7 +15,7 @@ Add this to your package's pubspec.yaml file:
```
dependencies:
get: ^1.4.0
get: ^1.6.3
```
And import it:
... ... @@ -69,6 +68,41 @@ ex:
```dart
if(data == 'sucess') madeAnything();
```
To open dialog:
```dart
Get.dialog(YourDialogWidget());
```
To open default dialog:
```dart
Get.defaultDialog(
title: "My Title",
content: Text("Hi, it's my dialog"),
confirm: FlatButton(
child: Text("Ok"),
onPressed: () => print("OK pressed"),
),
cancel: FlatButton(
child: Text("Cancel"),
onPressed: () => Get.back(),
));
```
To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold,
but with Get, all you have to do is call your SnackBar from anywhere in your code! No context, no cliche code!
```dart
GetBar(
title: "Hey i'm a Get SnackBar!",
message:
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!",
duration: Duration(seconds: 3),
)..show();
```
Plus, the default SnackBar is completely inflexible, while GetBar lets you change the color, shape, opacity, and anything else you want!
Others methods:
Get.removeRoute // remove one route.
... ... @@ -154,6 +188,17 @@ class FirstRoute extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
GetBar(
title: "Hey i'm a Get SnackBar!",
message:
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!",
duration: Duration(seconds: 3),
)..show();
},
),
title: Text('First Route'),
),
body: Center(
... ...
... ... @@ -7,7 +7,7 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.10"
version: "2.0.11"
args:
dependency: transitive
description:
... ... @@ -21,7 +21,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "2.4.0"
boolean_selector:
dependency: transitive
description:
... ... @@ -94,14 +94,14 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.5"
version: "0.12.6"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.7"
version: "1.1.8"
path:
dependency: transitive
description:
... ... @@ -176,7 +176,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.5"
version: "0.2.11"
typed_data:
dependency: transitive
description:
... ...
... ... @@ -2,4 +2,5 @@ library get;
export 'src/getroute.dart';
export 'src/routes.dart';
export 'src/snack.dart';
export 'src/snack_route.dart';
... ...
import 'package:flutter/material.dart';
import 'package:get/src/routes.dart';
class DialogGet extends StatelessWidget {
final Widget child;
final color;
final double opacity;
const DialogGet({Key key, this.child, this.color, this.opacity = 0.5})
: super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Get.back(),
child: Container(
color: (color == null)
? Theme.of(context).accentColor.withOpacity(opacity)
: color,
child: GestureDetector(onTap: () => null, child: child),
),
);
}
}
class DefaultDialogGet extends StatelessWidget {
final color;
final double opacity;
final String title;
final Widget content;
final Widget cancel;
final Widget confirm;
const DefaultDialogGet(
{Key key,
this.color,
this.opacity = 0.5,
this.title,
this.content,
this.cancel,
this.confirm})
: super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Get.back(),
child: Container(
color: (color == null)
? Theme.of(context).accentColor.withOpacity(opacity)
: color,
child: GestureDetector(
onTap: () => null,
child: AlertDialog(
title: Text(title, textAlign: TextAlign.center),
content: content,
actions: <Widget>[cancel, confirm],
),
),
),
);
}
}
... ...
... ... @@ -2,6 +2,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class GetRoute<T> extends PageRoute<T> {
/// Construct a MaterialPageRoute whose contents are defined by [builder].
///
/// The values of [builder], [maintainState], and [fullScreenDialog] must not
/// be null.
GetRoute({
... ... @@ -43,8 +45,8 @@ class GetRoute<T> extends PageRoute<T> {
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a fullscreen dialog.
return (nextRoute is GetRoute && !nextRoute.fullscreenDialog)
|| (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog);
return (nextRoute is GetRoute && !nextRoute.fullscreenDialog) ||
(nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog);
}
@override
... ... @@ -57,7 +59,8 @@ class GetRoute<T> extends PageRoute<T> {
assert(() {
if (result == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('The builder for route "${settings.name}" returned null.'),
ErrorSummary(
'The builder for route "${settings.name}" returned null.'),
ErrorDescription('Route builders must never return null.')
]);
}
... ... @@ -71,9 +74,11 @@ class GetRoute<T> extends PageRoute<T> {
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
return theme.buildTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
@override
... ...
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'dialog.dart';
import 'getroute.dart';
class Get {
... ... @@ -87,6 +89,28 @@ class Get {
.pushReplacement(GetRoute(opaque: rebuildRoutes, builder: (_) => page));
}
/// Show a dialog. You can choose color and opacity of background
static dialog(Widget page, {Color color, double opacity = 0.5}) {
Get.to(DialogGet(child: page, color: color, opacity: opacity));
}
static defaultDialog(
{Color color,
double opacity = 0.5,
String title = "Alert dialog",
Widget content,
Widget cancel,
Widget confirm}) {
Get.to(DefaultDialogGet(
color: color,
opacity: opacity,
title: title,
content: content,
cancel: cancel,
confirm: confirm,
));
}
/// It replaces Navigator.pushAndRemoveUntil, but needs no context
static offAll(Widget page, RoutePredicate predicate,
{bool rebuildRoutes = false}) {
... ...
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:get/get.dart';
import 'snack_route.dart' as route;
typedef void SnackStatusCallback(SnackStatus status);
typedef void OnTap(GetBar snack);
// ignore: must_be_immutable
class GetBar<T extends Object> extends StatefulWidget {
GetBar(
{Key key,
String title,
String message,
Widget titleText,
Widget messageText,
Widget icon,
bool shouldIconPulse = true,
double maxWidth,
EdgeInsets margin = const EdgeInsets.all(0.0),
EdgeInsets padding = const EdgeInsets.all(16),
double borderRadius = 0.0,
Color borderColor,
double borderWidth = 1.0,
Color backgroundColor = const Color(0xFF303030),
Color leftBarIndicatorColor,
List<BoxShadow> boxShadows,
Gradient backgroundGradient,
FlatButton mainButton,
OnTap onTap,
Duration duration,
bool isDismissible = true,
SnackDismissDirection dismissDirection = SnackDismissDirection.VERTICAL,
bool showProgressIndicator = false,
AnimationController progressIndicatorController,
Color progressIndicatorBackgroundColor,
Animation<Color> progressIndicatorValueColor,
SnackPosition snackPosition = SnackPosition.BOTTOM,
SnackStyle snackStyle = SnackStyle.FLOATING,
Curve forwardAnimationCurve = Curves.easeOutCirc,
Curve reverseAnimationCurve = Curves.easeOutCirc,
Duration animationDuration = const Duration(seconds: 1),
SnackStatusCallback onStatusChanged,
double barBlur = 0.0,
double overlayBlur = 0.0,
Color overlayColor = Colors.transparent,
Form userInputForm})
: this.title = title,
this.message = message,
this.titleText = titleText,
this.messageText = messageText,
this.icon = icon,
this.shouldIconPulse = shouldIconPulse,
this.maxWidth = maxWidth,
this.margin = margin,
this.padding = padding,
this.borderRadius = borderRadius,
this.borderColor = borderColor,
this.borderWidth = borderWidth,
this.backgroundColor = backgroundColor,
this.leftBarIndicatorColor = leftBarIndicatorColor,
this.boxShadows = boxShadows,
this.backgroundGradient = backgroundGradient,
this.mainButton = mainButton,
this.onTap = onTap,
this.duration = duration,
this.isDismissible = isDismissible,
this.dismissDirection = dismissDirection,
this.showProgressIndicator = showProgressIndicator,
this.progressIndicatorController = progressIndicatorController,
this.progressIndicatorBackgroundColor =
progressIndicatorBackgroundColor,
this.progressIndicatorValueColor = progressIndicatorValueColor,
this.snackPosition = snackPosition,
this.snackStyle = snackStyle,
this.forwardAnimationCurve = forwardAnimationCurve,
this.reverseAnimationCurve = reverseAnimationCurve,
this.animationDuration = animationDuration,
this.barBlur = barBlur,
this.overlayBlur = overlayBlur,
this.overlayColor = overlayColor,
this.userInputForm = userInputForm,
super(key: key) {
this.onStatusChanged = onStatusChanged ?? (status) {};
}
/// A callback for you to listen to the different Snack status
SnackStatusCallback onStatusChanged;
/// The title displayed to the user
final String title;
/// The message displayed to the user.
final String message;
/// Replaces [title]. Although this accepts a [Widget], it is meant to receive [Text] or [RichText]
final Widget titleText;
/// Replaces [message]. Although this accepts a [Widget], it is meant to receive [Text] or [RichText]
final Widget messageText;
/// Will be ignored if [backgroundGradient] is not null
final Color backgroundColor;
/// If not null, shows a left vertical colored bar on notification.
/// It is not possible to use it with a [Form] and I do not recommend using it with [LinearProgressIndicator]
final Color leftBarIndicatorColor;
/// [boxShadows] The shadows generated by Snack. Leave it null if you don't want a shadow.
/// You can use more than one if you feel the need.
/// Check (this example)[https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/shadows.dart]
final List<BoxShadow> boxShadows;
/// Makes [backgroundColor] be ignored.
final Gradient backgroundGradient;
/// You can use any widget here, but I recommend [Icon] or [Image] as indication of what kind
/// of message you are displaying. Other widgets may break the layout
final Widget icon;
/// An option to animate the icon (if present). Defaults to true.
final bool shouldIconPulse;
/// A [FlatButton] widget if you need an action from the user.
final FlatButton mainButton;
/// A callback that registers the user's click anywhere. An alternative to [mainButton]
final OnTap onTap;
/// How long until Snack will hide itself (be dismissed). To make it indefinite, leave it null.
final Duration duration;
/// True if you want to show a [LinearProgressIndicator].
final bool showProgressIndicator;
/// An optional [AnimationController] when you want to control the progress of your [LinearProgressIndicator].
final AnimationController progressIndicatorController;
/// A [LinearProgressIndicator] configuration parameter.
final Color progressIndicatorBackgroundColor;
/// A [LinearProgressIndicator] configuration parameter.
final Animation<Color> progressIndicatorValueColor;
/// Determines if the user can swipe or click the overlay (if [overlayBlur] > 0) to dismiss.
/// It is recommended that you set [duration] != null if this is false.
/// If the user swipes to dismiss or clicks the overlay, no value will be returned.
final bool isDismissible;
/// Used to limit Snack width (usually on large screens)
final double maxWidth;
/// Adds a custom margin to Snack
final EdgeInsets margin;
/// Adds a custom padding to Snack
/// The default follows material design guide line
final EdgeInsets padding;
/// Adds a radius to all corners of Snack. Best combined with [margin].
/// I do not recommend using it with [showProgressIndicator] or [leftBarIndicatorColor].
final double borderRadius;
/// Adds a border to every side of Snack
/// I do not recommend using it with [showProgressIndicator] or [leftBarIndicatorColor].
final Color borderColor;
/// Changes the width of the border if [borderColor] is specified
final double borderWidth;
/// Snack can be based on [SnackPosition.TOP] or on [SnackPosition.BOTTOM] of your screen.
/// [SnackPosition.BOTTOM] is the default.
final SnackPosition snackPosition;
/// [SnackDismissDirection.VERTICAL] by default.
/// Can also be [SnackDismissDirection.HORIZONTAL] in which case both left and right dismiss are allowed.
final SnackDismissDirection dismissDirection;
/// Snack can be floating or be grounded to the edge of the screen.
/// If grounded, I do not recommend using [margin] or [borderRadius]. [SnackStyle.FLOATING] is the default
/// If grounded, I do not recommend using a [backgroundColor] with transparency or [barBlur]
final SnackStyle snackStyle;
/// The [Curve] animation used when show() is called. [Curves.easeOut] is default
final Curve forwardAnimationCurve;
/// The [Curve] animation used when dismiss() is called. [Curves.fastOutSlowIn] is default
final Curve reverseAnimationCurve;
/// Use it to speed up or slow down the animation duration
final Duration animationDuration;
/// Default is 0.0. If different than 0.0, blurs only Snack's background.
/// To take effect, make sure your [backgroundColor] has some opacity.
/// The greater the value, the greater the blur.
final double barBlur;
/// Default is 0.0. If different than 0.0, creates a blurred
/// overlay that prevents the user from interacting with the screen.
/// The greater the value, the greater the blur.
final double overlayBlur;
/// Default is [Colors.transparent]. Only takes effect if [overlayBlur] > 0.0.
/// Make sure you use a color with transparency here e.g. Colors.grey[600].withOpacity(0.2).
final Color overlayColor;
/// A [TextFormField] in case you want a simple user input. Every other widget is ignored if this is not null.
final Form userInputForm;
route.SnackRoute<T> _snackRoute;
/// Show the snack. Kicks in [SnackStatus.IS_APPEARING] state followed by [SnackStatus.SHOWING]
Future<T> show() async {
_snackRoute = route.showSnack<T>(
snack: this,
);
return await Get.key.currentState.push(_snackRoute);
}
/// Dismisses the snack causing is to return a future containing [result].
/// When this future finishes, it is guaranteed that Snack was dismissed.
Future<T> dismiss([T result]) async {
// If route was never initialized, do nothing
if (_snackRoute == null) {
return null;
}
if (_snackRoute.isCurrent) {
_snackRoute.navigator.pop(result);
return _snackRoute.completed;
} else if (_snackRoute.isActive) {
// removeRoute is called every time you dismiss a Snack that is not the top route.
// It will not animate back and listeners will not detect SnackStatus.IS_HIDING or SnackStatus.DISMISSED
// To avoid this, always make sure that Snack is the top route when it is being dismissed
_snackRoute.navigator.removeRoute(_snackRoute);
}
return null;
}
/// Checks if the snack is visible
bool isShowing() {
return _snackRoute?.currentStatus == SnackStatus.SHOWING;
}
/// Checks if the snack is dismissed
bool isDismissed() {
return _snackRoute?.currentStatus == SnackStatus.DISMISSED;
}
@override
State createState() {
return _GetBarState<T>();
}
}
class _GetBarState<K extends Object> extends State<GetBar>
with TickerProviderStateMixin {
SnackStatus currentStatus;
AnimationController _fadeController;
Animation<double> _fadeAnimation;
final Widget _emptyWidget = SizedBox(width: 0.0, height: 0.0);
final double _initialOpacity = 1.0;
final double _finalOpacity = 0.4;
final Duration _pulseAnimationDuration = Duration(seconds: 1);
bool _isTitlePresent;
double _messageTopMargin;
FocusScopeNode _focusNode;
FocusAttachment _focusAttachment;
@override
void initState() {
super.initState();
assert(
((widget.userInputForm != null ||
((widget.message != null && widget.message.isNotEmpty) ||
widget.messageText != null))),
"A message is mandatory if you are not using userInputForm. Set either a message or messageText");
_isTitlePresent = (widget.title != null || widget.titleText != null);
_messageTopMargin = _isTitlePresent ? 6.0 : widget.padding.top;
_configureLeftBarFuture();
_configureProgressIndicatorAnimation();
if (widget.icon != null && widget.shouldIconPulse) {
_configurePulseAnimation();
_fadeController?.forward();
}
_focusNode = FocusScopeNode();
_focusAttachment = _focusNode.attach(context);
}
@override
void dispose() {
_fadeController?.dispose();
widget.progressIndicatorController?.removeListener(_progressListener);
widget.progressIndicatorController?.dispose();
_focusAttachment.detach();
_focusNode.dispose();
super.dispose();
}
final Completer<Size> _boxHeightCompleter = Completer<Size>();
void _configureLeftBarFuture() {
SchedulerBinding.instance.addPostFrameCallback(
(_) {
final keyContext = backgroundBoxKey.currentContext;
if (keyContext != null) {
final RenderBox box = keyContext.findRenderObject();
_boxHeightCompleter.complete(box.size);
}
},
);
}
void _configurePulseAnimation() {
_fadeController =
AnimationController(vsync: this, duration: _pulseAnimationDuration);
_fadeAnimation = Tween(begin: _initialOpacity, end: _finalOpacity).animate(
CurvedAnimation(
parent: _fadeController,
curve: Curves.linear,
),
);
_fadeController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_fadeController.reverse();
}
if (status == AnimationStatus.dismissed) {
_fadeController.forward();
}
});
_fadeController.forward();
}
Function _progressListener;
void _configureProgressIndicatorAnimation() {
if (widget.showProgressIndicator &&
widget.progressIndicatorController != null) {
_progressListener = () {
setState(() {});
};
widget.progressIndicatorController.addListener(_progressListener);
_progressAnimation = CurvedAnimation(
curve: Curves.linear, parent: widget.progressIndicatorController);
}
}
@override
Widget build(BuildContext context) {
return Align(
heightFactor: 1.0,
child: Material(
color: widget.snackStyle == SnackStyle.FLOATING
? Colors.transparent
: widget.backgroundColor,
child: SafeArea(
minimum: widget.snackPosition == SnackPosition.BOTTOM
? EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom)
: EdgeInsets.only(top: MediaQuery.of(context).viewInsets.top),
bottom: widget.snackPosition == SnackPosition.BOTTOM,
top: widget.snackPosition == SnackPosition.TOP,
left: false,
right: false,
child: _getSnack(),
),
),
);
}
Widget _getSnack() {
Widget snack;
if (widget.userInputForm != null) {
snack = _generateInputSnack();
} else {
snack = _generateSnack();
}
return Stack(
children: [
FutureBuilder(
future: _boxHeightCompleter.future,
builder: (context, AsyncSnapshot<Size> snapshot) {
if (snapshot.hasData) {
return ClipRRect(
borderRadius: BorderRadius.circular(widget.borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: widget.barBlur, sigmaY: widget.barBlur),
child: Container(
height: snapshot.data.height,
width: snapshot.data.width,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(widget.borderRadius),
),
),
),
);
} else {
return _emptyWidget;
}
},
),
snack,
],
);
}
Widget _generateInputSnack() {
return Container(
key: backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth)
: null,
decoration: BoxDecoration(
color: widget.backgroundColor,
gradient: widget.backgroundGradient,
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(color: widget.borderColor, width: widget.borderWidth)
: null,
),
child: Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0, bottom: 8.0, top: 16.0),
child: FocusScope(
child: widget.userInputForm,
node: _focusNode,
autofocus: true,
),
),
);
}
CurvedAnimation _progressAnimation;
GlobalKey backgroundBoxKey = GlobalKey();
Widget _generateSnack() {
return Container(
key: backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth)
: null,
decoration: BoxDecoration(
color: widget.backgroundColor,
gradient: widget.backgroundGradient,
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(color: widget.borderColor, width: widget.borderWidth)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
widget.showProgressIndicator
? LinearProgressIndicator(
value: widget.progressIndicatorController != null
? _progressAnimation.value
: null,
backgroundColor: widget.progressIndicatorBackgroundColor,
valueColor: widget.progressIndicatorValueColor,
)
: _emptyWidget,
Row(
mainAxisSize: MainAxisSize.max,
children: _getAppropriateRowLayout(),
),
],
),
);
}
List<Widget> _getAppropriateRowLayout() {
double buttonRightPadding;
double iconPadding = 0;
if (widget.padding.right - 12 < 0) {
buttonRightPadding = 4;
} else {
buttonRightPadding = widget.padding.right - 12;
}
if (widget.padding.left > 16.0) {
iconPadding = widget.padding.left;
}
if (widget.icon == null && widget.mainButton == null) {
return [
_buildLeftBarIndicator(),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: widget.padding.left,
right: widget.padding.right,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: widget.padding.left,
right: widget.padding.right,
bottom: widget.padding.bottom,
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
];
} else if (widget.icon != null && widget.mainButton == null) {
return <Widget>[
_buildLeftBarIndicator(),
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: 4.0,
right: widget.padding.left,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: 4.0,
right: widget.padding.right,
bottom: widget.padding.bottom,
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
];
} else if (widget.icon == null && widget.mainButton != null) {
return <Widget>[
_buildLeftBarIndicator(),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: widget.padding.left,
right: widget.padding.right,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: widget.padding.left,
right: 8.0,
bottom: widget.padding.bottom,
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
Padding(
padding: EdgeInsets.only(right: buttonRightPadding),
child: _getMainActionButton(),
),
];
} else {
return <Widget>[
_buildLeftBarIndicator(),
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: 4.0,
right: 8.0,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: 4.0,
right: 8.0,
bottom: widget.padding.bottom,
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
Padding(
padding: EdgeInsets.only(right: buttonRightPadding),
child: _getMainActionButton(),
) ??
_emptyWidget,
];
}
}
Widget _buildLeftBarIndicator() {
if (widget.leftBarIndicatorColor != null) {
return FutureBuilder(
future: _boxHeightCompleter.future,
builder: (BuildContext buildContext, AsyncSnapshot<Size> snapshot) {
if (snapshot.hasData) {
return Container(
color: widget.leftBarIndicatorColor,
width: 5.0,
height: snapshot.data.height,
);
} else {
return _emptyWidget;
}
},
);
} else {
return _emptyWidget;
}
}
Widget _getIcon() {
if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) {
return FadeTransition(
opacity: _fadeAnimation,
child: widget.icon,
);
} else if (widget.icon != null) {
return widget.icon;
} else {
return _emptyWidget;
}
}
Widget _getTitleText() {
return widget.titleText != null
? widget.titleText
: Text(
widget.title ?? "",
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
fontWeight: FontWeight.bold),
);
}
Text _getDefaultNotificationText() {
return Text(
widget.message ?? "",
style: TextStyle(fontSize: 14.0, color: Colors.white),
);
}
FlatButton _getMainActionButton() {
if (widget.mainButton != null) {
return widget.mainButton;
} else {
return null;
}
}
}
/// Indicates if snack is going to start at the [TOP] or at the [BOTTOM]
enum SnackPosition { TOP, BOTTOM }
/// Indicates if snack will be attached to the edge of the screen or not
enum SnackStyle { FLOATING, GROUNDED }
/// Indicates the direction in which it is possible to dismiss
/// If vertical, dismiss up will be allowed if [SnackPosition.TOP]
/// If vertical, dismiss down will be allowed if [SnackPosition.BOTTOM]
enum SnackDismissDirection { HORIZONTAL, VERTICAL }
/// Indicates the animation status
/// [SnackStatus.SHOWING] Snack has stopped and the user can see it
/// [SnackStatus.DISMISSED] Snack has finished its mission and returned any pending values
/// [SnackStatus.IS_APPEARING] Snack is moving towards [SnackStatus.SHOWING]
/// [SnackStatus.IS_HIDING] Snack is moving towards [] [SnackStatus.DISMISSED]
enum SnackStatus { SHOWING, DISMISSED, IS_APPEARING, IS_HIDING }
... ...
import 'dart:async';
import 'dart:ui';
import 'snack.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class SnackRoute<T> extends OverlayRoute<T> {
Animation<double> _filterBlurAnimation;
Animation<Color> _filterColorAnimation;
SnackRoute({
@required this.snack,
RouteSettings settings,
}) : super(settings: settings) {
this._builder = Builder(builder: (BuildContext innerContext) {
return GestureDetector(
child: snack,
onTap: snack.onTap != null
? () {
snack.onTap(snack);
}
: null,
);
});
_configureAlignment(this.snack.snackPosition);
_onStatusChanged = snack.onStatusChanged;
}
_configureAlignment(SnackPosition snackPosition) {
switch (snack.snackPosition) {
case SnackPosition.TOP:
{
_initialAlignment = Alignment(-1.0, -2.0);
_endAlignment = Alignment(-1.0, -1.0);
break;
}
case SnackPosition.BOTTOM:
{
_initialAlignment = Alignment(-1.0, 2.0);
_endAlignment = Alignment(-1.0, 1.0);
break;
}
}
}
GetBar snack;
Builder _builder;
Future<T> get completed => _transitionCompleter.future;
final Completer<T> _transitionCompleter = Completer<T>();
SnackStatusCallback _onStatusChanged;
Alignment _initialAlignment;
Alignment _endAlignment;
bool _wasDismissedBySwipe = false;
Timer _timer;
bool get opaque => false;
@override
Iterable<OverlayEntry> createOverlayEntries() {
List<OverlayEntry> overlays = [];
if (snack.overlayBlur > 0.0) {
overlays.add(
OverlayEntry(
builder: (BuildContext context) {
return GestureDetector(
onTap: snack.isDismissible ? () => snack.dismiss() : null,
child: AnimatedBuilder(
animation: _filterBlurAnimation,
builder: (context, child) {
return BackdropFilter(
filter: ImageFilter.blur(
sigmaX: _filterBlurAnimation.value,
sigmaY: _filterBlurAnimation.value),
child: Container(
constraints: BoxConstraints.expand(),
color: _filterColorAnimation.value,
),
);
},
),
);
},
maintainState: false,
opaque: opaque),
);
}
overlays.add(
OverlayEntry(
builder: (BuildContext context) {
final Widget annotatedChild = Semantics(
child: AlignTransition(
alignment: _animation,
child: snack.isDismissible
? _getDismissibleSnack(_builder)
: _getSnack(),
),
focused: false,
container: true,
explicitChildNodes: true,
);
return annotatedChild;
},
maintainState: false,
opaque: opaque),
);
return overlays;
}
/// This string is a workaround until Dismissible supports a returning item
String dismissibleKeyGen = "";
Widget _getDismissibleSnack(Widget child) {
return Dismissible(
direction: _getDismissDirection(),
resizeDuration: null,
confirmDismiss: (_) {
if (currentStatus == SnackStatus.IS_APPEARING ||
currentStatus == SnackStatus.IS_HIDING) {
return Future.value(false);
}
return Future.value(true);
},
key: Key(dismissibleKeyGen),
onDismissed: (_) {
dismissibleKeyGen += "1";
_cancelTimer();
_wasDismissedBySwipe = true;
if (isCurrent) {
navigator.pop();
} else {
navigator.removeRoute(this);
}
},
child: _getSnack(),
);
}
Widget _getSnack() {
return Container(
margin: snack.margin,
child: _builder,
);
}
DismissDirection _getDismissDirection() {
if (snack.dismissDirection == SnackDismissDirection.HORIZONTAL) {
return DismissDirection.horizontal;
} else {
if (snack.snackPosition == SnackPosition.TOP) {
return DismissDirection.up;
} else {
return DismissDirection.down;
}
}
}
@override
bool get finishedWhenPopped =>
_controller.status == AnimationStatus.dismissed;
/// The animation that drives the route's transition and the previous route's
/// forward transition.
Animation<Alignment> get animation => _animation;
Animation<Alignment> _animation;
/// The animation controller that the route uses to drive the transitions.
///
/// The animation itself is exposed by the [animation] property.
@protected
AnimationController get controller => _controller;
AnimationController _controller;
/// Called to create the animation controller that will drive the transitions to
/// this route from the previous one, and back to the previous route from this
/// one.
AnimationController createAnimationController() {
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
assert(snack.animationDuration != null &&
snack.animationDuration >= Duration.zero);
return AnimationController(
duration: snack.animationDuration,
debugLabel: debugLabel,
vsync: navigator,
);
}
/// Called to create the animation that exposes the current progress of
/// the transition controlled by the animation controller created by
/// [createAnimationController()].
Animation<Alignment> createAnimation() {
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
assert(_controller != null);
return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate(
CurvedAnimation(
parent: _controller,
curve: snack.forwardAnimationCurve,
reverseCurve: snack.reverseAnimationCurve,
),
);
}
Animation<double> createBlurFilterAnimation() {
return Tween(begin: 0.0, end: snack.overlayBlur).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Animation<Color> createColorFilterAnimation() {
return ColorTween(begin: Colors.transparent, end: snack.overlayColor)
.animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
T _result;
SnackStatus currentStatus;
//copy of `routes.dart`
void _handleStatusChanged(AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
currentStatus = SnackStatus.SHOWING;
_onStatusChanged(currentStatus);
if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = opaque;
break;
case AnimationStatus.forward:
currentStatus = SnackStatus.IS_APPEARING;
_onStatusChanged(currentStatus);
break;
case AnimationStatus.reverse:
currentStatus = SnackStatus.IS_HIDING;
_onStatusChanged(currentStatus);
if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = false;
break;
case AnimationStatus.dismissed:
assert(!overlayEntries.first.opaque);
// We might still be the current route if a subclass is controlling the
// the transition and hits the dismissed status. For example, the iOS
// back gesture drives this animation to the dismissed status before
// popping the navigator.
currentStatus = SnackStatus.DISMISSED;
_onStatusChanged(currentStatus);
if (!isCurrent) {
navigator.finalizeRoute(this);
assert(overlayEntries.isEmpty);
}
break;
}
changedInternalState();
}
@override
void install(OverlayEntry insertionPoint) {
assert(!_transitionCompleter.isCompleted,
'Cannot install a $runtimeType after disposing it.');
_controller = createAnimationController();
assert(_controller != null,
'$runtimeType.createAnimationController() returned null.');
_filterBlurAnimation = createBlurFilterAnimation();
_filterColorAnimation = createColorFilterAnimation();
_animation = createAnimation();
assert(_animation != null, '$runtimeType.createAnimation() returned null.');
super.install(insertionPoint);
}
@override
TickerFuture didPush() {
super.didPush();
assert(_controller != null,
'$runtimeType.didPush called before calling install() or after calling dispose().');
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
_animation.addStatusListener(_handleStatusChanged);
_configureTimer();
return _controller.forward();
}
@override
void didReplace(Route<dynamic> oldRoute) {
assert(_controller != null,
'$runtimeType.didReplace called before calling install() or after calling dispose().');
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
if (oldRoute is SnackRoute) _controller.value = oldRoute._controller.value;
_animation.addStatusListener(_handleStatusChanged);
super.didReplace(oldRoute);
}
@override
bool didPop(T result) {
assert(_controller != null,
'$runtimeType.didPop called before calling install() or after calling dispose().');
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
_result = result;
_cancelTimer();
if (_wasDismissedBySwipe) {
Timer(Duration(milliseconds: 200), () {
_controller.reset();
});
_wasDismissedBySwipe = false;
} else {
_controller.reverse();
}
return super.didPop(result);
}
void _configureTimer() {
if (snack.duration != null) {
if (_timer != null && _timer.isActive) {
_timer.cancel();
}
_timer = Timer(snack.duration, () {
if (this.isCurrent) {
navigator.pop();
} else if (this.isActive) {
navigator.removeRoute(this);
}
});
} else {
if (_timer != null) {
_timer.cancel();
}
}
}
void _cancelTimer() {
if (_timer != null && _timer.isActive) {
_timer.cancel();
}
}
/// Whether this route can perform a transition to the given route.
/// Subclasses can override this method to restrict the set of routes they
/// need to coordinate transitions with.
bool canTransitionTo(SnackRoute<dynamic> nextRoute) => true;
/// Whether this route can perform a transition from the given route.
///
/// Subclasses can override this method to restrict the set of routes they
/// need to coordinate transitions with.
bool canTransitionFrom(SnackRoute<dynamic> previousRoute) => true;
@override
void dispose() {
assert(!_transitionCompleter.isCompleted,
'Cannot dispose a $runtimeType twice.');
_controller?.dispose();
_transitionCompleter.complete(_result);
super.dispose();
}
/// A short description of this route useful for debugging.
String get debugLabel => '$runtimeType';
@override
String toString() => '$runtimeType(animation: $_controller)';
}
SnackRoute showSnack<T>({@required GetBar snack}) {
assert(snack != null);
return SnackRoute<T>(
snack: snack,
settings: RouteSettings(name: "getroute"),
);
}
... ...
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.11"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.6"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.4"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0+1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.5"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.11"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
sdks:
dart: ">=2.4.0 <3.0.0"
... ...
name: get
description: A consistent Flutter route navigation library that navigate with no context and not rebuild materialApp with each navigation.
version: 1.2.1
description: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars with no context.
version: 1.6.3
author: Jonny Borges <jonataborges01@gmail.com>
homepage: https://github.com/jonataslaw/get
... ...