Skip to content

Commit 77608a3

Browse files
authored
Merge pull request #522 from DevKor-github/feature/natural-route-transitions
[codex] Normalize route transitions
2 parents 8ae2e8e + 67584cd commit 77608a3

3 files changed

Lines changed: 357 additions & 53 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:go_router/go_router.dart';
3+
4+
enum AppRouteTransition {
5+
fade,
6+
standard,
7+
bottomNavFromLeft,
8+
bottomNavFromRight,
9+
scheduleFlow,
10+
}
11+
12+
CustomTransitionPage<T> buildAppRoutePage<T>({
13+
required LocalKey key,
14+
required Widget child,
15+
AppRouteTransition transition = AppRouteTransition.standard,
16+
}) {
17+
final spec = _AppRouteTransitionSpec.fromTransition(transition);
18+
19+
return CustomTransitionPage<T>(
20+
key: key,
21+
transitionDuration: spec.duration,
22+
reverseTransitionDuration: spec.reverseDuration,
23+
child: child,
24+
transitionsBuilder: (context, animation, secondaryAnimation, child) {
25+
return _AppRouteTransitionView(
26+
animation: animation,
27+
transition: transition,
28+
child: child,
29+
);
30+
},
31+
);
32+
}
33+
34+
class _AppRouteTransitionSpec {
35+
const _AppRouteTransitionSpec({
36+
required this.duration,
37+
required this.reverseDuration,
38+
});
39+
40+
final Duration duration;
41+
final Duration reverseDuration;
42+
43+
static _AppRouteTransitionSpec fromTransition(AppRouteTransition transition) {
44+
switch (transition) {
45+
case AppRouteTransition.fade:
46+
return const _AppRouteTransitionSpec(
47+
duration: Duration(milliseconds: 180),
48+
reverseDuration: Duration(milliseconds: 140),
49+
);
50+
case AppRouteTransition.standard:
51+
return const _AppRouteTransitionSpec(
52+
duration: Duration(milliseconds: 260),
53+
reverseDuration: Duration(milliseconds: 220),
54+
);
55+
case AppRouteTransition.bottomNavFromLeft:
56+
case AppRouteTransition.bottomNavFromRight:
57+
return const _AppRouteTransitionSpec(
58+
duration: Duration(milliseconds: 220),
59+
reverseDuration: Duration(milliseconds: 180),
60+
);
61+
case AppRouteTransition.scheduleFlow:
62+
return const _AppRouteTransitionSpec(
63+
duration: Duration(milliseconds: 200),
64+
reverseDuration: Duration(milliseconds: 160),
65+
);
66+
}
67+
}
68+
}
69+
70+
class _AppRouteTransitionView extends StatelessWidget {
71+
const _AppRouteTransitionView({
72+
required this.animation,
73+
required this.transition,
74+
required this.child,
75+
});
76+
77+
final Animation<double> animation;
78+
final AppRouteTransition transition;
79+
final Widget child;
80+
81+
@override
82+
Widget build(BuildContext context) {
83+
switch (transition) {
84+
case AppRouteTransition.fade:
85+
return FadeTransition(
86+
opacity: _curvedOpacityAnimation(animation, begin: 0),
87+
child: child,
88+
);
89+
case AppRouteTransition.standard:
90+
return FadeTransition(
91+
opacity: _curvedOpacityAnimation(animation, begin: 0.08),
92+
child: SlideTransition(
93+
position: _curvedOffsetAnimation(
94+
animation,
95+
begin: const Offset(0, 0.032),
96+
),
97+
child: child,
98+
),
99+
);
100+
case AppRouteTransition.bottomNavFromLeft:
101+
return FadeTransition(
102+
opacity: _curvedOpacityAnimation(animation, begin: 0.2),
103+
child: SlideTransition(
104+
position: _curvedOffsetAnimation(
105+
animation,
106+
begin: const Offset(-0.16, 0),
107+
),
108+
child: child,
109+
),
110+
);
111+
case AppRouteTransition.bottomNavFromRight:
112+
return FadeTransition(
113+
opacity: _curvedOpacityAnimation(animation, begin: 0.2),
114+
child: SlideTransition(
115+
position: _curvedOffsetAnimation(
116+
animation,
117+
begin: const Offset(0.16, 0),
118+
),
119+
child: child,
120+
),
121+
);
122+
case AppRouteTransition.scheduleFlow:
123+
return FadeTransition(
124+
opacity: _curvedOpacityAnimation(animation, begin: 0),
125+
child: ScaleTransition(
126+
scale: Tween<double>(begin: 0.985, end: 1).animate(
127+
CurvedAnimation(parent: animation, curve: Curves.easeOutCubic),
128+
),
129+
child: child,
130+
),
131+
);
132+
}
133+
}
134+
135+
Animation<double> _curvedOpacityAnimation(
136+
Animation<double> animation, {
137+
required double begin,
138+
}) {
139+
return Tween<double>(
140+
begin: begin,
141+
end: 1,
142+
).animate(CurvedAnimation(parent: animation, curve: Curves.easeOutCubic));
143+
}
144+
145+
Animation<Offset> _curvedOffsetAnimation(
146+
Animation<double> animation, {
147+
required Offset begin,
148+
}) {
149+
return Tween<Offset>(
150+
begin: begin,
151+
end: Offset.zero,
152+
).animate(CurvedAnimation(parent: animation, curve: Curves.easeOutCubic));
153+
}
154+
}

