Skip to content

Commit 0359ece

Browse files
committed
🎨 style(animation): Added entry animations on OnboardingPage
1 parent 4495c6f commit 0359ece

File tree

12 files changed

+279
-25
lines changed

12 files changed

+279
-25
lines changed

‎l10n.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
arb-dir: lib/l10n/arb
22
template-arb-file: app_en.arb
3+
untranslated-messages-file: lib/l10n/arb/untranslated.txt
34
output-localization-file: app_localizations.dart
45
nullable-getter: false

‎lib/core/routes/app_routes.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
sealed class AppRoutes {
22
static const splash = AppRoute._('splash', '/');
33
static const onboarding = AppRoute._('onboarding', '/onboarding');
4+
5+
static const home = AppRoute._('home', '/home');
46
}
57

68
class AppRoute {

‎lib/core/routes/go_router.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import 'package:flutter/foundation.dart';
21
import 'package:go_router/go_router.dart';
32
import 'package:nasa_app_challenge/core/core.dart';
43
import 'package:nasa_app_challenge/features/welcome/presentation/pages/onboarding_page.dart';
54
import 'package:nasa_app_challenge/features/welcome/presentation/pages/splash_page.dart';
65

76
final goRouterConfig = GoRouter(
87
initialLocation:
9-
kDebugMode ? AppRoutes.onboarding.path : AppRoutes.splash.path,
8+
AppRoutes.splash.path,
109
routes: [
1110
GoRoute(
1211
name: AppRoutes.splash.name,

‎lib/core/shared/cubits/value_cubit.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'package:bloc/bloc.dart';
22

33
/// Provides a [T] value for the widget tree, use it if you want to listen
44
/// and modify a single value
5-
abstract class ValueCubit<T> extends Cubit<T> {
5+
class ValueCubit<T> extends Cubit<T> {
66
ValueCubit({required T value}) : super(value);
77

88
void change(T value) => emit(value);
Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
21
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:flutter_screenutil/flutter_screenutil.dart';
4+
import 'package:go_router/go_router.dart';
45
import 'package:nasa_app_challenge/core/core.dart';
6+
import 'package:nasa_app_challenge/features/welcome/presentation/widgets/onboarding_page_view.dart';
7+
import 'package:nasa_app_challenge/features/welcome/presentation/widgets/rotating_earth_widget.dart';
8+
import 'package:nasa_app_challenge/l10n/l10n.dart';
59
import 'package:ui_common/ui_common.dart';
610

711
class OnboardingPage extends StatelessWidget {
812
const OnboardingPage({super.key});
913

1014
@override
1115
Widget build(BuildContext context) {
12-
return const _OnboardingView();
16+
return BlocProvider(
17+
create: (_) => ValueCubit<int>(value: 0),
18+
child: const _OnboardingView(),
19+
);
1320
}
1421
}
1522

@@ -20,19 +27,67 @@ class _OnboardingView extends StatelessWidget {
2027
Widget build(BuildContext context) {
2128
return Scaffold(
2229
backgroundColor: context.backgroundColor,
23-
body: Stack(
30+
appBar: const _OnboardingAppBar(),
31+
body: const Stack(
2432
alignment: Alignment.center,
33+
fit: StackFit.expand,
2534
children: [
26-
Positioned(
27-
height: 3.sh,
28-
bottom: -2.2.sh,
29-
right: -3.5.sw,
30-
child: AutomaticRotationWidget(
31-
child: Assets.images.earth.image(),
35+
RotatingEarthWidget(),
36+
SafeArea(
37+
child: OnboardingPageView(),
38+
),
39+
],
40+
),
41+
);
42+
}
43+
}
44+
45+
class _OnboardingAppBar extends StatelessWidget implements PreferredSize {
46+
const _OnboardingAppBar({
47+
super.key,
48+
});
49+
50+
@override
51+
Widget build(BuildContext context) {
52+
return TweenAnimationBuilder<double>(
53+
duration: const Duration(milliseconds: 1000),
54+
tween: Tween(begin: 1, end: 0),
55+
curve: const Interval(
56+
0.33,
57+
1,
58+
curve: Curves.fastOutSlowIn,
59+
),
60+
builder: (_, value, child) {
61+
return Opacity(
62+
opacity: 1 - value,
63+
child: Transform.translate(
64+
offset: Offset(0, 30 * value),
65+
child: child,
66+
),
67+
);
68+
},
69+
child: AppBar(
70+
backgroundColor: context.backgroundColor,
71+
toolbarHeight: (kToolbarHeight + 10).sp,
72+
centerTitle: false,
73+
title: Assets.images.nasaGlobe.image(height: kToolbarHeight.sp),
74+
actions: [
75+
TextButton(
76+
onPressed: () => context.pushReplacementNamed(AppRoutes.home.name),
77+
style: TextButton.styleFrom(padding: EdgeInsets.zero),
78+
child: Text(
79+
context.l10n.skip,
80+
style: context.labelSmall.copyWith(color: Colors.white38),
3281
),
3382
),
3483
],
3584
),
3685
);
3786
}
87+
88+
@override
89+
Widget get child => this;
90+
91+
@override
92+
Size get preferredSize => Size.fromHeight((kToolbarHeight + 10).sp);
3893
}

‎lib/features/welcome/presentation/pages/splash_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class _SplashViewState extends State<_SplashView>
3535
void initState() {
3636
controller = AnimationController(
3737
vsync: this,
38-
duration: const Duration(seconds: 5),
38+
duration: const Duration(seconds: 4),
3939
)..forward();
4040
circleAnimation = CurvedAnimation(
4141
parent: controller,
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_screenutil/flutter_screenutil.dart';
3+
import 'package:nasa_app_challenge/l10n/l10n.dart';
4+
import 'package:ui_common/ui_common.dart';
5+
6+
class OnboardingPageView extends StatefulWidget {
7+
const OnboardingPageView({
8+
super.key,
9+
});
10+
11+
@override
12+
State<OnboardingPageView> createState() => _OnboardingPageViewState();
13+
}
14+
15+
class _OnboardingPageViewState extends State<OnboardingPageView>
16+
with SingleTickerProviderStateMixin {
17+
late final AnimationController controller;
18+
late final Animation<double> titleEntryAnimation;
19+
late final Animation<double> buttonEntryAnimation;
20+
21+
void animationListener() {}
22+
23+
@override
24+
void initState() {
25+
controller = AnimationController(
26+
vsync: this,
27+
duration: const Duration(milliseconds: 800),
28+
)..addListener(animationListener);
29+
titleEntryAnimation = CurvedAnimation(
30+
parent: controller,
31+
curve: const Interval(0, .7),
32+
);
33+
buttonEntryAnimation = CurvedAnimation(
34+
parent: controller,
35+
curve: const Interval(.3, 1),
36+
);
37+
Future.delayed(
38+
const Duration(milliseconds: 400),
39+
() => controller.forward(),
40+
);
41+
super.initState();
42+
}
43+
44+
@override
45+
void dispose() {
46+
controller
47+
..removeListener(animationListener)
48+
..dispose();
49+
super.dispose();
50+
}
51+
52+
@override
53+
Widget build(BuildContext context) {
54+
return Padding(
55+
padding: 20.edgeInsetsA,
56+
child: Column(
57+
crossAxisAlignment: CrossAxisAlignment.start,
58+
children: [
59+
12.verticalSpace,
60+
_EntryAnimationWidget(
61+
listenable: titleEntryAnimation,
62+
child: Padding(
63+
padding: 20.edgeInsetsR,
64+
child: Text(
65+
context.l10n.welcomeToTheNASAAwesomeApp,
66+
style: context.displaySmall,
67+
),
68+
),
69+
),
70+
30.verticalSpace,
71+
_EntryAnimationWidget(
72+
listenable: buttonEntryAnimation,
73+
child: Transform.translate(
74+
offset: Offset(-12.w, 0),
75+
child: TextButton(
76+
onPressed: () {},
77+
style: TextButton.styleFrom(
78+
padding: 12.edgeInsetsH,
79+
),
80+
child: Text(context.l10n.goAhead),
81+
),
82+
),
83+
),
84+
],
85+
),
86+
);
87+
}
88+
}
89+
90+
class _EntryAnimationWidget extends AnimatedWidget {
91+
const _EntryAnimationWidget({
92+
required super.listenable,
93+
required this.child,
94+
super.key,
95+
});
96+
97+
final Widget child;
98+
99+
@override
100+
Widget build(BuildContext context) {
101+
final animation = listenable as Animation<double>;
102+
final value = animation.value;
103+
return FadeTransition(
104+
opacity: animation,
105+
child: Transform.translate(
106+
offset: Offset(0, 20 * (1 - value)),
107+
child: child,
108+
),
109+
);
110+
}
111+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:flutter_screenutil/flutter_screenutil.dart';
4+
import 'package:nasa_app_challenge/core/shared/cubits/value_cubit.dart';
5+
import 'package:nasa_app_challenge/core/shared/widgets/automatic_rotation_widget.dart';
6+
import 'package:ui_common/ui_common.dart';
7+
8+
class RotatingEarthWidget extends StatelessWidget {
9+
const RotatingEarthWidget({super.key});
10+
11+
@override
12+
Widget build(BuildContext context) {
13+
return BlocBuilder<ValueCubit<int>, int>(
14+
builder: (_, index) {
15+
return AnimatedPositioned(
16+
duration: const Duration(seconds: 3),
17+
curve: Curves.fastLinearToSlowEaseIn,
18+
height: 2.5.sh,
19+
bottom: [
20+
-1.85.sh,
21+
-1.6.sh,
22+
-1.95.sh,
23+
][index],
24+
right: [
25+
-1.5.sh,
26+
-0.25.sh,
27+
-1.sh,
28+
][index],
29+
child: TweenAnimationBuilder<double>(
30+
duration: const Duration(milliseconds: 2000),
31+
tween: Tween(begin: 1, end: 0),
32+
curve: const Interval(
33+
0.3,
34+
1,
35+
curve: Curves.fastOutSlowIn,
36+
),
37+
builder: (_, value, child) {
38+
return Opacity(
39+
opacity: 1 - value,
40+
child: Transform.translate(
41+
offset: Offset(200 * value, 200 * value),
42+
child: child,
43+
),
44+
);
45+
},
46+
child: const _EarthWidget(),
47+
),
48+
);
49+
},
50+
);
51+
}
52+
}
53+
54+
class _EarthWidget extends StatelessWidget {
55+
const _EarthWidget({
56+
super.key,
57+
});
58+
59+
@override
60+
Widget build(BuildContext context) {
61+
return DecoratedBox(
62+
decoration: BoxDecoration(
63+
shape: BoxShape.circle,
64+
gradient: RadialGradient(
65+
colors: [
66+
AppColors.absoluteBlue,
67+
AppColors.absoluteBlue.withOpacity(0.5),
68+
AppColors.absoluteBlue.withOpacity(0),
69+
],
70+
),
71+
),
72+
child: AutomaticRotationWidget(
73+
child: Assets.images.earth.image(fit: BoxFit.cover),
74+
),
75+
);
76+
}
77+
}

‎lib/l10n/arb/app_en.arb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
22
"@@locale": "en",
3-
"counterAppBarTitle": "Counter",
4-
"@counterAppBarTitle": {
5-
"description": "Text shown in the AppBar of the Counter Page"
6-
}
3+
"welcomeToTheNASAAwesomeApp": "Welcome to the awesome NASA Flutter App",
4+
"exploreThingsBeyondPlanetEarth": "Explore Things Beyond Planet Earth",
5+
"andDiscoverTheWondersOfTheCosmos": "And discover the wonders of the cosmos",
6+
"goAhead": "Continue",
7+
"areYouReady": "Are you ready?",
8+
"startNow": "Start now",
9+
"skip": "Skip"
710
}

‎lib/l10n/arb/app_es.arb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
22
"@@locale": "es",
3-
"counterAppBarTitle": "Contador",
4-
"@counterAppBarTitle": {
5-
"description": "Texto mostrado en la AppBar de la página del contador"
6-
}
3+
"welcomeToTheNASAAwesomeApp": "Welcome to the awesome NASA app",
4+
"exploreThingsBeyondPlanetEarth": "Explore Things Beyond Planet Earht",
5+
"andDiscoverTheWondersOfTheCosmos": "And discover the wonders of the cosmos",
6+
"continue": "Continue",
7+
"areYouReady": "Are you ready?",
8+
"startNow": "Start now"
9+
710
}

0 commit comments

Comments
 (0)