Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:flutter/cupertino.dart';
4 : import 'package:liquid_swipe/Helpers/Helpers.dart';
5 : import 'package:liquid_swipe/Helpers/slide_update.dart';
6 : import 'package:liquid_swipe/PageHelpers/animated_page_dragger.dart';
7 : import 'package:liquid_swipe/PageHelpers/page_dragger.dart';
8 : import 'package:liquid_swipe/liquid_swipe.dart';
9 : import 'package:provider/provider.dart';
10 :
11 : /// Internal Class
12 : ///
13 : /// A [ChangeNotifierProvider] to manage [LiquidSwipe] State.
14 : /// Every Change is notified from it.
15 : /// Methods Included :
16 : /// - [animateToPage]
17 : /// - [_animateDirectlyToPage]
18 : /// - [jumpToPage]
19 : /// - [updateData]
20 : /// - some more, soon.
21 : class LiquidProvider extends ChangeNotifier {
22 : /// A [SlideUpdate] type for storing the current Slide Update.
23 : SlideUpdate? slideUpdate;
24 :
25 : /// [AnimatedPageDragger] required for completing the animation when [UpdateType] is [UpdateType.doneAnimating]
26 : late AnimatedPageDragger animatedPageDragger;
27 :
28 : /// Storing ActivePage Index
29 : /// default = 0
30 : int activePageIndex = 0;
31 :
32 : ///Storing next Page Index
33 : ///default = 0
34 : int nextPageIndex = 0;
35 :
36 : ///Storing the Swipe Direction using [SlideDirection]
37 : SlideDirection slideDirection = SlideDirection.none;
38 :
39 : /// percentage of slide both Horizontal and Vertical, during touch
40 : double slidePercentHor = 0.00;
41 : double slidePercentVer = 0.00;
42 :
43 : ///Storing Previous [UpdateType]
44 : UpdateType? prevUpdate;
45 :
46 : ///user manageable bool to make Enable and Disable loop within the Pages
47 : bool enableLoop = true;
48 :
49 : ///Number of Page.
50 : int pagesLength = 0;
51 :
52 : ///Ticker Provider from [LiquidSwipe], cause need to use it in [AnimatedPageDragger]
53 : late TickerProviderStateMixin singleTickerProviderStateMixin;
54 :
55 : ///SlideIcon position, always Horizontal, used in [PageDragger]
56 : late double positionSlideIcon;
57 :
58 : ///see [CurrentUpdateTypeCallback]
59 : CurrentUpdateTypeCallback? _currentUpdateTypeCallback;
60 :
61 : ///see [OnPageChangeCallback]
62 : OnPageChangeCallback? _onPageChangeCallback;
63 :
64 : ///see [SlidePercentCallback]
65 : SlidePercentCallback? _slidePercentCallback;
66 :
67 : ///bool variable to set if Liquid Swipe is currently in Progress
68 : bool isInProgress = false;
69 :
70 : ///bool to store is its still animating
71 : bool _isAnimating = false;
72 :
73 : ///A user handled value if user want, just only to use programmatic pages changes
74 : ///default = false
75 : bool shouldDisableUserGesture = false;
76 :
77 : Size iconSize = Size.zero;
78 :
79 : Timer? _timer;
80 : Timer? _timerInner;
81 :
82 : ///Constructor
83 : ///Contains Default value or Developer desired Values
84 : /// [initialPage] - Initial Page of the LiquidSwipe (0 - n)
85 : /// [loop] - Should Enable Loop between Pages
86 : /// [length] - Total Number of Pages
87 1 : LiquidProvider(
88 : {required int initialPage,
89 : required bool loop,
90 : required int length,
91 : required TickerProviderStateMixin vsync,
92 : required double slideIcon,
93 : required OnPageChangeCallback? onPageChangeCallback,
94 : required CurrentUpdateTypeCallback? currentUpdateTypeCallback,
95 : required SlidePercentCallback? slidePercentCallback,
96 : required bool disableGesture}) {
97 1 : slidePercentHor = 0.00;
98 1 : slidePercentVer = 0.00;
99 1 : activePageIndex = initialPage;
100 1 : nextPageIndex = initialPage;
101 1 : enableLoop = loop;
102 1 : pagesLength = length;
103 1 : singleTickerProviderStateMixin = vsync;
104 1 : positionSlideIcon = slideIcon;
105 1 : _currentUpdateTypeCallback = currentUpdateTypeCallback;
106 1 : _slidePercentCallback = slidePercentCallback;
107 1 : _onPageChangeCallback = onPageChangeCallback;
108 1 : shouldDisableUserGesture = disableGesture;
109 :
110 2 : updateSlide(SlideUpdate(
111 : SlideDirection.rightToLeft,
112 : 0.00,
113 1 : positionSlideIcon,
114 : UpdateType.dragging,
115 : ));
116 : }
117 :
118 : ///Animating page to the mentioned page
119 : ///
120 : ///Known Issue : First we have to jump to the previous screen.
121 : ///
122 : ///Current Behaviour : Lets say there are 3 pages,
123 : ///current page is Red and next is Blue and and last is Green.
124 : ///if [page] in this method came to ne 2 i.e., we need to animate directly to Green Page
125 : ///but currently I have to jump to page 1 i.e., Blue and than perform Animation using [updateSlide]
126 : ///
127 : ///Required Behaviour : I Don't want Blue to be there in between the transition of Red and Green i.e.,
128 : ///If we are animating [activePageIndex] should be 0 and [nextPageIndex] should be 2, which is not possible through current implementation.
129 : ///
130 : /// If you encounter this and have suggestions don't forget to raise an Issue.
131 : ///
132 : ///Not making it for Public usage for now due to the mentioned Issue
133 : /// _animateDirectlyToPage(int page, int duration) {
134 : /// if (isInProgress || activePageIndex == page) return;
135 : /// isInProgress = true;
136 : /// activePageIndex = page - 1;
137 : /// nextPageIndex = page;
138 : /// if (activePageIndex < 0) {
139 : /// activePageIndex = 0;
140 : /// jumpToPage(page);
141 : /// return;
142 : /// }
143 : /// _timer = Timer.periodic(const Duration(milliseconds: 1), (t) {
144 : /// if (t.tick < duration / 2) {
145 : /// updateSlide(SlideUpdate(SlideDirection.rightToLeft, t.tick / duration,
146 : /// 1, UpdateType.dragging));
147 : /// } else if (t.tick < duration) {
148 : /// updateSlide(SlideUpdate(SlideDirection.rightToLeft, t.tick / duration,
149 : /// 1, UpdateType.animating));
150 : /// } else {
151 : /// updateSlide(SlideUpdate(
152 : /// SlideDirection.rightToLeft, 1, 1, UpdateType.doneAnimating));
153 : /// t.cancel();
154 : /// isInProgress = false;
155 : /// }
156 : /// });
157 : /// }
158 : ///
159 : ///Animating to the Page in One-by-One manner
160 : ///Required parameters :
161 : /// - [page], the page index you want to animate to.
162 : /// - [duration], of [Duration] type, for complete animation
163 1 : animateToPage(int page, int duration) {
164 3 : if (isInProgress || activePageIndex == page) return;
165 1 : isInProgress = true;
166 : int diff = 0;
167 1 : _timer?.cancel();
168 1 : _timerInner?.cancel();
169 2 : if (activePageIndex < page) {
170 2 : diff = page - activePageIndex;
171 1 : int newDuration = duration ~/ diff;
172 3 : _timer = Timer.periodic(Duration(milliseconds: newDuration), (callback) {
173 0 : _timerInner = Timer.periodic(const Duration(milliseconds: 1), (t) {
174 0 : if (t.tick < newDuration / 2) {
175 0 : updateSlide(SlideUpdate(SlideDirection.rightToLeft,
176 0 : t.tick / newDuration, positionSlideIcon, UpdateType.dragging));
177 0 : } else if (t.tick < newDuration) {
178 0 : updateSlide(SlideUpdate(SlideDirection.rightToLeft,
179 0 : t.tick / newDuration, positionSlideIcon, UpdateType.animating));
180 : } else {
181 0 : updateSlide(SlideUpdate(SlideDirection.rightToLeft, 1,
182 0 : positionSlideIcon, UpdateType.doneAnimating));
183 0 : t.cancel();
184 : }
185 : });
186 0 : if (callback.tick >= diff) {
187 0 : callback.cancel();
188 0 : isInProgress = false;
189 : }
190 : });
191 : } else {
192 2 : diff = activePageIndex - page;
193 1 : int newDuration = duration ~/ diff;
194 3 : _timer = Timer.periodic(Duration(milliseconds: newDuration), (callback) {
195 0 : _timerInner = Timer.periodic(const Duration(milliseconds: 1), (t) {
196 0 : if (t.tick < newDuration / 2) {
197 0 : updateSlide(SlideUpdate(SlideDirection.leftToRight,
198 0 : t.tick / newDuration, positionSlideIcon, UpdateType.dragging));
199 0 : } else if (t.tick < newDuration) {
200 0 : updateSlide(SlideUpdate(SlideDirection.leftToRight,
201 0 : t.tick / newDuration, positionSlideIcon, UpdateType.animating));
202 : } else {
203 0 : updateSlide(SlideUpdate(SlideDirection.leftToRight, 1,
204 0 : positionSlideIcon, UpdateType.doneAnimating));
205 0 : t.cancel();
206 : }
207 : });
208 0 : if (callback.tick >= diff) {
209 0 : callback.cancel();
210 0 : isInProgress = false;
211 : }
212 : });
213 : }
214 : }
215 :
216 : ///Directly Jump to the mentioned [page] without any animation
217 1 : jumpToPage(int page) {
218 3 : if (page == activePageIndex || isInProgress) return;
219 4 : if (page > pagesLength - 1 || page < 0) {
220 0 : throw ("Index $page not found in the Pages list");
221 : }
222 1 : isInProgress = true;
223 2 : activePageIndex = page - 1;
224 1 : nextPageIndex = page;
225 3 : if (nextPageIndex >= pagesLength) nextPageIndex = 0;
226 3 : updateSlide(SlideUpdate(SlideDirection.rightToLeft, 1, positionSlideIcon,
227 : UpdateType.doneAnimating));
228 1 : isInProgress = false;
229 : }
230 :
231 : ///Method to update the [slideUpdate] and it directly calls [updateData]
232 1 : updateSlide(SlideUpdate slidUpdate) {
233 1 : slideUpdate = slidUpdate;
234 1 : updateData(slidUpdate);
235 1 : notifyListeners();
236 : }
237 :
238 : ///updating data using [SlideUpdate], generally we are handling and managing the Animation [UpdateType]
239 : ///in this methods,
240 : ///All callbacks and factors are also managed by this method.
241 1 : updateData(SlideUpdate event) {
242 4 : if (prevUpdate != event.updateType && _currentUpdateTypeCallback != null)
243 3 : _currentUpdateTypeCallback!(event.updateType);
244 :
245 1 : if (_slidePercentCallback != null &&
246 2 : event.updateType != UpdateType.doneAnimating) {
247 3 : String hor = (event.slidePercentHor * 100).toStringAsExponential(2);
248 3 : String ver = (event.slidePercentVer * 100).toStringAsExponential(2);
249 2 : _slidePercentCallback!(
250 4 : double.parse(hor), (((double.parse(ver)) * 100) / 100));
251 : }
252 :
253 2 : prevUpdate = event.updateType;
254 :
255 : //if the user is dragging then
256 2 : if (event.updateType == UpdateType.dragging) {
257 2 : slideDirection = event.direction;
258 2 : slidePercentHor = event.slidePercentHor;
259 2 : slidePercentVer = event.slidePercentVer;
260 :
261 : // making pages to be in loop
262 2 : nextPageIndex = activePageIndex;
263 1 : if (enableLoop) {
264 : //conditions on slide direction
265 2 : if (slideDirection == SlideDirection.leftToRight) {
266 3 : nextPageIndex = activePageIndex - 1;
267 2 : } else if (slideDirection == SlideDirection.rightToLeft) {
268 3 : nextPageIndex = activePageIndex + 1;
269 : }
270 :
271 4 : if (nextPageIndex > pagesLength - 1) {
272 0 : nextPageIndex = 0;
273 2 : } else if (nextPageIndex < 0) {
274 3 : nextPageIndex = pagesLength - 1;
275 : }
276 : return;
277 : }
278 :
279 : //conditions on slide direction
280 2 : if (slideDirection == SlideDirection.leftToRight &&
281 0 : activePageIndex != 0) {
282 0 : nextPageIndex = activePageIndex - 1;
283 2 : } else if (slideDirection == SlideDirection.rightToLeft &&
284 4 : activePageIndex != pagesLength - 1) {
285 3 : nextPageIndex = activePageIndex + 1;
286 : }
287 : return;
288 : }
289 : //if the user has done dragging
290 2 : else if (event.updateType == UpdateType.doneDragging) {
291 : // slidepercent > 0.2 so that it wont reveal itself unless this condition is true
292 2 : if (slidePercentHor > 0.2) {
293 1 : isAnimating = true; // Page started to animate
294 :
295 2 : animatedPageDragger = AnimatedPageDragger(
296 : slideUpdateStream: this,
297 1 : slideDirection: slideDirection,
298 : transitionGoal: TransitionGoal.open,
299 1 : slidePercentHor: slidePercentHor,
300 1 : slidePercentVer: slidePercentVer,
301 1 : vsync: singleTickerProviderStateMixin,
302 : );
303 : } else {
304 2 : animatedPageDragger = AnimatedPageDragger(
305 : slideUpdateStream: this,
306 1 : slideDirection: slideDirection,
307 : transitionGoal: TransitionGoal.close,
308 1 : slidePercentHor: slidePercentHor,
309 1 : slidePercentVer: slidePercentVer,
310 1 : vsync: singleTickerProviderStateMixin,
311 : );
312 :
313 2 : nextPageIndex = activePageIndex;
314 : }
315 : //Run the animation
316 2 : animatedPageDragger.run();
317 : return;
318 : }
319 : //when animating
320 2 : else if (event.updateType == UpdateType.animating) {
321 2 : slideDirection = event.direction;
322 2 : slidePercentHor = event.slidePercentHor;
323 2 : slidePercentVer = event.slidePercentVer;
324 : return;
325 : }
326 :
327 : //done animating
328 1 : if (_onPageChangeCallback != null) {
329 3 : _onPageChangeCallback!(nextPageIndex);
330 : }
331 2 : activePageIndex = nextPageIndex;
332 1 : slideDirection = SlideDirection.rightToLeft;
333 1 : slidePercentHor = 0.00;
334 2 : slidePercentVer = positionSlideIcon;
335 2 : nextPageIndex = activePageIndex;
336 :
337 1 : if (enableLoop) {
338 : //conditions on slide direction
339 2 : if (slideDirection == SlideDirection.leftToRight) {
340 0 : nextPageIndex = activePageIndex - 1;
341 2 : } else if (slideDirection == SlideDirection.rightToLeft) {
342 3 : nextPageIndex = activePageIndex + 1;
343 : }
344 :
345 4 : if (nextPageIndex > pagesLength - 1) {
346 1 : nextPageIndex = 0;
347 2 : } else if (nextPageIndex < 0) {
348 0 : nextPageIndex = pagesLength - 1;
349 : }
350 : } else {
351 2 : if (slideDirection == SlideDirection.leftToRight &&
352 0 : activePageIndex != 0) {
353 0 : nextPageIndex = activePageIndex - 1;
354 2 : } else if (slideDirection == SlideDirection.rightToLeft &&
355 4 : activePageIndex != pagesLength - 1) {
356 0 : nextPageIndex = activePageIndex + 1;
357 : }
358 : }
359 :
360 1 : isAnimating = false; // Page stopped animating
361 : return;
362 : }
363 :
364 : ///Setter for [_isAnimating]
365 1 : set isAnimating(bool newValue) {
366 1 : this._isAnimating = newValue;
367 1 : notifyListeners();
368 : }
369 :
370 : ///Getter for [_isAnimating]
371 2 : bool get isAnimating => _isAnimating;
372 :
373 : ///Setter for [isUserGestureDisabled]
374 1 : set setUserGesture(bool disable) {
375 1 : this.shouldDisableUserGesture = disable;
376 1 : notifyListeners();
377 : }
378 :
379 : ///Setter for [isUserGestureDisabled]
380 2 : bool get isUserGestureDisabled => shouldDisableUserGesture;
381 :
382 : ///Method to set [iconSize]
383 1 : setIconSize(Size size) {
384 1 : iconSize = size;
385 1 : notifyListeners();
386 : }
387 :
388 1 : @override
389 : void dispose() {
390 2 : _timer?.cancel();
391 1 : _timerInner?.cancel();
392 1 : super.dispose();
393 : }
394 : }
|