Line data Source code
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 1 : 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 1 : super(key: key) {
86 2 : 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 1 : Future<T> show() async {
216 2 : _snackRoute = route.showSnack<T>(
217 : snack: this,
218 : );
219 5 : return await Get.key.currentState.push(_snackRoute);
220 : }
221 :
222 : /// Dismisses the snack causing is to return a future containing [result].
223 : /// When this future finishes, it is guaranteed that Snack was dismissed.
224 0 : Future<T> dismiss([T result]) async {
225 : // If route was never initialized, do nothing
226 0 : if (_snackRoute == null) {
227 : return null;
228 : }
229 :
230 0 : if (_snackRoute.isCurrent) {
231 0 : _snackRoute.navigator.pop(result);
232 0 : return _snackRoute.completed;
233 0 : } else if (_snackRoute.isActive) {
234 : // removeRoute is called every time you dismiss a Snack that is not the top route.
235 : // It will not animate back and listeners will not detect SnackStatus.IS_HIDING or SnackStatus.DISMISSED
236 : // To avoid this, always make sure that Snack is the top route when it is being dismissed
237 0 : _snackRoute.navigator.removeRoute(_snackRoute);
238 : }
239 :
240 : return null;
241 : }
242 :
243 : /// Checks if the snack is visible
244 0 : bool isShowing() {
245 0 : return _snackRoute?.currentStatus == SnackStatus.SHOWING;
246 : }
247 :
248 : /// Checks if the snack is dismissed
249 0 : bool isDismissed() {
250 0 : return _snackRoute?.currentStatus == SnackStatus.DISMISSED;
251 : }
252 :
253 0 : @override
254 : State createState() {
255 0 : return _GetBarState<T>();
256 : }
257 : }
258 :
259 : class _GetBarState<K extends Object> extends State<GetBar>
260 : with TickerProviderStateMixin {
261 : SnackStatus currentStatus;
262 :
263 : AnimationController _fadeController;
264 : Animation<double> _fadeAnimation;
265 :
266 : final Widget _emptyWidget = SizedBox(width: 0.0, height: 0.0);
267 : final double _initialOpacity = 1.0;
268 : final double _finalOpacity = 0.4;
269 :
270 : final Duration _pulseAnimationDuration = Duration(seconds: 1);
271 :
272 : bool _isTitlePresent;
273 : double _messageTopMargin;
274 :
275 : FocusScopeNode _focusNode;
276 : FocusAttachment _focusAttachment;
277 :
278 0 : @override
279 : void initState() {
280 0 : super.initState();
281 :
282 : assert(
283 0 : ((widget.userInputForm != null ||
284 0 : ((widget.message != null && widget.message.isNotEmpty) ||
285 0 : widget.messageText != null))),
286 : "A message is mandatory if you are not using userInputForm. Set either a message or messageText");
287 :
288 0 : _isTitlePresent = (widget.title != null || widget.titleText != null);
289 0 : _messageTopMargin = _isTitlePresent ? 6.0 : widget.padding.top;
290 :
291 0 : _configureLeftBarFuture();
292 0 : _configureProgressIndicatorAnimation();
293 :
294 0 : if (widget.icon != null && widget.shouldIconPulse) {
295 0 : _configurePulseAnimation();
296 0 : _fadeController?.forward();
297 : }
298 :
299 0 : _focusNode = FocusScopeNode();
300 0 : _focusAttachment = _focusNode.attach(context);
301 : }
302 :
303 0 : @override
304 : void dispose() {
305 0 : _fadeController?.dispose();
306 :
307 0 : widget.progressIndicatorController?.removeListener(_progressListener);
308 0 : widget.progressIndicatorController?.dispose();
309 :
310 0 : _focusAttachment.detach();
311 0 : _focusNode.dispose();
312 0 : super.dispose();
313 : }
314 :
315 : final Completer<Size> _boxHeightCompleter = Completer<Size>();
316 :
317 0 : void _configureLeftBarFuture() {
318 0 : SchedulerBinding.instance.addPostFrameCallback(
319 0 : (_) {
320 0 : final keyContext = backgroundBoxKey.currentContext;
321 :
322 : if (keyContext != null) {
323 0 : final RenderBox box = keyContext.findRenderObject();
324 0 : _boxHeightCompleter.complete(box.size);
325 : }
326 : },
327 : );
328 : }
329 :
330 0 : void _configurePulseAnimation() {
331 0 : _fadeController =
332 0 : AnimationController(vsync: this, duration: _pulseAnimationDuration);
333 0 : _fadeAnimation = Tween(begin: _initialOpacity, end: _finalOpacity).animate(
334 0 : CurvedAnimation(
335 0 : parent: _fadeController,
336 : curve: Curves.linear,
337 : ),
338 : );
339 :
340 0 : _fadeController.addStatusListener((status) {
341 0 : if (status == AnimationStatus.completed) {
342 0 : _fadeController.reverse();
343 : }
344 0 : if (status == AnimationStatus.dismissed) {
345 0 : _fadeController.forward();
346 : }
347 : });
348 :
349 0 : _fadeController.forward();
350 : }
351 :
352 : Function _progressListener;
353 :
354 0 : void _configureProgressIndicatorAnimation() {
355 0 : if (widget.showProgressIndicator &&
356 0 : widget.progressIndicatorController != null) {
357 0 : _progressListener = () {
358 0 : setState(() {});
359 : };
360 0 : widget.progressIndicatorController.addListener(_progressListener);
361 :
362 0 : _progressAnimation = CurvedAnimation(
363 0 : curve: Curves.linear, parent: widget.progressIndicatorController);
364 : }
365 : }
366 :
367 0 : @override
368 : Widget build(BuildContext context) {
369 0 : return Align(
370 : heightFactor: 1.0,
371 0 : child: Material(
372 0 : color: widget.snackStyle == SnackStyle.FLOATING
373 : ? Colors.transparent
374 0 : : widget.backgroundColor,
375 0 : child: SafeArea(
376 0 : minimum: widget.snackPosition == SnackPosition.BOTTOM
377 0 : ? EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom)
378 : :
379 : // EdgeInsets.only(top: MediaQuery.of(context).viewInsets.top),
380 0 : EdgeInsets.only(top: MediaQuery.of(context).padding.top),
381 0 : bottom: widget.snackPosition == SnackPosition.BOTTOM,
382 0 : top: widget.snackPosition == SnackPosition.TOP,
383 : left: false,
384 : right: false,
385 0 : child: _getSnack(),
386 : ),
387 : ),
388 : );
389 : }
390 :
391 0 : Widget _getSnack() {
392 : Widget snack;
393 :
394 0 : if (widget.userInputForm != null) {
395 0 : snack = _generateInputSnack();
396 : } else {
397 0 : snack = _generateSnack();
398 : }
399 :
400 0 : return Stack(
401 0 : children: [
402 0 : FutureBuilder(
403 0 : future: _boxHeightCompleter.future,
404 0 : builder: (context, AsyncSnapshot<Size> snapshot) {
405 0 : if (snapshot.hasData) {
406 0 : return ClipRRect(
407 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
408 0 : child: BackdropFilter(
409 0 : filter: ImageFilter.blur(
410 0 : sigmaX: widget.barBlur, sigmaY: widget.barBlur),
411 0 : child: Container(
412 0 : height: snapshot.data.height,
413 0 : width: snapshot.data.width,
414 0 : decoration: BoxDecoration(
415 : color: Colors.transparent,
416 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
417 : ),
418 : ),
419 : ),
420 : );
421 : } else {
422 0 : return _emptyWidget;
423 : }
424 : },
425 : ),
426 : snack,
427 : ],
428 : );
429 : }
430 :
431 0 : Widget _generateInputSnack() {
432 0 : return Container(
433 0 : key: backgroundBoxKey,
434 0 : constraints: widget.maxWidth != null
435 0 : ? BoxConstraints(maxWidth: widget.maxWidth)
436 : : null,
437 0 : decoration: BoxDecoration(
438 0 : color: widget.backgroundColor,
439 0 : gradient: widget.backgroundGradient,
440 0 : boxShadow: widget.boxShadows,
441 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
442 0 : border: widget.borderColor != null
443 0 : ? Border.all(color: widget.borderColor, width: widget.borderWidth)
444 : : null,
445 : ),
446 0 : child: Padding(
447 : padding: const EdgeInsets.only(
448 : left: 8.0, right: 8.0, bottom: 8.0, top: 16.0),
449 0 : child: FocusScope(
450 0 : child: widget.userInputForm,
451 0 : node: _focusNode,
452 : autofocus: true,
453 : ),
454 : ),
455 : );
456 : }
457 :
458 : CurvedAnimation _progressAnimation;
459 : GlobalKey backgroundBoxKey = GlobalKey();
460 :
461 0 : Widget _generateSnack() {
462 0 : return Container(
463 0 : key: backgroundBoxKey,
464 0 : constraints: widget.maxWidth != null
465 0 : ? BoxConstraints(maxWidth: widget.maxWidth)
466 : : null,
467 0 : decoration: BoxDecoration(
468 0 : color: widget.backgroundColor,
469 0 : gradient: widget.backgroundGradient,
470 0 : boxShadow: widget.boxShadows,
471 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
472 0 : border: widget.borderColor != null
473 0 : ? Border.all(color: widget.borderColor, width: widget.borderWidth)
474 : : null,
475 : ),
476 0 : child: Column(
477 : mainAxisSize: MainAxisSize.min,
478 0 : children: [
479 0 : widget.showProgressIndicator
480 0 : ? LinearProgressIndicator(
481 0 : value: widget.progressIndicatorController != null
482 0 : ? _progressAnimation.value
483 : : null,
484 0 : backgroundColor: widget.progressIndicatorBackgroundColor,
485 0 : valueColor: widget.progressIndicatorValueColor,
486 : )
487 0 : : _emptyWidget,
488 0 : Row(
489 : mainAxisSize: MainAxisSize.max,
490 0 : children: _getAppropriateRowLayout(),
491 : ),
492 : ],
493 : ),
494 : );
495 : }
496 :
497 0 : List<Widget> _getAppropriateRowLayout() {
498 : double buttonRightPadding;
499 : double iconPadding = 0;
500 0 : if (widget.padding.right - 12 < 0) {
501 : buttonRightPadding = 4;
502 : } else {
503 0 : buttonRightPadding = widget.padding.right - 12;
504 : }
505 :
506 0 : if (widget.padding.left > 16.0) {
507 0 : iconPadding = widget.padding.left;
508 : }
509 :
510 0 : if (widget.icon == null && widget.mainButton == null) {
511 0 : return [
512 0 : _buildLeftBarIndicator(),
513 0 : Expanded(
514 : flex: 1,
515 0 : child: Column(
516 : crossAxisAlignment: CrossAxisAlignment.stretch,
517 : mainAxisSize: MainAxisSize.min,
518 0 : children: <Widget>[
519 0 : (_isTitlePresent)
520 0 : ? Padding(
521 0 : padding: EdgeInsets.only(
522 0 : top: widget.padding.top,
523 0 : left: widget.padding.left,
524 0 : right: widget.padding.right,
525 : ),
526 0 : child: _getTitleText(),
527 : )
528 0 : : _emptyWidget,
529 0 : Padding(
530 0 : padding: EdgeInsets.only(
531 0 : top: _messageTopMargin,
532 0 : left: widget.padding.left,
533 0 : right: widget.padding.right,
534 0 : bottom: widget.padding.bottom,
535 : ),
536 0 : child: widget.messageText ?? _getDefaultNotificationText(),
537 : ),
538 : ],
539 : ),
540 : ),
541 : ];
542 0 : } else if (widget.icon != null && widget.mainButton == null) {
543 0 : return <Widget>[
544 0 : _buildLeftBarIndicator(),
545 0 : ConstrainedBox(
546 0 : constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
547 0 : child: _getIcon(),
548 : ),
549 0 : Expanded(
550 : flex: 1,
551 0 : child: Column(
552 : crossAxisAlignment: CrossAxisAlignment.stretch,
553 : mainAxisSize: MainAxisSize.min,
554 0 : children: <Widget>[
555 0 : (_isTitlePresent)
556 0 : ? Padding(
557 0 : padding: EdgeInsets.only(
558 0 : top: widget.padding.top,
559 : left: 4.0,
560 0 : right: widget.padding.left,
561 : ),
562 0 : child: _getTitleText(),
563 : )
564 0 : : _emptyWidget,
565 0 : Padding(
566 0 : padding: EdgeInsets.only(
567 0 : top: _messageTopMargin,
568 : left: 4.0,
569 0 : right: widget.padding.right,
570 0 : bottom: widget.padding.bottom,
571 : ),
572 0 : child: widget.messageText ?? _getDefaultNotificationText(),
573 : ),
574 : ],
575 : ),
576 : ),
577 : ];
578 0 : } else if (widget.icon == null && widget.mainButton != null) {
579 0 : return <Widget>[
580 0 : _buildLeftBarIndicator(),
581 0 : Expanded(
582 : flex: 1,
583 0 : child: Column(
584 : crossAxisAlignment: CrossAxisAlignment.stretch,
585 : mainAxisSize: MainAxisSize.min,
586 0 : children: <Widget>[
587 0 : (_isTitlePresent)
588 0 : ? Padding(
589 0 : padding: EdgeInsets.only(
590 0 : top: widget.padding.top,
591 0 : left: widget.padding.left,
592 0 : right: widget.padding.right,
593 : ),
594 0 : child: _getTitleText(),
595 : )
596 0 : : _emptyWidget,
597 0 : Padding(
598 0 : padding: EdgeInsets.only(
599 0 : top: _messageTopMargin,
600 0 : left: widget.padding.left,
601 : right: 8.0,
602 0 : bottom: widget.padding.bottom,
603 : ),
604 0 : child: widget.messageText ?? _getDefaultNotificationText(),
605 : ),
606 : ],
607 : ),
608 : ),
609 0 : Padding(
610 0 : padding: EdgeInsets.only(right: buttonRightPadding),
611 0 : child: _getMainActionButton(),
612 : ),
613 : ];
614 : } else {
615 0 : return <Widget>[
616 0 : _buildLeftBarIndicator(),
617 0 : ConstrainedBox(
618 0 : constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
619 0 : child: _getIcon(),
620 : ),
621 0 : Expanded(
622 : flex: 1,
623 0 : child: Column(
624 : crossAxisAlignment: CrossAxisAlignment.stretch,
625 : mainAxisSize: MainAxisSize.min,
626 0 : children: <Widget>[
627 0 : (_isTitlePresent)
628 0 : ? Padding(
629 0 : padding: EdgeInsets.only(
630 0 : top: widget.padding.top,
631 : left: 4.0,
632 : right: 8.0,
633 : ),
634 0 : child: _getTitleText(),
635 : )
636 0 : : _emptyWidget,
637 0 : Padding(
638 0 : padding: EdgeInsets.only(
639 0 : top: _messageTopMargin,
640 : left: 4.0,
641 : right: 8.0,
642 0 : bottom: widget.padding.bottom,
643 : ),
644 0 : child: widget.messageText ?? _getDefaultNotificationText(),
645 : ),
646 : ],
647 : ),
648 : ),
649 0 : Padding(
650 0 : padding: EdgeInsets.only(right: buttonRightPadding),
651 0 : child: _getMainActionButton(),
652 : ) ??
653 0 : _emptyWidget,
654 : ];
655 : }
656 : }
657 :
658 0 : Widget _buildLeftBarIndicator() {
659 0 : if (widget.leftBarIndicatorColor != null) {
660 0 : return FutureBuilder(
661 0 : future: _boxHeightCompleter.future,
662 0 : builder: (BuildContext buildContext, AsyncSnapshot<Size> snapshot) {
663 0 : if (snapshot.hasData) {
664 0 : return Container(
665 0 : color: widget.leftBarIndicatorColor,
666 : width: 5.0,
667 0 : height: snapshot.data.height,
668 : );
669 : } else {
670 0 : return _emptyWidget;
671 : }
672 : },
673 : );
674 : } else {
675 0 : return _emptyWidget;
676 : }
677 : }
678 :
679 0 : Widget _getIcon() {
680 0 : if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) {
681 0 : return FadeTransition(
682 0 : opacity: _fadeAnimation,
683 0 : child: widget.icon,
684 : );
685 0 : } else if (widget.icon != null) {
686 0 : return widget.icon;
687 : } else {
688 0 : return _emptyWidget;
689 : }
690 : }
691 :
692 0 : Widget _getTitleText() {
693 0 : return widget.titleText != null
694 0 : ? widget.titleText
695 0 : : Text(
696 0 : widget.title ?? "",
697 0 : style: TextStyle(
698 : fontSize: 16.0,
699 : color: Colors.white,
700 : fontWeight: FontWeight.bold),
701 : );
702 : }
703 :
704 0 : Text _getDefaultNotificationText() {
705 0 : return Text(
706 0 : widget.message ?? "",
707 0 : style: TextStyle(fontSize: 14.0, color: Colors.white),
708 : );
709 : }
710 :
711 0 : FlatButton _getMainActionButton() {
712 0 : if (widget.mainButton != null) {
713 0 : 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 39 : enum SnackPosition { TOP, BOTTOM }
722 :
723 : /// Indicates if snack will be attached to the edge of the screen or not
724 39 : 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 39 : 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 65 : enum SnackStatus { SHOWING, DISMISSED, IS_APPEARING, IS_HIDING }
|