420 lines
15 KiB
Dart
420 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../../../core/theme/app_colors.dart';
|
|
import '../../../../core/services/auth_service.dart';
|
|
import '../../../../core/services/session_service.dart';
|
|
import '../../../../l10n/app_localizations.dart';
|
|
|
|
class SplashPage extends StatefulWidget {
|
|
const SplashPage({super.key});
|
|
|
|
@override
|
|
State<SplashPage> createState() => _SplashPageState();
|
|
}
|
|
|
|
class _SplashPageState extends State<SplashPage> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_checkAuthenticationAndNavigate();
|
|
}
|
|
|
|
void _checkAuthenticationAndNavigate() async {
|
|
try {
|
|
print('DEBUG: Checking authentication state...');
|
|
|
|
// Always show splash for full 3 seconds for complete animation
|
|
await Future.delayed(const Duration(seconds: 3));
|
|
|
|
// Check if user is currently authenticated
|
|
final currentUser = AuthService.currentUser;
|
|
|
|
if (currentUser != null) {
|
|
print('DEBUG: User already authenticated: ${currentUser.email}');
|
|
print(
|
|
'DEBUG: User displayName before reload: ${currentUser.displayName}',
|
|
);
|
|
|
|
// Reload user data to get latest information from Firebase
|
|
await currentUser.reload();
|
|
|
|
// Get the updated user
|
|
final updatedUser = AuthService.currentUser;
|
|
print(
|
|
'DEBUG: User displayName after reload: ${updatedUser?.displayName}',
|
|
);
|
|
|
|
// Update session with current user if needed
|
|
await SessionService.updateSessionWithCurrentUser();
|
|
|
|
// Navigate to dashboard
|
|
if (mounted) {
|
|
print('DEBUG: Navigating to student dashboard');
|
|
context.go('/student-dashboard');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check if user should be auto-logged in
|
|
final sessionData = await SessionService.shouldAutoLogin();
|
|
|
|
if (sessionData['shouldAutoLogin'] == true) {
|
|
print('DEBUG: Auto-login available for: ${sessionData['email']}');
|
|
|
|
if (mounted) {
|
|
// Navigate to login page with pre-filled data
|
|
print('DEBUG: Navigating to login for auto-login');
|
|
context.go('/login');
|
|
}
|
|
} else {
|
|
print('DEBUG: No auto-login available, going to role selection');
|
|
|
|
if (mounted) {
|
|
context.go('/role-selection');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('DEBUG: Error in authentication check: $e');
|
|
|
|
// Fallback to role selection
|
|
if (mounted) {
|
|
context.go('/role-selection');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
AppColors.background,
|
|
AppColors.primaryTeal.withOpacity(0.05),
|
|
AppColors.primaryOrange.withOpacity(0.03),
|
|
AppColors.background,
|
|
],
|
|
),
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
// Animated background particles
|
|
...List.generate(25, (index) => _buildParticle(index)),
|
|
|
|
// Main content
|
|
Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// Modern logo with gradient animation
|
|
Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
// Outer ring animation
|
|
Container(
|
|
width: 160,
|
|
height: 160,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: AppColors.primaryTeal.withOpacity(0.2),
|
|
width: 2,
|
|
),
|
|
),
|
|
)
|
|
.animate()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 2000),
|
|
curve: Curves.easeInOut,
|
|
begin: const Offset(0.8, 0.8),
|
|
end: const Offset(1.2, 1.2),
|
|
)
|
|
.then()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 2000),
|
|
curve: Curves.easeInOut,
|
|
begin: const Offset(1.2, 1.2),
|
|
end: const Offset(0.8, 0.8),
|
|
),
|
|
|
|
// Middle ring animation
|
|
Container(
|
|
width: 120,
|
|
height: 120,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: AppColors.primaryOrange.withOpacity(0.3),
|
|
width: 3,
|
|
),
|
|
),
|
|
)
|
|
.animate()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 1500),
|
|
curve: Curves.easeInOut,
|
|
begin: const Offset(1.0, 1.0),
|
|
end: const Offset(1.1, 1.1),
|
|
)
|
|
.then()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 1500),
|
|
curve: Curves.easeInOut,
|
|
begin: const Offset(1.1, 1.1),
|
|
end: const Offset(1.0, 1.0),
|
|
),
|
|
|
|
// Main logo container
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [
|
|
AppColors.primaryTeal,
|
|
AppColors.primaryOrange,
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.primaryTeal.withOpacity(0.3),
|
|
blurRadius: 20,
|
|
offset: const Offset(0, 8),
|
|
),
|
|
BoxShadow(
|
|
color: AppColors.primaryOrange.withOpacity(
|
|
0.2,
|
|
),
|
|
blurRadius: 15,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: const Icon(
|
|
Icons.school,
|
|
size: 40,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
.animate()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 800),
|
|
curve: Curves.elasticOut,
|
|
)
|
|
.then()
|
|
.shimmer(
|
|
duration: const Duration(milliseconds: 2500),
|
|
color: Colors.white.withOpacity(0.4),
|
|
)
|
|
.then()
|
|
.rotate(
|
|
duration: const Duration(milliseconds: 1000),
|
|
curve: Curves.easeInOut,
|
|
begin: 0.0,
|
|
end: 0.05,
|
|
)
|
|
.then()
|
|
.rotate(
|
|
duration: const Duration(milliseconds: 1000),
|
|
curve: Curves.easeInOut,
|
|
begin: 0.05,
|
|
end: 0.0,
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
|
|
// School name with gradient
|
|
ShaderMask(
|
|
shaderCallback: (bounds) => const LinearGradient(
|
|
colors: [
|
|
AppColors.primaryTeal,
|
|
AppColors.primaryOrange,
|
|
],
|
|
begin: Alignment.centerLeft,
|
|
end: Alignment.centerRight,
|
|
).createShader(bounds),
|
|
child: Text(
|
|
AppLocalizations.of(context)!.appTitle,
|
|
style: Theme.of(context).textTheme.headlineMedium
|
|
?.copyWith(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 1.2,
|
|
),
|
|
),
|
|
)
|
|
.animate()
|
|
.fadeIn(
|
|
duration: const Duration(milliseconds: 1000),
|
|
delay: const Duration(milliseconds: 500),
|
|
)
|
|
.slideY(
|
|
duration: const Duration(milliseconds: 800),
|
|
delay: const Duration(milliseconds: 500),
|
|
begin: 0.3,
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// School name subtitle
|
|
ShaderMask(
|
|
shaderCallback: (bounds) => LinearGradient(
|
|
colors: [
|
|
AppColors.primaryOrange.withOpacity(0.8),
|
|
AppColors.primaryTeal.withOpacity(0.8),
|
|
],
|
|
begin: Alignment.centerLeft,
|
|
end: Alignment.centerRight,
|
|
).createShader(bounds),
|
|
child: Text(
|
|
AppLocalizations.of(context)!.schoolName,
|
|
style: Theme.of(context).textTheme.bodyMedium
|
|
?.copyWith(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
)
|
|
.animate()
|
|
.fadeIn(
|
|
duration: const Duration(milliseconds: 1000),
|
|
delay: const Duration(milliseconds: 700),
|
|
)
|
|
.slideY(
|
|
duration: const Duration(milliseconds: 800),
|
|
delay: const Duration(milliseconds: 700),
|
|
begin: 0.2,
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
|
|
// Modern loading indicator
|
|
Column(
|
|
children: [
|
|
// Loading dots
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_buildLoadingDot(0),
|
|
const SizedBox(width: 8),
|
|
_buildLoadingDot(1),
|
|
const SizedBox(width: 8),
|
|
_buildLoadingDot(2),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Loading text
|
|
Text(
|
|
'A preparar a sua experiência...',
|
|
style: Theme.of(context).textTheme.bodySmall
|
|
?.copyWith(
|
|
color: AppColors.primaryOrange,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
)
|
|
.animate()
|
|
.fadeIn(
|
|
duration: const Duration(milliseconds: 800),
|
|
delay: const Duration(milliseconds: 1200),
|
|
)
|
|
.then()
|
|
.shimmer(
|
|
duration: const Duration(milliseconds: 2000),
|
|
color: AppColors.primaryTeal.withOpacity(0.3),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLoadingDot(int index) {
|
|
return Container(
|
|
width: 12,
|
|
height: 12,
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [AppColors.primaryTeal, AppColors.primaryOrange],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
shape: BoxShape.circle,
|
|
),
|
|
)
|
|
.animate()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 600),
|
|
delay: Duration(milliseconds: index * 200),
|
|
curve: Curves.elasticOut,
|
|
begin: const Offset(0.0, 0.0),
|
|
end: const Offset(1.0, 1.0),
|
|
)
|
|
.then()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 400),
|
|
delay: Duration(milliseconds: 800 + index * 200),
|
|
begin: const Offset(1.0, 1.0),
|
|
end: const Offset(0.8, 0.8),
|
|
)
|
|
.then()
|
|
.scale(
|
|
duration: const Duration(milliseconds: 400),
|
|
begin: const Offset(0.8, 0.8),
|
|
end: const Offset(1.0, 1.0),
|
|
);
|
|
}
|
|
|
|
Widget _buildParticle(int index) {
|
|
final random = index * 137.5;
|
|
return Positioned(
|
|
top: (random % 400) + 50,
|
|
left: (random % 300) + 50,
|
|
child:
|
|
Container(
|
|
width: 2,
|
|
height: 2,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.primaryTeal.withOpacity(0.3),
|
|
AppColors.primaryOrange.withOpacity(0.2),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
shape: BoxShape.circle,
|
|
),
|
|
)
|
|
.animate()
|
|
.moveY(
|
|
duration: Duration(milliseconds: 4000 + (index * 200)),
|
|
begin: 0.0,
|
|
end: -200.0,
|
|
curve: Curves.easeInOut,
|
|
)
|
|
.fadeIn(
|
|
duration: const Duration(milliseconds: 1000),
|
|
delay: Duration(milliseconds: index * 150),
|
|
)
|
|
.then()
|
|
.fadeOut(duration: const Duration(milliseconds: 1000)),
|
|
);
|
|
}
|
|
}
|