Feat: add package animation

This commit is contained in:
orangdeso 2025-05-31 18:49:42 +07:00
parent ae09c5a89a
commit ac1d7680bb
15 changed files with 526 additions and 37 deletions

View File

@ -7,6 +7,12 @@
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "animations",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/animations-2.0.11",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "archive",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.7",
@ -680,7 +686,7 @@
"languageVersion": "3.4"
}
],
"generated": "2025-05-20T21:06:43.916068Z",
"generated": "2025-05-29T17:34:26.701705Z",
"generator": "pub",
"generatorVersion": "3.5.0",
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",

View File

@ -2,6 +2,10 @@ _flutterfire_internals
3.2
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.54/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.54/lib/
animations
3.2
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/animations-2.0.11/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/animations-2.0.11/lib/
archive
3.0
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.7/

View File

@ -1,4 +1,7 @@
import 'package:e_porter/domain/bindings/auth_binding.dart';
import 'package:e_porter/domain/bindings/navigation_binding.dart';
import 'package:e_porter/presentation/screens/boarding_pass/provider/porter_service_provider.dart';
import 'package:e_porter/presentation/widgets/animations/animation_configs.dart';
import 'package:get/get.dart';
import 'package:e_porter/data/repositories/transaction_repository_impl.dart';
import 'package:e_porter/_core/service/transaction_expiry_service.dart';
@ -6,17 +9,30 @@ import 'package:e_porter/_core/service/transaction_expiry_service.dart';
class AppBinding extends Bindings {
@override
void dependencies() {
// Inisialisasi TransactionExpiryService
_initCoreBindings();
_initServices();
_configureGlobalTransitions();
}
void _initCoreBindings() {
AuthBinding().dependencies();
MainNavigationBinding().dependencies();
}
void _initServices() {
Get.put<TransactionRepositoryImpl>(TransactionRepositoryImpl(), permanent: true);
// Inisialisasi dan mulai service
final repository = Get.find<TransactionRepositoryImpl>();
TransactionExpiryService().initialize(repository);
// Inisialisasi dependency Porter
PorterServiceProvider.registerDependencies();
// Mulai layanan pengalihan transaksi
PorterServiceProvider.initServices();
}
void _configureGlobalTransitions() {
Get.config(
defaultTransition: Transition.fadeIn,
defaultDurationTransition: AnimationConfigs.sharedAxisDuration,
);
}
}

View File

@ -1,5 +1,4 @@
import 'dart:developer';
import 'package:e_porter/_core/component/button/button_fill.dart';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.dart';

View File