lib/presentation/shared/router/go_router.dart

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:on_time_front/presentation/schedule_create/schedule_spare_and_pr
2525
import 'package:on_time_front/presentation/schedule_create/screens/schedule_create_screen.dart';
2626
import 'package:on_time_front/presentation/schedule_create/screens/schedule_edit_screen.dart';
2727
import 'package:on_time_front/presentation/shared/components/bottom_nav_bar_scaffold.dart';
28+
import 'package:on_time_front/presentation/shared/router/app_route_transition.dart';
2829
import 'package:on_time_front/presentation/shared/router/route_arguments.dart';
2930
import 'package:on_time_front/presentation/shared/utils/stream_to_listenable.dart';
3031
import 'package:on_time_front/presentation/startup/screens/startup_screen.dart';
@@ -87,25 +88,43 @@ GoRouter goRouterConfig(
8788
routes: [
8889
GoRoute(
8990
path: '/startup',
90-
builder: (context, state) => const StartupScreen(),
91+
pageBuilder: (context, state) => _buildAppRoutePage(
92+
state: state,
93+
transition: AppRouteTransition.fade,
94+
child: const StartupScreen(),
95+
),
9196
),
9297
GoRoute(
9398
path: '/allowNotification',
94-
builder: (context, state) {
95-
return NotificationAllowScreen();
96-
},
99+
pageBuilder: (context, state) => _buildAppRoutePage(
100+
state: state,
101+
transition: AppRouteTransition.fade,
102+
child: NotificationAllowScreen(),
103+
),
97104
),
98105
GoRoute(
99106
path: '/allowAlarm',
100-
builder: (context, state) => const AlarmAllowScreen(),
107+
pageBuilder: (context, state) => _buildAppRoutePage(
108+
state: state,
109+
transition: AppRouteTransition.fade,
110+
child: const AlarmAllowScreen(),
111+
),
101112
),
102113
GoRoute(
103114
path: '/onboarding',
104-
builder: (context, state) => OnboardingScreen(),
115+
pageBuilder: (context, state) => _buildAppRoutePage(
116+
state: state,
117+
transition: AppRouteTransition.fade,
118+
child: OnboardingScreen(),
119+
),
105120
routes: [
106121
GoRoute(
107122
path: '/start',
108-
builder: (context, state) => OnboardingStartScreen(),
123+
pageBuilder: (context, state) => _buildAppRoutePage(
124+
state: state,
125+
transition: AppRouteTransition.fade,
126+
child: OnboardingStartScreen(),
127+
),
109128
),
110129
],
111130
),
@@ -114,58 +133,86 @@ GoRouter goRouterConfig(
114133
routes: [
115134
GoRoute(
116135
path: '/home',
117-
pageBuilder: (context, state) => _buildBottomNavSlidePage(
136+
pageBuilder: (context, state) => _buildAppRoutePage(
118137
state: state,
119-
beginOffset: const Offset(-1, 0),
138+
transition: AppRouteTransition.bottomNavFromLeft,
120139
child: HomeScreenTmp(),
121140
),
122141
),
123142
GoRoute(
124143
path: '/myPage',
125-
pageBuilder: (context, state) => _buildBottomNavSlidePage(
144+
pageBuilder: (context, state) => _buildAppRoutePage(
126145
state: state,
127-
beginOffset: const Offset(1, 0),
146+
transition: AppRouteTransition.bottomNavFromRight,
128147
child: MyPageScreen(),
129148
),
130149
),
131150
],
132151
),
133152
GoRoute(
134153
path: '/defaultPreparationSpareTimeEdit',
135-
builder: (context, state) => PreparationSpareTimeEditScreen(),
154+
pageBuilder: (context, state) => _buildAppRoutePage(
155+
state: state,
156+
child: PreparationSpareTimeEditScreen(),
157+
),
158+
),
159+
GoRoute(
160+
path: '/signIn',
161+
pageBuilder: (context, state) => _buildAppRoutePage(
162+
state: state,
163+
transition: AppRouteTransition.fade,
164+
child: SignInMainScreen(),
165+
),
136166
),
137-
GoRoute(path: '/signIn', builder: (context, state) => SignInMainScreen()),
138167
GoRoute(
139168
path: '/calendar',
140-
builder: (context, state) =>
141-
CalendarScreen(initialDate: calendarInitialDateFromState(state)),
169+
pageBuilder: (context, state) => _buildAppRoutePage(
170+
state: state,
171+
child: CalendarScreen(
172+
initialDate: calendarInitialDateFromState(state),
173+
),
174+
),
142175
),
143176
GoRoute(
144177
path: '/scheduleCreate',
145-
builder: (context, state) => ScheduleCreateScreen(),
178+
pageBuilder: (context, state) =>
179+
_buildAppRoutePage(state: state, child: ScheduleCreateScreen()),
146180
),
147181
GoRoute(
148182
path: '/scheduleEdit/:scheduleId',
149-
builder: (context, state) =>
150-
ScheduleEditScreen(scheduleId: state.pathParameters['scheduleId']!),
183+
pageBuilder: (context, state) => _buildAppRoutePage(
184+
state: state,
185+
child: ScheduleEditScreen(
186+
scheduleId: state.pathParameters['scheduleId']!,
187+
),
188+
),
151189
),
152190
GoRoute(
153191
path: '/preparationEdit',
154-
builder: (context, state) => const PreparationEditForm(),
192+
pageBuilder: (context, state) => _buildAppRoutePage(
193+
state: state,
194+
child: const PreparationEditForm(),
195+
),
155196
),
156197
GoRoute(
157198
path: '/scheduleStart',
158199
name: 'scheduleStart',
159-
builder: (context, state) {
200+
pageBuilder: (context, state) {
160201
final extra = scheduleStartRouteExtraFromState(state);
161-
return _ScheduleStartRouteGate(extra: extra);
202+
return _buildAppRoutePage(
203+
state: state,
204+
transition: AppRouteTransition.scheduleFlow,
205+
child: _ScheduleStartRouteGate(extra: extra),
206+
);
162207
},
163208
),
164209
GoRoute(
165210
path: '/alarmScreen',
166-
builder: (context, state) {
167-
return AlarmScreen();
168-
},
211+
pageBuilder: (context, state) => _buildAppRoutePage(
212+
state: state,
213+
transition: AppRouteTransition.scheduleFlow,
214+
child: AlarmScreen(),
215+
),
169216
),
170217
GoRoute(
171218
path: '/earlyLate',
@@ -174,51 +221,44 @@ GoRouter goRouterConfig(
174221
? '/home'
175222
: null;
176223
},
177-
builder: (context, state) {
224+
pageBuilder: (context, state) {
178225
final arguments = earlyLateRouteArgumentsFromState(state);
179226
if (arguments == null) {
180-
return const LoadingScreen();
227+
return _buildAppRoutePage(
228+
state: state,
229+
transition: AppRouteTransition.scheduleFlow,
230+
child: const LoadingScreen(),
231+
);
181232
}
182233

183-
return EarlyLateScreen(
184-
earlyLateTime: arguments.earlyLateTime,
185-
isLate: arguments.isLate,
234+
return _buildAppRoutePage(
235+
state: state,
236+
transition: AppRouteTransition.scheduleFlow,
237+
child: EarlyLateScreen(
238+
earlyLateTime: arguments.earlyLateTime,
239+
isLate: arguments.isLate,
240+
),
186241
);
187242
},
188243
),
189-
GoRoute(path: '/moving', builder: (context, state) => MovingScreen()),
244+
GoRoute(
245+
path: '/moving',
246+
pageBuilder: (context, state) =>
247+
_buildAppRoutePage(state: state, child: MovingScreen()),
248+
),
190249
],
191250
);
192251
}
193252

194-
CustomTransitionPage<void> _buildBottomNavSlidePage({
253+
CustomTransitionPage<void> _buildAppRoutePage({
195254
required GoRouterState state,
196-
required Offset beginOffset,
255+
AppRouteTransition transition = AppRouteTransition.standard,
197256
required Widget child,
198257
}) {
199-
final slideTween = Tween<Offset>(
200-
begin: beginOffset,
201-
end: Offset.zero,
202-
).chain(CurveTween(curve: Curves.easeOutCubic));
203-
final secondarySlideTween = Tween<Offset>(
204-
begin: Offset.zero,
205-
end: beginOffset,
206-
).chain(CurveTween(curve: Curves.easeOutCubic));
207-
208-
return CustomTransitionPage<void>(
258+
return buildAppRoutePage<void>(
209259
key: state.pageKey,
210-
transitionDuration: const Duration(milliseconds: 280),
211-
reverseTransitionDuration: const Duration(milliseconds: 280),
260+
transition: transition,
212261
child: child,
213-
transitionsBuilder: (context, animation, secondaryAnimation, child) {
214-
return SlideTransition(
215-
position: secondaryAnimation.drive(secondarySlideTween),
216-
child: SlideTransition(
217-
position: animation.drive(slideTween),
218-
child: child,
219-
),
220-
);
221-
},
222262
);
223263
}
224264

0 commit comments

Comments
 (0)