Skip to content

Commit 9225f4e

Browse files
committed
update
1 parent dd9e3a9 commit 9225f4e

File tree

4 files changed

+108
-71
lines changed

4 files changed

+108
-71
lines changed

lib/main.dart

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,25 @@ void main() {
99
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1010

1111
final class HomeRouteState extends RouteState {
12-
HomeRouteState() : super.parse('/home');
12+
HomeRouteState() : super.parse('/home', animationEffect: BottomToTopEffect());
1313
}
1414

1515
final class MessagesRouteState extends RouteState {
16-
MessagesRouteState() : super.parse('/messages');
16+
MessagesRouteState() : super.parse('/messages', animationEffect: QuickLeftToRightEffect());
1717
}
1818

1919
final class MessagesRouteState1 extends RouteState {
20-
MessagesRouteState1() : super.parse('/messages?key1=value1');
20+
MessagesRouteState1()
21+
: super.parse('/messages?key1=value1', animationEffect: QuickLeftToRightEffect());
2122
}
2223

2324
final class MessagesRouteState2 extends RouteState {
24-
MessagesRouteState2() : super.parse('/messages?key1=value1', queryParameters: {'key2': 'value2'});
25+
MessagesRouteState2()
26+
: super.parse(
27+
'/messages?key1=value1',
28+
queryParameters: {'key2': 'value2'},
29+
animationEffect: QuickLeftToRightEffect(),
30+
);
2531
}
2632

2733
class MyApp extends StatelessWidget {
@@ -92,25 +98,6 @@ class MyApp extends StatelessWidget {
9298
// ],
9399
// );
94100
// },
95-
// transitionBuilder: (context, params) {
96-
// // For iOS.
97-
// // return Expanded(
98-
// // child: HorizontalSlideFadeTransition(
99-
// // // Prev is a capture of the previous page.
100-
// // prev: params.prev,
101-
// // controller: params.controller,
102-
// // duration: const Duration(milliseconds: 300),
103-
// // child: params.child,
104-
// // ),
105-
// // );
106-
// // For Android.
107-
// return VerticalSlideFadeTransition(
108-
// prev: params.prevSnapshot,
109-
// controller: params.controller,
110-
// duration: const Duration(milliseconds: 300),
111-
// child: params.child,
112-
// );
113-
// },
114101
builders: [
115102
RouteBuilder(
116103
routeState: HomeRouteState(),
@@ -132,7 +119,7 @@ class MyApp extends StatelessWidget {
132119
// Pre-builds the widget even if the RouteState is not at the top of
133120
// the stack. This is useful for RouteStates that are frequently
134121
// navigated to or that takes some time to build.
135-
//shouldPrebuild: true,
122+
shouldPrebuild: true,
136123
builder: (context, state) {
137124
return ChatScreen(routeState: state);
138125
},
@@ -205,7 +192,7 @@ class _MessagesScreenState extends State<MessagesScreen> {
205192
FilledButton(
206193
onPressed:
207194
() => controller.push(
208-
RouteState.parse('/messages?key1=value1').copyWith(shouldAnimate: true),
195+
RouteState.parse('/messages?key1=value1').copyWith(animationEffect: NoEffect()),
209196
),
210197
child: const Text('Go to Messages (key1=value1)'),
211198
),
@@ -215,7 +202,7 @@ class _MessagesScreenState extends State<MessagesScreen> {
215202
() => controller.push(
216203
MessagesRouteState2().copyWith(
217204
extra: 'HELLO THERE HOW ARE YOU?',
218-
shouldAnimate: true,
205+
animationEffect: NoEffect(),
219206
),
220207
),
221208
child: const Text('Go to Messages (key2=value2)'),
@@ -259,7 +246,11 @@ class HomeScreen extends StatelessWidget with RouteWidgetMixin {
259246
child: const Text('Go to Messages (key2=value2)'),
260247
),
261248
FilledButton(
262-
onPressed: () => controller.push(RouteState.parse('/detail')),
249+
onPressed:
250+
() => controller.push(
251+
RouteState.parse('/detail'),
252+
animationEffect: BounceOutEffect(),
253+
),
263254
child: const Text('Go to Home Detail'),
264255
),
265256
FilledButton(

lib/src/effects/effects.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class NoEffect extends AnimationEffect {
88
duration: const Duration(milliseconds: 300),
99
curve: Curves.linear,
1010
data: (context, value) {
11-
return [const AnimationLayerEffect(), const AnimationLayerEffect()];
11+
return const [AnimationLayerEffect(), AnimationLayerEffect()];
1212
},
1313
);
1414
}
@@ -110,17 +110,16 @@ class TopToBottomEffect extends AnimationEffect {
110110
);
111111
}
112112

113-
class GravityBounceEffect extends AnimationEffect {
114-
GravityBounceEffect()
113+
class BounceOutEffect extends AnimationEffect {
114+
BounceOutEffect()
115115
: super(
116-
duration: const Duration(milliseconds: 900),
116+
duration: Duration.zero, // makes no difference, as the curve is bounceOut
117117
curve: Curves.bounceOut,
118118
data: (context, value) {
119119
final size = MediaQuery.sizeOf(context);
120-
final height75 = size.height * 0.75;
121120
return [
122121
AnimationLayerEffect(
123-
transform: Matrix4.translationValues(0.0, -height75 + height75 * value, 0.0),
122+
transform: Matrix4.translationValues(0.0, -size.height + size.height * value, 0.0),
124123
),
125124
const AnimationLayerEffect(ignorePointer: true),
126125
];

lib/src/route_controller.dart

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@
1212

1313
// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
1414

15-
import 'dart:math' as math;
16-
import 'dart:ui';
17-
1815
import 'package:df_pwa_utils/df_pwa_utils.dart';
1916
import 'package:df_widgets/_common.dart';
20-
import 'package:flutter/material.dart';
2117

2218
import '_src.g.dart';
2319

@@ -42,6 +38,8 @@ class RouteController {
4238
final RouteState Function() fallbackRouteState;
4339
RouteState? _requestedRouteState;
4440

41+
AnimationEffect nextEffect = NoEffect();
42+
4543
//
4644
//
4745
//
@@ -52,13 +50,12 @@ class RouteController {
5250
required this.fallbackRouteState,
5351
required this.builders,
5452
}) {
53+
platformNavigator.addStateCallback(pushUri);
54+
// Set all the builder output to SizedBox.shrink.
55+
resetState();
5556
_requestedRouteState = getNavigatorRouteState();
5657
final routeState = initialRouteState?.call() ?? _requestedRouteState ?? fallbackRouteState();
57-
for (final builder in builders) {
58-
_widgetCache[builder.routeState] = SizedBox.shrink(key: builder.routeState.key);
59-
}
60-
platformNavigator.addStateCallback(pushUri);
61-
push(routeState, shouldAnimate: false);
58+
push(routeState);
6259
}
6360

6461
//
@@ -108,7 +105,7 @@ class RouteController {
108105
final builder = _getBuilderByPath(routeState.uri);
109106
if (builder == null) continue;
110107
if (_widgetCache[routeState] is SizedBox) continue;
111-
_widgetCache[routeState] = SizedBox.shrink(key: _widgetCache[routeState]?.key);
108+
_widgetCache[routeState] = SizedBox.shrink(key: routeState.key);
112109
_pRouteState.notifyListeners();
113110
}
114111
}
@@ -118,20 +115,34 @@ class RouteController {
118115
//
119116

120117
void clearCache() {
121-
removeStatesFromCache(_widgetCache.keys);
118+
for (final builder in builders) {
119+
_widgetCache[builder.routeState] = SizedBox.shrink(key: builder.routeState.key);
120+
}
121+
}
122+
123+
//
124+
//
125+
//
126+
127+
void resetState() {
128+
clearCache();
129+
final routeStates = builders
130+
.where((builder) => builder.shouldPrebuild)
131+
.map((e) => e.routeState);
132+
addStatesToCache(routeStates);
122133
}
123134

124135
//
125136
//
126137
//
127138

128-
// void resetCache() {
129-
// clearCache();
130-
// final routeStates = builders
131-
// .where((builder) => builder.shouldPrebuild)
132-
// .map((e) => e.routeState);
133-
// cacheStates(routeStates);
134-
// }
139+
void _maybeRemoveStaleRoute(RouteState routeState) {
140+
final builder = _getBuilderByPath(routeState.uri);
141+
if (builder == null) return;
142+
if (!builder.shouldPreserve) {
143+
_widgetCache[routeState] = SizedBox.shrink(key: routeState.key);
144+
}
145+
}
135146

136147
//
137148
//
@@ -159,8 +170,9 @@ class RouteController {
159170
RouteState<TExtra> routeState, {
160171
RouteState? errorFallback,
161172
RouteState? fallback,
162-
bool shouldAnimate = true,
173+
AnimationEffect? animationEffect,
163174
}) {
175+
nextEffect = animationEffect ?? routeState.animationEffect ?? NoEffect();
164176
print('PUSHING!!!');
165177
final uri = routeState.uri;
166178
final skipCurrent = routeState.skipCurrent;
@@ -198,20 +210,16 @@ class RouteController {
198210
return;
199211
}
200212
platformNavigator.pushState(uri);
213+
201214
_prevRouteState = _pRouteState.value;
215+
216+
// Remove the previous route state from the cache if it is stale.
217+
202218
_pRouteState.value = routeState;
203219
addStatesToCache([routeState]);
204220

205-
globalKey.currentState?.setControllerValues(shouldAnimate ? 0.0 : 1.0);
221+
globalKey.currentState?.setControllerValues(0.0);
206222
globalKey.currentState?.forward();
207-
208-
//controller.startAnimation();
209-
// final shouldAnimate = routeState.shouldAnimate;
210-
// if (shouldAnimate) {
211-
// Future.microtask(() {
212-
// _controller.reset();
213-
// });
214-
// }
215223
}
216224

217225
//
@@ -249,7 +257,10 @@ class RouteController {
249257
Widget buildScreen(BuildContext context, RouteState routeState) {
250258
return AnimationEffectBuilder(
251259
key: globalKey,
252-
effects: [FadeEffect()],
260+
effects: [nextEffect],
261+
onComplete: () {
262+
_maybeRemoveStaleRoute(_prevRouteState);
263+
},
253264
builder: (context, results) {
254265
final layerEffects = results.map((e) => e.data).toList()[0];
255266
return PrioritizedIndexedStack(
@@ -287,6 +298,7 @@ class RouteController {
287298
}
288299
}
289300

301+
// Assuming AnimationEffect and AnimationLayerEffect are defined as in the provided code
290302
abstract class AnimationEffect {
291303
final Duration duration;
292304
final Curve curve;
@@ -305,8 +317,14 @@ class LayerEffectResult {
305317
class AnimationEffectBuilder extends StatefulWidget {
306318
final List<AnimationEffect> effects;
307319
final Widget Function(BuildContext context, List<LayerEffectResult> results) builder;
320+
final VoidCallback? onComplete; // New callback parameter
308321

309-
const AnimationEffectBuilder({super.key, required this.effects, required this.builder});
322+
const AnimationEffectBuilder({
323+
super.key,
324+
required this.effects,
325+
required this.builder,
326+
this.onComplete,
327+
});
310328

311329
@override
312330
AnimationEffectBuilderState createState() => AnimationEffectBuilderState();
@@ -316,23 +334,27 @@ class AnimationEffectBuilderState extends State<AnimationEffectBuilder>
316334
with TickerProviderStateMixin {
317335
late List<AnimationController> controllers;
318336
late List<Animation<double>> animations;
337+
bool _hasTriggeredCompletion = false; // Track if callback has been triggered
319338

320339
void setControllerValues(double value) {
321340
for (final controller in controllers) {
322341
controller.value = value;
323342
}
343+
_hasTriggeredCompletion = false; // Reset completion state
324344
}
325345

326346
void forwardControllers() {
327347
for (final controller in controllers) {
328348
controller.forward();
329349
}
350+
_hasTriggeredCompletion = false; // Reset completion state
330351
}
331352

332353
void reverseControllers() {
333354
for (final controller in controllers) {
334355
controller.reverse();
335356
}
357+
_hasTriggeredCompletion = false; // Reset completion state
336358
}
337359

338360
@override
@@ -344,7 +366,14 @@ class AnimationEffectBuilderState extends State<AnimationEffectBuilder>
344366
void _initializeAnimations() {
345367
controllers =
346368
widget.effects.map((config) {
347-
return AnimationController(vsync: this, duration: config.duration, value: 1.0);
369+
final controller = AnimationController(
370+
vsync: this,
371+
duration: config.duration,
372+
value: 1.0,
373+
);
374+
// Add status listener to track completion
375+
controller.addStatusListener(_handleAnimationStatus);
376+
return controller;
348377
}).toList();
349378
animations =
350379
widget.effects.asMap().entries.map((entry) {
@@ -354,11 +383,25 @@ class AnimationEffectBuilderState extends State<AnimationEffectBuilder>
354383
}).toList();
355384
}
356385

386+
void _handleAnimationStatus(AnimationStatus status) {
387+
// Check if all controllers are completed
388+
if (status == AnimationStatus.completed && !_hasTriggeredCompletion) {
389+
final allCompleted = controllers.every(
390+
(controller) => controller.status == AnimationStatus.completed,
391+
);
392+
if (allCompleted) {
393+
widget.onComplete?.call();
394+
_hasTriggeredCompletion = true; // Prevent multiple triggers
395+
}
396+
}
397+
}
398+
357399
@override
358400
void didUpdateWidget(AnimationEffectBuilder oldWidget) {
359401
super.didUpdateWidget(oldWidget);
360402
if (widget.effects.length != oldWidget.effects.length) {
361403
for (var controller in controllers) {
404+
controller.removeStatusListener(_handleAnimationStatus);
362405
controller.dispose();
363406
}
364407
_initializeAnimations();
@@ -372,11 +415,13 @@ class AnimationEffectBuilderState extends State<AnimationEffectBuilder>
372415
}
373416
}
374417
}
418+
_hasTriggeredCompletion = false; // Reset completion state on widget update
375419
}
376420

377421
@override
378422
void dispose() {
379423
for (final controller in controllers) {
424+
controller.removeStatusListener(_handleAnimationStatus);
380425
controller.dispose();
381426
}
382427
super.dispose();
@@ -386,12 +431,14 @@ class AnimationEffectBuilderState extends State<AnimationEffectBuilder>
386431
for (final controller in controllers) {
387432
controller.value = 0.0;
388433
}
434+
_hasTriggeredCompletion = false; // Reset completion state
389435
}
390436

391437
void forward() {
392438
for (final controller in controllers) {
393439
controller.forward();
394440
}
441+
_hasTriggeredCompletion = false; // Reset completion state
395442
}
396443

397444
@override

0 commit comments

Comments
 (0)