@ -1,8 +1,12 @@
import 'package:animations/animations.dart';
import 'package:e_porter/_core/component/button/button_fill.dart';
import 'package:e_porter/_core/component/button/button_outline.dart';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.dart';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
import 'package:e_porter/presentation/widgets/animations/container_transform.dart';
import 'package:e_porter/presentation/widgets/animations/fade_through.dart';
import 'package:e_porter/presentation/widgets/animations/staggered_fade.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
@ -24,17 +28,28 @@ class OnboardingScreen extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: Column(
children: [
SvgPicture.asset(
'assets/images/ilustrasi_onboarding.svg',
StaggeredFadeAnimation(
index: 0,
delay: const Duration(milliseconds: 500),
duration: const Duration(milliseconds: 1000),
reverse: true,
transitionType: SharedAxisTransitionType.vertical,
child: SvgPicture.asset(
'assets/images/ilustrasi_onboarding.svg',
),
),
SizedBox(height: 16.h),
TypographyStyles.h6(
'Hemat waktu Anda di bandara dengan layanan E-Porter. Kami siap membantu setiap kebutuhan perjalanan prioritas Anda dengan mudah dan cepat',
fontWeight: FontWeight.w600,
color: GrayColors.gray600,
textAlign: TextAlign.center,
maxlines: 5,
)
FadeThroughAnimation(
delay: const Duration(milliseconds: 1200),
duration: const Duration(milliseconds: 800),
child: TypographyStyles.h6(
'Hemat waktu Anda di bandara dengan layanan E-Porter. Kami siap membantu setiap kebutuhan perjalanan prioritas Anda dengan mudah dan cepat',
fontWeight: FontWeight.w600,
color: GrayColors.gray600,
textAlign: TextAlign.center,
maxlines: 5,
),
),
],
),
),
@ -44,25 +59,35 @@ class OnboardingScreen extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ZoomTapAnimation(
child: ButtonFill(
text: 'Masuk sebagai Penumpang',
textColor: Colors.white,
onTap: () {
Get.toNamed(Routes.LOGIN, arguments: 'penumpang');
},
ContainerTransformAnimation(
delay: const Duration(milliseconds: 1800),
duration: const Duration(milliseconds: 600),
animationType: AnimationType.fadeThrough,
child: ZoomTapAnimation(
child: ButtonFill(
text: 'Masuk sebagai Penumpang',
textColor: Colors.white,
onTap: () {
Get.toNamed(Routes.LOGIN, arguments: 'penumpang');
},
),
),
),
SizedBox(
height: 10.h,
),
ZoomTapAnimation(
child: ButtonOutline(
text: 'Masuk sebagai Porter',
textColor: PrimaryColors.primary800,
onTap: () {
Get.toNamed(Routes.LOGIN, arguments: 'porter');
},
ContainerTransformAnimation(
delay: const Duration(milliseconds: 2100),
duration: const Duration(milliseconds: 600),
animationType: AnimationType.sharedAxisHorizontal,
child: ZoomTapAnimation(
child: ButtonOutline(
text: 'Masuk sebagai Porter',
textColor: PrimaryColors.primary800,
onTap: () {
Get.toNamed(Routes.LOGIN, arguments: 'porter');
},
),
),
),
],

View File

@ -76,6 +76,10 @@ class AppRoutes {
page: () => LoginScreen(),
binding: AuthBinding(),
),
GetPage(
name: Routes.REGISTER,
page: () => RegisterScreen(),
),
GetPage(
name: Routes.VERIFICATION,
page: () => VerifikasiScreen(),
@ -93,10 +97,6 @@ class AppRoutes {
name: Routes.PROFILE,
page: () => ProfileScreen(),
),
GetPage(
name: Routes.REGISTER,
page: () => RegisterScreen(),
),
GetPage(
name: Routes.FORGETPASSWORD,
page: () => ForgetPasswordScreen(),

View File

@ -1,4 +1,5 @@
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
import 'package:e_porter/presentation/widgets/animations/fade_slide_animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
@ -16,7 +17,19 @@ class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 3), () async {
// Future.delayed(Duration(seconds: 3), () async {
// final userData = await PreferencesService.getUserData();
// if (userData != null) {
// Get.offAllNamed(Routes.NAVBAR, arguments: userData.role);
// } else {
// Get.offAllNamed(Routes.ONBOARDING);
// }
// });
_navigateAfterDelay();
}
void _navigateAfterDelay() {
Future.delayed(const Duration(seconds: 3), () async {
final userData = await PreferencesService.getUserData();
if (userData != null) {
Get.offAllNamed(Routes.NAVBAR, arguments: userData.role);
@ -31,8 +44,18 @@ class _SplashScreenState extends State<SplashScreen> {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: SvgPicture.asset(
'assets/images/eporter-logo.svg',
child: FadeSlideAnimation(
duration: const Duration(milliseconds: 1200),
delay: const Duration(milliseconds: 300),
slideOffset: const Offset(0, 0.3),
curve: Curves.easeOutBack,
enableScale: true,
enableBlur: true,
scaleBegin: 0.2,
scaleEnd: 1.0,
child: SvgPicture.asset(
'assets/images/eporter-logo.svg',
),
),
),
);

View File

@ -0,0 +1,31 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
class AnimationConfigs {
// === FADE THROUGH TRANSITIONS ===
static const Duration fadeTransitionDuration = Duration(milliseconds: 800);
static const Duration fadeTransitionReverseDuration = Duration(milliseconds: 500);
// === SHARED AXIS TRANSITIONS ===
static const Duration sharedAxisDuration = Duration(milliseconds: 600);
static const SharedAxisTransitionType horizontalTransition = SharedAxisTransitionType.horizontal;
static const SharedAxisTransitionType verticalTransition = SharedAxisTransitionType.vertical;
static const SharedAxisTransitionType scaledTransition = SharedAxisTransitionType.scaled;
// === CONTAINER TRANSFORM ===
static const Duration containerTransformDuration = Duration(milliseconds: 700);
// === CUSTOM CURVES ===
static const Curve defaultCurve = Curves.easeOutCubic;
static const Curve bouncyCurve = Curves.elasticOut;
static const Curve smoothCurve = Curves.easeInOutCubic;
// === STAGGER DELAYS ===
static const Duration baseDelay = Duration(milliseconds: 300);
static const Duration itemDelay = Duration(milliseconds: 150);
static const Duration buttonDelay = Duration(milliseconds: 200);
// === MODAL TRANSITIONS ===
static const Duration modalDuration = Duration(milliseconds: 500);
static const Duration overlayDuration = Duration(milliseconds: 300);
}

View File

@ -0,0 +1,90 @@
import 'package:animations/animations.dart';
import 'package:e_porter/presentation/widgets/animations/animation_configs.dart';
import 'package:flutter/material.dart';
enum AnimationType {
fadeThrough,
sharedAxisVertical,
sharedAxisHorizontal,
sharedAxisScaled,
}
class ContainerTransformAnimation extends StatefulWidget {
final Widget child;
final Duration delay;
final Duration? duration;
final AnimationType animationType;
const ContainerTransformAnimation({
Key? key,
required this.child,
this.delay = Duration.zero,
this.duration,
this.animationType = AnimationType.fadeThrough,
}) : super(key: key);
@override
State<ContainerTransformAnimation> createState() => _ContainerTransformAnimationState();
}
class _ContainerTransformAnimationState extends State<ContainerTransformAnimation> {
bool _isVisible = false;
@override
void initState() {
super.initState();
Future.delayed(widget.delay, () {
if (mounted) {
setState(() {
_isVisible = true;
});
}
});
}
Widget _buildTransition(Widget child, Animation<double> primaryAnimation, Animation<double> secondaryAnimation) {
switch (widget.animationType) {
case AnimationType.fadeThrough:
return FadeThroughTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
child: child,
);
case AnimationType.sharedAxisVertical:
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.vertical,
child: child,
);
case AnimationType.sharedAxisHorizontal:
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: child,
);
case AnimationType.sharedAxisScaled:
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
child: child,
);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: PageTransitionSwitcher(
duration: widget.duration ?? AnimationConfigs.containerTransformDuration,
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
return _buildTransition(child, primaryAnimation, secondaryAnimation);
},
child: _isVisible ? widget.child : const SizedBox.shrink(),
),
);
}
}

View File

@ -0,0 +1,120 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class FadeSlideAnimation extends StatefulWidget {
final Widget child;
final Duration duration;
final Duration delay;
final Offset slideOffset;
final Curve curve;
final bool enableScale;
final bool enableBlur;
final double scaleBegin;
final double scaleEnd;
const FadeSlideAnimation({
Key? key,
required this.child,
this.duration = const Duration(milliseconds: 800),
this.delay = Duration.zero,
this.slideOffset = const Offset(0, 0.3),
this.curve = Curves.easeOutCubic,
this.enableScale = false,
this.enableBlur = false,
this.scaleBegin = 0.3,
this.scaleEnd = 1.0,
}) : super(key: key);
@override
State<FadeSlideAnimation> createState() => _FadeSlideAnimationState();
}
class _FadeSlideAnimationState extends State<FadeSlideAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _blurAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
// Fade animation
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: widget.curve,
));
// Scale animation (dari kecil ke besar)
_scaleAnimation = Tween<double>(
begin: widget.scaleBegin,
end: widget.scaleEnd,
).animate(CurvedAnimation(
parent: _controller,
curve: widget.enableScale ? Curves.elasticOut : widget.curve,
));
// Blur animation (blur tinggi ke blur rendah/hilang)
_blurAnimation = Tween<double>(
begin: widget.enableBlur ? 10.0 : 0.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeOutQuart,
));
Future.delayed(widget.delay, () {
if (mounted) {
_controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
Widget animatedChild = widget.child;
if (widget.enableScale) {
animatedChild = Transform.scale(
scale: _scaleAnimation.value,
child: animatedChild,
);
}
if (widget.enableBlur) {
animatedChild = ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: _blurAnimation.value,
sigmaY: _blurAnimation.value,
),
child: animatedChild,
);
}
return FadeTransition(
opacity: _fadeAnimation,
child: animatedChild,
);
},
);
}
}

