import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; @RoutePage() class ErrorPage extends StatefulWidget { final String? title; final String? message; final VoidCallback? onRetry; final VoidCallback? onBack; final String? errorCode; final IconData? errorIcon; const ErrorPage({ Key? key, this.title, this.message, this.onRetry, this.onBack, this.errorCode, this.errorIcon, }) : super(key: key); @override State createState() => _ErrorPageState(); } class _ErrorPageState extends State with TickerProviderStateMixin { late AnimationController _bounceController; late AnimationController _fadeController; late AnimationController _slideController; late Animation _bounceAnimation; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _bounceController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _fadeController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _slideController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _bounceAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _bounceController, curve: Curves.elasticOut), ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), ); _slideAnimation = Tween(begin: const Offset(0.0, 0.5), end: Offset.zero).animate( CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), ); // Start animations _bounceController.forward(); Future.delayed(const Duration(milliseconds: 300), () { _fadeController.forward(); }); Future.delayed(const Duration(milliseconds: 500), () { _slideController.forward(); }); } @override void dispose() { _bounceController.dispose(); _fadeController.dispose(); _slideController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColor.primary.withOpacity(0.05), AppColor.background, AppColor.primaryLight.withOpacity(0.03), ], ), ), child: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 60), // Animated Error Illustration AnimatedBuilder( animation: _bounceAnimation, builder: (context, child) { return Transform.scale( scale: _bounceAnimation.value, child: Stack( alignment: Alignment.center, children: [ // Outer glow circle Container( width: 200, height: 200, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ AppColor.error.withOpacity(0.1), Colors.transparent, ], ), ), ), // Middle circle with gradient Container( width: 140, height: 140, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColor.error.withOpacity(0.15), AppColor.warning.withOpacity(0.15), AppColor.primary.withOpacity(0.1), ], ), boxShadow: [ BoxShadow( color: AppColor.error.withOpacity(0.2), blurRadius: 30, offset: const Offset(0, 10), ), ], ), ), // Inner circle with icon Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: AppColor.primaryGradient, ), boxShadow: [ BoxShadow( color: AppColor.primary.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Icon( widget.errorIcon ?? Icons.sentiment_dissatisfied, size: 50, color: AppColor.textWhite, ), ), // Decorative floating dots ...List.generate(6, (index) { final radius = 120.0; return Positioned( left: 100 + radius * 0.8 * (index.isEven ? 1 : -1) * (index / 6), top: 100 + radius * 0.6 * (index.isOdd ? 1 : -1) * (index / 6), child: Container( width: 8 + (index % 3) * 4, height: 8 + (index % 3) * 4, decoration: BoxDecoration( shape: BoxShape.circle, color: [ AppColor.primary, AppColor.error, AppColor.warning, ][index % 3].withOpacity(0.6), ), ), ); }), ], ), ); }, ), const SizedBox(height: 40), // Animated Title FadeTransition( opacity: _fadeAnimation, child: Text( widget.title ?? 'Ups! Ada Yang Salah', style: AppStyle.h2.copyWith( fontWeight: FontWeight.bold, color: AppColor.primary, // warna solid ), textAlign: TextAlign.center, ), ), const SizedBox(height: 16), // Animated Message SlideTransition( position: _slideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Container( padding: const EdgeInsets.all(20), margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: AppColor.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppColor.primary.withOpacity(0.08), blurRadius: 20, offset: const Offset(0, 8), ), ], border: Border.all( color: AppColor.border.withOpacity(0.5), ), ), child: Column( children: [ Text( widget.message ?? 'Sepertinya ada masalah teknis yang tidak terduga. Jangan khawatir, tim kami sedang bekerja keras untuk memperbaikinya!', style: AppStyle.lg.copyWith( color: AppColor.textSecondary, height: 1.6, ), textAlign: TextAlign.center, ), if (widget.errorCode != null) ...[ const SizedBox(height: 16), Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColor.error.withOpacity(0.1), AppColor.warning.withOpacity(0.1), ], ), borderRadius: BorderRadius.circular(20), border: Border.all( color: AppColor.error.withOpacity(0.3), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.code, size: 16, color: AppColor.error, ), const SizedBox(width: 8), Text( 'Kode Error: ${widget.errorCode}', style: AppStyle.sm.copyWith( color: AppColor.error, fontWeight: FontWeight.w600, ), ), ], ), ), ], ], ), ), ), ), const SizedBox(height: 50), // Animated Buttons SlideTransition( position: _slideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Column( children: [ // Retry Button with gradient if (widget.onRetry != null) Container( width: double.infinity, height: 60, decoration: BoxDecoration( gradient: LinearGradient( colors: AppColor.primaryGradient, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppColor.primary.withOpacity(0.4), blurRadius: 15, offset: const Offset(0, 8), ), ], ), child: ElevatedButton.icon( onPressed: widget.onRetry, icon: const Icon( Icons.refresh_rounded, color: AppColor.textWhite, size: 24, ), label: Text( 'Coba Lagi', style: AppStyle.xl.copyWith( color: AppColor.textWhite, fontWeight: FontWeight.bold, ), ), style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), ), ), const SizedBox(height: 16), // Back Button with modern design if (widget.onBack != null) Container( width: double.infinity, height: 60, decoration: BoxDecoration( color: AppColor.surface, borderRadius: BorderRadius.circular(16), border: Border.all( width: 2, color: AppColor.primary.withOpacity(0.3), ), boxShadow: [ BoxShadow( color: AppColor.primary.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: OutlinedButton.icon( onPressed: widget.onBack, icon: Icon( Icons.arrow_back_ios_rounded, color: AppColor.primary, size: 20, ), label: Text( 'Kembali', style: AppStyle.xl.copyWith( color: AppColor.primary, fontWeight: FontWeight.bold, ), ), style: OutlinedButton.styleFrom( backgroundColor: Colors.transparent, side: BorderSide.none, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), ), ), ], ), ), ), const SizedBox(height: 40), // Help text with icon FadeTransition( opacity: _fadeAnimation, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColor.info.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColor.info.withOpacity(0.1)), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.help_outline_rounded, size: 18, color: AppColor.info, ), const SizedBox(width: 8), Flexible( child: Text( 'Butuh bantuan? Hubungi tim support kami', style: AppStyle.sm.copyWith( color: AppColor.info, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), ], ), ), ), ], ), ), ), ), ); } } // Usage Example dengan berbagai variasi class ErrorPageExamples extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // Network Error ElevatedButton( onPressed: () => Navigator.push( context, MaterialPageRoute( builder: (context) => ErrorPage( title: 'Koneksi Terputus', message: 'Sepertinya koneksi internet Anda bermasalah. Periksa jaringan dan coba lagi.', errorCode: 'NET_404', errorIcon: Icons.wifi_off_rounded, onRetry: () => Navigator.pop(context), onBack: () => Navigator.pop(context), ), ), ), child: Text('Network Error'), ), // Server Error ElevatedButton( onPressed: () => Navigator.push( context, MaterialPageRoute( builder: (context) => ErrorPage( title: 'Server Sibuk', message: 'Server sedang mengalami gangguan. Tim kami sedang memperbaikinya.', errorCode: 'SRV_500', errorIcon: Icons.dns_rounded, onRetry: () => Navigator.pop(context), onBack: () => Navigator.pop(context), ), ), ), child: Text('Server Error'), ), ], ); } }