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/SlideUpdate.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 : bool enableSideReveal = false;
78 :
79 : Size iconSize = Size.zero;
80 :
81 : Timer? _timer;
82 : Timer? _timerInner;
83 :
84 : ///Constructor
85 : ///Contains Default value or Developer desired Values
86 : /// [initialPage] - Initial Page of the LiquidSwipe (0 - n)
87 : /// [loop] - Should Enable Loop between Pages
88 : /// [length] - Total Number of Pages
89 1 : LiquidProvider({
90 : required int initialPage,
91 : required bool loop,
92 : required int length,
93 : required TickerProviderStateMixin vsync,
94 : required double slideIcon,
95 : required OnPageChangeCallback? onPageChangeCallback,
96 : required CurrentUpdateTypeCallback? currentUpdateTypeCallback,
97 : required SlidePercentCallback? slidePercentCallback,
98 : required bool disableGesture,
99 : required bool enableSideReveal,
100 : }) {
101 1 : slidePercentHor = 0.00;
102 1 : slidePercentVer = 0.00;
103 1 : activePageIndex = initialPage;
104 1 : nextPageIndex = initialPage;
105 1 : enableLoop = loop;
106 1 : pagesLength = length;
107 1 : singleTickerProviderStateMixin = vsync;
108 1 : positionSlideIcon = slideIcon;
109 1 : _currentUpdateTypeCallback = currentUpdateTypeCallback;
110 1 : _slidePercentCallback = slidePercentCallback;
111 1 : _onPageChangeCallback = onPageChangeCallback;
112 1 : shouldDisableUserGesture = disableGesture;
113 1 : this.enableSideReveal = enableSideReveal;
114 :
115 2 : updateSlide(SlideUpdate(
116 : SlideDirection.rightToLeft,
117 : 0.00,
118 1 : positionSlideIcon,
119 : UpdateType.dragging,
120 : ));
121 : }
122 :
123 : ///Animating page to the mentioned page
124 : ///
125 : ///Known Issue : First we have to jump to the previous screen.
126 : ///
127 : ///Current Behaviour : Lets say there are 3 pages,
128 : ///current page is Red and next is Blue and and last is Green.
129 : ///if [page] in this method came to ne 2 i.e., we need to animate directly to Green Page
130 : ///but currently I have to jump to page 1 i.e., Blue and than perform Animation using [updateSlide]
131 : ///
132 : ///Required Behaviour : I Don't want Blue to be there in between the transition of Red and Green i.e.,
133 : ///If we are animating [activePageIndex] should be 0 and [nextPageIndex] should be 2, which is not possible through current implementation.
134 : ///
135 : /// If you encounter this and have suggestions don't forget to raise an Issue.
136 : ///
137 : ///Not making it for Public usage for now due to the mentioned Issue
138 : /// _animateDirectlyToPage(int page, int duration) {
139 : /// if (isInProgress || activePageIndex == page) return;
140 : /// isInProgress = true;
141 : /// activePageIndex = page - 1;
142 : /// nextPageIndex = page;
143 : /// if (activePageIndex < 0) {
144 : /// activePageIndex = 0;
145 : /// jumpToPage(page);
146 : /// return;
147 : /// }
148 : /// _timer = Timer.periodic(const Duration(milliseconds: 1), (t) {
149 : /// if (t.tick < duration / 2) {
150 : /// updateSlide(SlideUpdate(SlideDirection.rightToLeft, t.tick / duration,
151 : /// 1, UpdateType.dragging));
152 : /// } else if (t.tick < duration) {
153 : /// updateSlide(SlideUpdate(SlideDirection.rightToLeft, t.tick / duration,
154 : /// 1, UpdateType.animating));
155 : /// } else {
156 : /// updateSlide(SlideUpdate(
157 : /// SlideDirection.rightToLeft, 1, 1, UpdateType.doneAnimating));
158 : /// t.cancel();
159 : /// isInProgress = false;
160 : /// }
161 : /// });
162 : /// }
163 : ///
164 : ///Animating to the Page in One-by-One manner
165 : ///Required parameters :
166 : /// - [page], the page index you want to animate to.
167 : /// - [duration], of [Duration] type, for complete animation
168 1 : animateToPage(int page, int duration) {
169 3 : if (isInProgress || activePageIndex == page) return;
170 1 : isInProgress = true;
171 : int diff = 0;
172 1 : _timer?.cancel();
173 1 : _timerInner?.cancel();
174 2 : if (activePageIndex < page) {
175 2 : diff = page - activePageIndex;
176 1 : int newDuration = duration ~/ diff;
177 3 : _timer = Timer.periodic(Duration(milliseconds: newDuration), (callback) {
178 0 : _timerInner = Timer.periodic(const Duration(milliseconds: 1), (t) {
179 0 : if (t.tick < newDuration / 2) {
180 0 : updateSlide(SlideUpdate(SlideDirection.rightToLeft,
181 0 : t.tick / newDuration, positionSlideIcon, UpdateType.dragging));
182 0 : } else if (t.tick < newDuration) {
183 0 : updateSlide(SlideUpdate(SlideDirection.rightToLeft,
184 0 : t.tick / newDuration, positionSlideIcon, UpdateType.animating));
185 : } else {
186 0 : updateSlide(SlideUpdate(SlideDirection.rightToLeft, 1,
187 0 : positionSlideIcon, UpdateType.doneAnimating));
188 0 : t.cancel();
189 : }
190 : });
191 0 : if (callback.tick >= diff) {
192 0 : callback.cancel();
193 0 : isInProgress = false;
194 : }
195 : });
196 : } else {
197 2 : diff = activePageIndex - page;
198 1 : int newDuration = duration ~/ diff;
199 3 : _timer = Timer.periodic(Duration(milliseconds: newDuration), (callback) {
200 0 : _timerInner = Timer.periodic(const Duration(milliseconds: 1), (t) {
201 0 : if (t.tick < newDuration / 2) {
202 0 : updateSlide(SlideUpdate(SlideDirection.leftToRight,
203 0 : t.tick / newDuration, positionSlideIcon, UpdateType.dragging));
204 0 : } else if (t.tick < newDuration) {
205 0 : updateSlide(SlideUpdate(SlideDirection.leftToRight,
206 0 : t.tick / newDuration, positionSlideIcon, UpdateType.animating));
207 : } else {
208 0 : updateSlide(SlideUpdate(SlideDirection.leftToRight, 1,
209 0 : positionSlideIcon, UpdateType.doneAnimating));
210 0 : t.cancel();
211 : }
212 : });
213 0 : if (callback.tick >= diff) {
214 0 : callback.cancel();
215 0 : isInProgress = false;
216 : }
217 : });
218 : }
219 : }
220 :
221 : ///Directly Jump to the mentioned [page] without any animation
222 1 : jumpToPage(int page) {
223 3 : if (page == activePageIndex || isInProgress) return;
224 4 : if (page > pagesLength - 1 || page < 0) {
225 0 : throw ("Index $page not found in the Pages list");
226 : }
227 1 : isInProgress = true;
228 2 : activePageIndex = page - 1;
229 1 : nextPageIndex = page;
230 3 : if (nextPageIndex >= pagesLength) nextPageIndex = 0;
231 3 : updateSlide(SlideUpdate(SlideDirection.rightToLeft, 1, positionSlideIcon,
232 : UpdateType.doneAnimating));
233 1 : isInProgress = false;
234 : }
235 :
236 : ///Method to update the [slideUpdate] and it directly calls [updateData]
237 1 : updateSlide(SlideUpdate slidUpdate) {
238 1 : slideUpdate = slidUpdate;
239 1 : updateData(slidUpdate);
240 1 : notifyListeners();
241 : }
242 :
243 : ///updating data using [SlideUpdate], generally we are handling and managing the Animation [UpdateType]
244 : ///in this methods,
245 : ///All callbacks and factors are also managed by this method.
246 1 : updateData(SlideUpdate event) {
247 4 : if (prevUpdate != event.updateType && _currentUpdateTypeCallback != null)
248 3 : _currentUpdateTypeCallback!(event.updateType);
249 :
250 1 : if (_slidePercentCallback != null &&
251 2 : event.updateType != UpdateType.doneAnimating) {
252 3 : String hor = (event.slidePercentHor * 100).toStringAsExponential(2);
253 3 : String ver = (event.slidePercentVer * 100).toStringAsExponential(2);
254 2 : _slidePercentCallback!(
255 4 : double.parse(hor), (((double.parse(ver)) * 100) / 100));
256 : }
257 :
258 2 : prevUpdate = event.updateType;
259 :
260 : //if the user is dragging then
261 2 : if (event.updateType == UpdateType.dragging) {
262 2 : slideDirection = event.direction;
263 2 : slidePercentHor = event.slidePercentHor;
264 2 : slidePercentVer = event.slidePercentVer;
265 :
266 : // making pages to be in loop
267 2 : nextPageIndex = activePageIndex;
268 1 : if (enableLoop) {
269 : //conditions on slide direction
270 2 : if (slideDirection == SlideDirection.leftToRight) {
271 3 : nextPageIndex = activePageIndex - 1;
272 2 : } else if (slideDirection == SlideDirection.rightToLeft) {
273 3 : nextPageIndex = activePageIndex + 1;
274 : }
275 :
276 4 : if (nextPageIndex > pagesLength - 1) {
277 0 : nextPageIndex = 0;
278 2 : } else if (nextPageIndex < 0) {
279 3 : nextPageIndex = pagesLength - 1;
280 : }
281 : return;
282 : }
283 :
284 : //conditions on slide direction
285 2 : if (slideDirection == SlideDirection.leftToRight &&
286 0 : activePageIndex != 0) {
287 0 : nextPageIndex = activePageIndex - 1;
288 2 : } else if (slideDirection == SlideDirection.rightToLeft &&
289 4 : activePageIndex != pagesLength - 1) {
290 3 : nextPageIndex = activePageIndex + 1;
291 : }
292 : return;
293 : }
294 : //if the user has done dragging
295 2 : else if (event.updateType == UpdateType.doneDragging) {
296 : // slidepercent > 0.2 so that it wont reveal itself unless this condition is true
297 2 : if (slidePercentHor > 0.2) {
298 1 : isAnimating = true; // Page started to animate
299 :
300 2 : animatedPageDragger = AnimatedPageDragger(
301 : slideUpdateStream: this,
302 1 : slideDirection: slideDirection,
303 : transitionGoal: TransitionGoal.open,
304 1 : slidePercentHor: slidePercentHor,
305 1 : slidePercentVer: slidePercentVer,
306 1 : vsync: singleTickerProviderStateMixin,
307 : );
308 : } else {
309 2 : animatedPageDragger = AnimatedPageDragger(
310 : slideUpdateStream: this,
311 1 : slideDirection: slideDirection,
312 : transitionGoal: TransitionGoal.close,
313 1 : slidePercentHor: slidePercentHor,
314 1 : slidePercentVer: slidePercentVer,
315 1 : vsync: singleTickerProviderStateMixin,
316 : );
317 :
318 2 : nextPageIndex = activePageIndex;
319 : }
320 : //Run the animation
321 2 : animatedPageDragger.run();
322 : return;
323 : }
324 : //when animating
325 2 : else if (event.updateType == UpdateType.animating) {
326 2 : slideDirection = event.direction;
327 2 : slidePercentHor = event.slidePercentHor;
328 2 : slidePercentVer = event.slidePercentVer;
329 : return;
330 : }
331 :
332 : //done animating
333 1 : if (_onPageChangeCallback != null) {
334 3 : _onPageChangeCallback!(nextPageIndex);
335 : }
336 2 : activePageIndex = nextPageIndex;
337 1 : slideDirection = SlideDirection.rightToLeft;
338 1 : slidePercentHor = 0.00;
339 2 : slidePercentVer = positionSlideIcon;
340 2 : nextPageIndex = activePageIndex;
341 :
342 1 : if (enableLoop) {
343 : //conditions on slide direction
344 2 : if (slideDirection == SlideDirection.leftToRight) {
345 0 : nextPageIndex = activePageIndex - 1;
346 2 : } else if (slideDirection == SlideDirection.rightToLeft) {
347 3 : nextPageIndex = activePageIndex + 1;
348 : }
349 :
350 4 : if (nextPageIndex > pagesLength - 1) {
351 1 : nextPageIndex = 0;
352 2 : } else if (nextPageIndex < 0) {
353 0 : nextPageIndex = pagesLength - 1;
354 : }
355 : } else {
356 2 : if (slideDirection == SlideDirection.leftToRight &&
357 0 : activePageIndex != 0) {
358 0 : nextPageIndex = activePageIndex - 1;
359 2 : } else if (slideDirection == SlideDirection.rightToLeft &&
360 4 : activePageIndex != pagesLength - 1) {
361 0 : nextPageIndex = activePageIndex + 1;
362 : }
363 : }
364 :
365 1 : isAnimating = false; // Page stopped animating
366 : return;
367 : }
368 :
369 : ///Setter for [_isAnimating]
370 1 : set isAnimating(bool newValue) {
371 1 : this._isAnimating = newValue;
372 1 : notifyListeners();
373 : }
374 :
375 : ///Getter for [_isAnimating]
376 2 : bool get isAnimating => _isAnimating;
377 :
378 : ///Setter for [isUserGestureDisabled]
379 1 : set setUserGesture(bool disable) {
380 1 : this.shouldDisableUserGesture = disable;
381 1 : notifyListeners();
382 : }
383 :
384 : ///Setter for [isUserGestureDisabled]
385 2 : bool get isUserGestureDisabled => shouldDisableUserGesture;
386 :
387 : ///Method to set [iconSize]
388 1 : setIconSize(Size size) {
389 1 : iconSize = size;
390 1 : notifyListeners();
391 : }
392 :
393 1 : @override
394 : void dispose() {
395 2 : _timer?.cancel();
396 1 : _timerInner?.cancel();
397 1 : super.dispose();
398 : }
399 : }
|