Line data Source code
1 : import 'dart:async';
2 : import 'dart:ui';
3 : import 'package:flutter/material.dart';
4 : import 'package:get/src/snackbar/snack.dart';
5 :
6 : class SnackRoute<T> extends OverlayRoute<T> {
7 : final GetBar snack;
8 : final Builder _builder;
9 : final Completer<T> _transitionCompleter = Completer<T>();
10 : final SnackStatusCallback _onStatusChanged;
11 : Alignment _initialAlignment;
12 : Alignment _endAlignment;
13 : bool _wasDismissedBySwipe = false;
14 : Timer _timer;
15 : T _result;
16 : SnackStatus currentStatus;
17 :
18 1 : SnackRoute({
19 : @required this.snack,
20 : RouteSettings settings,
21 1 : }) : _builder = Builder(builder: (BuildContext innerContext) {
22 0 : return GestureDetector(
23 : child: snack,
24 0 : onTap: snack.onTap != null ? () => snack.onTap(snack) : null,
25 : );
26 : }),
27 1 : _onStatusChanged = snack.onStatusChanged,
28 1 : super(settings: settings) {
29 3 : _configureAlignment(this.snack.snackPosition);
30 : }
31 :
32 1 : void _configureAlignment(SnackPosition snackPosition) {
33 2 : switch (snack.snackPosition) {
34 1 : case SnackPosition.TOP:
35 : {
36 4 : _initialAlignment = Alignment(-1.0, -2.0);
37 4 : _endAlignment = Alignment(-1.0, -1.0);
38 : break;
39 : }
40 1 : case SnackPosition.BOTTOM:
41 : {
42 3 : _initialAlignment = Alignment(-1.0, 2.0);
43 3 : _endAlignment = Alignment(-1.0, 1.0);
44 : break;
45 : }
46 : }
47 : }
48 :
49 0 : Future<T> get completed => _transitionCompleter.future;
50 1 : bool get opaque => false;
51 :
52 1 : @override
53 : Iterable<OverlayEntry> createOverlayEntries() {
54 1 : final List<OverlayEntry> overlays = [];
55 :
56 1 : overlays.add(
57 1 : OverlayEntry(
58 0 : builder: (BuildContext context) {
59 0 : final Widget annotatedChild = Semantics(
60 0 : child: AlignTransition(
61 0 : alignment: _animation,
62 0 : child: _getSnack(),
63 : ),
64 : focused: false,
65 : container: true,
66 : explicitChildNodes: true,
67 : );
68 : return annotatedChild;
69 : },
70 : maintainState: false,
71 1 : opaque: opaque),
72 : );
73 :
74 : return overlays;
75 : }
76 :
77 : /// This string is a workaround until Dismissible supports a returning item
78 : String dismissibleKeyGen = "";
79 :
80 0 : Widget _getSnack() {
81 0 : return Container(
82 0 : margin: snack.margin,
83 0 : child: _builder,
84 : );
85 : }
86 :
87 1 : @override
88 : bool get finishedWhenPopped =>
89 3 : _controller.status == AnimationStatus.dismissed;
90 :
91 : /// The animation that drives the route's transition and the previous route's
92 : /// forward transition.
93 0 : Animation<Alignment> get animation => _animation;
94 : Animation<Alignment> _animation;
95 :
96 : /// The animation controller that the route uses to drive the transitions.
97 : ///
98 : /// The animation itself is exposed by the [animation] property.
99 0 : @protected
100 0 : AnimationController get controller => _controller;
101 : AnimationController _controller;
102 :
103 : /// Called to create the animation controller that will drive the transitions to
104 : /// this route from the previous one, and back to the previous route from this
105 : /// one.
106 1 : AnimationController createAnimationController() {
107 2 : assert(!_transitionCompleter.isCompleted,
108 0 : 'Cannot reuse a $runtimeType after disposing it.');
109 2 : assert(snack.animationDuration != null &&
110 3 : snack.animationDuration >= Duration.zero);
111 1 : return AnimationController(
112 2 : duration: snack.animationDuration,
113 1 : debugLabel: debugLabel,
114 1 : vsync: navigator,
115 : );
116 : }
117 :
118 : /// Called to create the animation that exposes the current progress of
119 : /// the transition controlled by the animation controller created by
120 : /// [createAnimationController()].
121 1 : Animation<Alignment> createAnimation() {
122 2 : assert(!_transitionCompleter.isCompleted,
123 0 : 'Cannot reuse a $runtimeType after disposing it.');
124 1 : assert(_controller != null);
125 4 : return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate(
126 1 : CurvedAnimation(
127 1 : parent: _controller,
128 2 : curve: snack.forwardAnimationCurve,
129 2 : reverseCurve: snack.reverseAnimationCurve,
130 : ),
131 : );
132 : }
133 :
134 0 : Animation<double> createBlurFilterAnimation() {
135 0 : if (snack.overlayBlur == null) return null;
136 :
137 0 : return Tween(begin: 0.0, end: snack.overlayBlur).animate(
138 0 : CurvedAnimation(
139 0 : parent: _controller,
140 0 : curve: Interval(
141 : 0.0,
142 : 0.35,
143 : curve: Curves.easeInOutCirc,
144 : ),
145 : ),
146 : );
147 : }
148 :
149 0 : Animation<Color> createColorFilterAnimation() {
150 0 : if (snack.overlayColor == null) return null;
151 :
152 0 : return ColorTween(begin: Colors.transparent, end: snack.overlayColor)
153 0 : .animate(
154 0 : CurvedAnimation(
155 0 : parent: _controller,
156 0 : curve: Interval(
157 : 0.0,
158 : 0.35,
159 : curve: Curves.easeInOutCirc,
160 : ),
161 : ),
162 : );
163 : }
164 :
165 : //copy of `routes.dart`
166 1 : void _handleStatusChanged(AnimationStatus status) {
167 : switch (status) {
168 1 : case AnimationStatus.completed:
169 0 : currentStatus = SnackStatus.SHOWING;
170 0 : _onStatusChanged(currentStatus);
171 0 : if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = opaque;
172 :
173 : break;
174 1 : case AnimationStatus.forward:
175 1 : currentStatus = SnackStatus.IS_APPEARING;
176 3 : _onStatusChanged(currentStatus);
177 : break;
178 1 : case AnimationStatus.reverse:
179 0 : currentStatus = SnackStatus.IS_HIDING;
180 0 : _onStatusChanged(currentStatus);
181 0 : if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = false;
182 : break;
183 1 : case AnimationStatus.dismissed:
184 3 : assert(!overlayEntries.first.opaque);
185 : // We might still be the current route if a subclass is controlling the
186 : // the transition and hits the dismissed status. For example, the iOS
187 : // back gesture drives this animation to the dismissed status before
188 : // popping the navigator.
189 1 : currentStatus = SnackStatus.DISMISSED;
190 3 : _onStatusChanged(currentStatus);
191 :
192 1 : if (!isCurrent) {
193 0 : navigator.finalizeRoute(this);
194 0 : assert(overlayEntries.isEmpty);
195 : }
196 : break;
197 : }
198 1 : changedInternalState();
199 : }
200 :
201 1 : @override
202 : void install() {
203 2 : assert(!_transitionCompleter.isCompleted,
204 0 : 'Cannot install a $runtimeType after disposing it.');
205 2 : _controller = createAnimationController();
206 1 : assert(_controller != null,
207 0 : '$runtimeType.createAnimationController() returned null.');
208 2 : _animation = createAnimation();
209 1 : assert(_animation != null, '$runtimeType.createAnimation() returned null.');
210 1 : super.install();
211 : }
212 :
213 1 : @override
214 : TickerFuture didPush() {
215 1 : assert(_controller != null,
216 0 : '$runtimeType.didPush called before calling install() or after calling dispose().');
217 2 : assert(!_transitionCompleter.isCompleted,
218 0 : 'Cannot reuse a $runtimeType after disposing it.');
219 3 : _animation.addStatusListener(_handleStatusChanged);
220 1 : _configureTimer();
221 1 : super.didPush();
222 2 : return _controller.forward();
223 : }
224 :
225 1 : @override
226 : bool didPop(T result) {
227 1 : assert(_controller != null,
228 0 : '$runtimeType.didPop called before calling install() or after calling dispose().');
229 2 : assert(!_transitionCompleter.isCompleted,
230 0 : 'Cannot reuse a $runtimeType after disposing it.');
231 :
232 1 : _result = result;
233 1 : _cancelTimer();
234 :
235 1 : if (_wasDismissedBySwipe) {
236 0 : Timer(Duration(milliseconds: 200), () {
237 0 : _controller.reset();
238 : });
239 :
240 0 : _wasDismissedBySwipe = false;
241 : } else {
242 2 : _controller.reverse();
243 : }
244 :
245 1 : return super.didPop(result);
246 : }
247 :
248 1 : void _configureTimer() {
249 2 : if (snack.duration != null) {
250 1 : if (_timer != null && _timer.isActive) {
251 0 : _timer.cancel();
252 : }
253 5 : _timer = Timer(snack.duration, () {
254 1 : if (this.isCurrent) {
255 2 : navigator.pop();
256 0 : } else if (this.isActive) {
257 0 : navigator.removeRoute(this);
258 : }
259 : });
260 : } else {
261 0 : if (_timer != null) {
262 0 : _timer.cancel();
263 : }
264 : }
265 : }
266 :
267 1 : void _cancelTimer() {
268 3 : if (_timer != null && _timer.isActive) {
269 0 : _timer.cancel();
270 : }
271 : }
272 :
273 1 : @override
274 : void dispose() {
275 2 : assert(!_transitionCompleter.isCompleted,
276 0 : 'Cannot dispose a $runtimeType twice.');
277 2 : _controller?.dispose();
278 3 : _transitionCompleter.complete(_result);
279 1 : super.dispose();
280 : }
281 :
282 : /// A short description of this route useful for debugging.
283 3 : String get debugLabel => '$runtimeType';
284 :
285 1 : @override
286 3 : String toString() => '$runtimeType(animation: $_controller)';
287 : }
288 :
289 1 : SnackRoute showSnack<T>({@required GetBar snack}) {
290 1 : return SnackRoute<T>(
291 : snack: snack,
292 1 : settings: RouteSettings(name: 'snackbar'),
293 : );
294 : }
|