View File

@ -0,0 +1,54 @@
import 'package:animations/animations.dart';
import 'package:e_porter/presentation/widgets/animations/animation_configs.dart';
import 'package:flutter/material.dart';
class FadeThroughAnimation extends StatefulWidget {
final Widget child;
final Duration delay;
final Duration? duration;
const FadeThroughAnimation({
Key? key,
required this.child,
this.delay = Duration.zero,
this.duration,
}) : super(key: key);
@override
State<FadeThroughAnimation> createState() => _FadeThroughAnimationState();
}
class _FadeThroughAnimationState extends State<FadeThroughAnimation> {
bool _isVisible = false;
@override
void initState() {
super.initState();
Future.delayed(widget.delay, () {
if (mounted) {
setState(() {
_isVisible = true;
});
}
});
}
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
duration: widget.duration ?? AnimationConfigs.fadeTransitionDuration,
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
return FadeThroughTransition(
fillColor: Colors.transparent,
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: _isVisible
? widget.child
: const SizedBox.shrink(),
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:animations/animations.dart';
import 'package:e_porter/presentation/widgets/animations/animation_configs.dart';
import 'package:flutter/material.dart';
class StaggeredFadeAnimation extends StatefulWidget {
final Widget child;
final int index;
final Duration? delay;
final Duration? duration;
final SharedAxisTransitionType transitionType;
final bool reverse;
const StaggeredFadeAnimation({
Key? key,
required this.child,
required this.index,
this.delay,
this.duration,
this.transitionType = SharedAxisTransitionType.vertical,
this.reverse = false,
}) : super(key: key);
@override
State<StaggeredFadeAnimation> createState() => _StaggeredFadeAnimationState();
}
class _StaggeredFadeAnimationState extends State<StaggeredFadeAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool _isVisible = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration ?? AnimationConfigs.sharedAxisDuration,
vsync: this,
);
// Calculate staggered delay
final totalDelay = (widget.delay ?? AnimationConfigs.baseDelay) +
Duration(milliseconds: AnimationConfigs.itemDelay.inMilliseconds * widget.index);
// Start animation after delay
Future.delayed(totalDelay, () {
if (mounted) {
setState(() {
_isVisible = true;
});
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: PageTransitionSwitcher(
duration: widget.duration ?? AnimationConfigs.sharedAxisDuration,
reverse: widget.reverse,
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
return SharedAxisTransition(
fillColor: Colors.transparent,
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: widget.transitionType,
child: child,
);
},
child: _isVisible
? widget.child
: const SizedBox.shrink(),
),
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:shimmer/shimmer.dart';
class SkeletonWidget extends StatelessWidget {
final double height;
final double width;
const SkeletonWidget({
Key? key,
required this.height,
required this.width,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[200]!,
highlightColor: Colors.grey[50]!,
child: Container(
height: height,
width: width,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10.r),
),
),
);
}
}

View File

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.54"
animations:
dependency: "direct main"
description:
name: animations
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
url: "https://pub.dev"
source: hosted
version: "2.0.11"
archive:
dependency: transitive
description:

View File

@ -61,6 +61,7 @@ dependencies:
mobile_scanner: ^6.0.10
pdf: ^3.11.3
printing: ^5.14.2
animations: ^2.0.11
# firebase_dynamic_links: ^6.1.5
# workmanager: ^0.5.2