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 0 : : EdgeInsets.only(top: MediaQuery.of(context).padding.top),
379 0 : child: SafeArea(
380 0 : minimum: widget.snackPosition == SnackPosition.BOTTOM
381 0 : ? EdgeInsets.only(
382 0 : bottom: MediaQuery.of(context).viewInsets.bottom)
383 0 : : EdgeInsets.only(top: MediaQuery.of(context).viewInsets.top),
384 0 : bottom: widget.snackPosition == SnackPosition.BOTTOM,
385 0 : top: widget.snackPosition == SnackPosition.TOP,
386 : left: false,
387 : right: false,
388 0 : child: _getSnack(),
389 : ),
390 : ),
391 : ),
392 : );
393 : }
394 :
395 0 : Widget _getSnack() {
396 : Widget snack;
397 :
398 0 : if (widget.userInputForm != null) {
399 0 : snack = _generateInputSnack();
400 : } else {
401 0 : snack = _generateSnack();
402 : }
403 :
404 0 : return Stack(
405 0 : children: [
406 0 : FutureBuilder(
407 0 : future: _boxHeightCompleter.future,
408 0 : builder: (context, AsyncSnapshot<Size> snapshot) {
409 0 : if (snapshot.hasData) {
410 0 : return ClipRRect(
411 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
412 0 : child: BackdropFilter(
413 0 : filter: ImageFilter.blur(
414 0 : sigmaX: widget.barBlur, sigmaY: widget.barBlur),
415 0 : child: Container(
416 0 : height: snapshot.data.height,
417 0 : width: snapshot.data.width,
418 0 : decoration: BoxDecoration(
419 : color: Colors.transparent,
420 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
421 : ),
422 : ),
423 : ),
424 : );
425 : } else {
426 0 : return _emptyWidget;
427 : }
428 : },
429 : ),
430 : snack,
431 : ],
432 : );
433 : }
434 :
435 0 : Widget _generateInputSnack() {
436 0 : return Container(
437 0 : key: backgroundBoxKey,
438 0 : constraints: widget.maxWidth != null
439 0 : ? BoxConstraints(maxWidth: widget.maxWidth)
440 : : null,
441 0 : decoration: BoxDecoration(
442 0 : color: widget.backgroundColor,
443 0 : gradient: widget.backgroundGradient,
444 0 : boxShadow: widget.boxShadows,
445 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
446 0 : border: widget.borderColor != null
447 0 : ? Border.all(color: widget.borderColor, width: widget.borderWidth)
448 : : null,
449 : ),
450 0 : child: Padding(
451 : padding: const EdgeInsets.only(
452 : left: 8.0, right: 8.0, bottom: 8.0, top: 16.0),
453 0 : child: FocusScope(
454 0 : child: widget.userInputForm,
455 0 : node: _focusNode,
456 : autofocus: true,
457 : ),
458 : ),
459 : );
460 : }
461 :
462 : CurvedAnimation _progressAnimation;
463 : GlobalKey backgroundBoxKey = GlobalKey();
464 :
465 0 : Widget _generateSnack() {
466 0 : return Container(
467 0 : key: backgroundBoxKey,
468 0 : constraints: widget.maxWidth != null
469 0 : ? BoxConstraints(maxWidth: widget.maxWidth)
470 : : null,
471 0 : decoration: BoxDecoration(
472 0 : color: widget.backgroundColor,
473 0 : gradient: widget.backgroundGradient,
474 0 : boxShadow: widget.boxShadows,
475 0 : borderRadius: BorderRadius.circular(widget.borderRadius),
476 0 : border: widget.borderColor != null
477 0 : ? Border.all(color: widget.borderColor, width: widget.borderWidth)
478 : : null,
479 : ),
480 0 : child: Column(
481 : mainAxisSize: MainAxisSize.min,
482 0 : children: [
483 0 : widget.showProgressIndicator
484 0 : ? LinearProgressIndicator(
485 0 : value: widget.progressIndicatorController != null
486 0 : ? _progressAnimation.value
487 : : null,
488 0 : backgroundColor: widget.progressIndicatorBackgroundColor,
489 0 : valueColor: widget.progressIndicatorValueColor,
490 : )
491 0 : : _emptyWidget,
492 0 : Row(
493 : mainAxisSize: MainAxisSize.max,
494 0 : children: _getAppropriateRowLayout(),
495 : ),
496 : ],
497 : ),
498 : );
499 : }
500 :
501 0 : List<Widget> _getAppropriateRowLayout() {
502 : double buttonRightPadding;
503 : double iconPadding = 0;
504 0 : if (widget.padding.right - 12 < 0) {
505 : buttonRightPadding = 4;
506 : } else {
507 0 : buttonRightPadding = widget.padding.right - 12;
508 : }
509 :
510 0 : if (widget.padding.left > 16.0) {
511 0 : iconPadding = widget.padding.left;
512 : }
513 :
514 0 : if (widget.icon == null && widget.mainButton == null) {
515 0 : return [
516 0 : _buildLeftBarIndicator(),
517 0 : Expanded(
518 : flex: 1,
519 0 : child: Column(
520 : crossAxisAlignment: CrossAxisAlignment.stretch,
521 : mainAxisSize: MainAxisSize.min,
522 0 : children: <Widget>[
523 0 : (_isTitlePresent)
524 0 : ? Padding(
525 0 : padding: EdgeInsets.only(
526 0 : top: widget.padding.top,
527 0 : left: widget.padding.left,
528 0 : right: widget.padding.right,
529 : ),
530 0 : child: _getTitleText(),
531 : )
532 0 : : _emptyWidget,
533 0 : Padding(
534 0 : padding: EdgeInsets.only(
535 0 : top: _messageTopMargin,
536 0 : left: widget.padding.left,
537 0 : right: widget.padding.right,
538 0 : bottom: widget.padding.bottom,
539 : ),
540 0 : child: widget.messageText ?? _getDefaultNotificationText(),
541 : ),
542 : ],
543 : ),
544 : ),
545 : ];
546 0 : } else if (widget.icon != null && widget.mainButton == null) {
547 0 : return <Widget>[
548 0 : _buildLeftBarIndicator(),
549 0 : ConstrainedBox(
550 0 : constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
551 0 : child: _getIcon(),
552 : ),
553 0 : Expanded(
554 : flex: 1,
555 0 : child: Column(
556 : crossAxisAlignment: CrossAxisAlignment.stretch,
557 : mainAxisSize: MainAxisSize.min,
558 0 : children: <Widget>[
559 0 : (_isTitlePresent)
560 0 : ? Padding(
561 0 : padding: EdgeInsets.only(
562 0 : top: widget.padding.top,
563 : left: 4.0,
564 0 : right: widget.padding.left,
565 : ),
566 0 : child: _getTitleText(),
567 : )
568 0 : : _emptyWidget,
569 0 : Padding(
570 0 : padding: EdgeInsets.only(
571 0 : top: _messageTopMargin,
572 : left: 4.0,
573 0 : right: widget.padding.right,
574 0 : bottom: widget.padding.bottom,
575 : ),
576 0 : child: widget.messageText ?? _getDefaultNotificationText(),
577 : ),
578 : ],
579 : ),
580 : ),
581 : ];
582 0 : } else if (widget.icon == null && widget.mainButton != null) {
583 0 : return <Widget>[
584 0 : _buildLeftBarIndicator(),
585 0 : Expanded(
586 : flex: 1,
587 0 : child: Column(
588 : crossAxisAlignment: CrossAxisAlignment.stretch,
589 : mainAxisSize: MainAxisSize.min,
590 0 : children: <Widget>[
591 0 : (_isTitlePresent)
592 0 : ? Padding(
593 0 : padding: EdgeInsets.only(
594 0 : top: widget.padding.top,
595 0 : left: widget.padding.left,
596 0 : right: widget.padding.right,
597 : ),
598 0 : child: _getTitleText(),
599 : )
600 0 : : _emptyWidget,
601 0 : Padding(
602 0 : padding: EdgeInsets.only(
603 0 : top: _messageTopMargin,
604 0 : left: widget.padding.left,
605 : right: 8.0,
606 0 : bottom: widget.padding.bottom,
607 : ),
608 0 : child: widget.messageText ?? _getDefaultNotificationText(),
609 : ),
610 : ],
611 : ),
612 : ),
613 0 : Padding(
614 0 : padding: EdgeInsets.only(right: buttonRightPadding),
615 0 : child: _getMainActionButton(),
616 : ),
617 : ];
618 : } else {
619 0 : return <Widget>[
620 0 : _buildLeftBarIndicator(),
621 0 : ConstrainedBox(
622 0 : constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
623 0 : child: _getIcon(),
624 : ),
625 0 : Expanded(
626 : flex: 1,
627 0 : child: Column(
628 : crossAxisAlignment: CrossAxisAlignment.stretch,
629 : mainAxisSize: MainAxisSize.min,
630 0 : children: <Widget>[
631 0 : (_isTitlePresent)
632 0 : ? Padding(
633 0 : padding: EdgeInsets.only(
634 0 : top: widget.padding.top,
635 : left: 4.0,
636 : right: 8.0,
637 : ),
638 0 : child: _getTitleText(),
639 : )
640 0 : : _emptyWidget,
641 0 : Padding(
642 0 : padding: EdgeInsets.only(
643 0 : top: _messageTopMargin,
644 : left: 4.0,
645 : right: 8.0,
646 0 : bottom: widget.padding.bottom,
647 : ),
648 0 : child: widget.messageText ?? _getDefaultNotificationText(),
649 : ),
650 : ],
651 : ),
652 : ),
653 0 : Padding(
654 0 : padding: EdgeInsets.only(right: buttonRightPadding),
655 0 : child: _getMainActionButton(),
656 : ) ??
657 0 : _emptyWidget,
658 : ];
659 : }
660 : }
661 :
662 0 : Widget _buildLeftBarIndicator() {
663 0 : if (widget.leftBarIndicatorColor != null) {
664 0 : return FutureBuilder(
665 0 : future: _boxHeightCompleter.future,
666 0 : builder: (BuildContext buildContext, AsyncSnapshot<Size> snapshot) {
667 0 : if (snapshot.hasData) {
668 0 : return Container(
669 0 : color: widget.leftBarIndicatorColor,
670 : width: 5.0,
671 0 : height: snapshot.data.height,
672 : );
673 : } else {
674 0 : return _emptyWidget;
675 : }
676 : },
677 : );
678 : } else {
679 0 : return _emptyWidget;
680 : }
681 : }
682 :
683 0 : Widget _getIcon() {
684 0 : if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) {
685 0 : return FadeTransition(
686 0 : opacity: _fadeAnimation,
687 0 : child: widget.icon,
688 : );
689 0 : } else if (widget.icon != null) {
690 0 : return widget.icon;
691 : } else {
692 0 : return _emptyWidget;
693 : }
694 : }
695 :
696 0 : Widget _getTitleText() {
697 0 : return widget.titleText != null
698 0 : ? widget.titleText
699 0 : : Text(
700 0 : widget.title ?? "",
701 0 : style: TextStyle(
702 : fontSize: 16.0,
703 : color: Colors.white,
704 : fontWeight: FontWeight.bold),
705 : );
706 : }
707 :
708 0 : Text _getDefaultNotificationText() {
709 0 : return Text(
710 0 : widget.message ?? "",
711 0 : style: TextStyle(fontSize: 14.0, color: Colors.white),
712 : );
713 : }
714 :
715 0 : FlatButton _getMainActionButton() {
716 0 : if (widget.mainButton != null) {
717 0 : return widget.mainButton;
718 : } else {
719 : return null;
720 : }
721 : }
722 : }
723 :
724 : /// Indicates if snack is going to start at the [TOP] or at the [BOTTOM]
725 36 : enum SnackPosition { TOP, BOTTOM }
726 :
727 : /// Indicates if snack will be attached to the edge of the screen or not
728 36 : enum SnackStyle { FLOATING, GROUNDED }
729 :
730 : /// Indicates the direction in which it is possible to dismiss
731 : /// If vertical, dismiss up will be allowed if [SnackPosition.TOP]
732 : /// If vertical, dismiss down will be allowed if [SnackPosition.BOTTOM]
733 36 : enum SnackDismissDirection { HORIZONTAL, VERTICAL }
734 :
735 : /// Indicates the animation status
736 : /// [SnackStatus.SHOWING] Snack has stopped and the user can see it
737 : /// [SnackStatus.DISMISSED] Snack has finished its mission and returned any pending values
738 : /// [SnackStatus.IS_APPEARING] Snack is moving towards [SnackStatus.SHOWING]
739 : /// [SnackStatus.IS_HIDING] Snack is moving towards [] [SnackStatus.DISMISSED]
740 60 : enum SnackStatus { SHOWING, DISMISSED, IS_APPEARING, IS_HIDING }
|