This commit is contained in:
Phuoc Nguyen
2025-10-21 16:45:52 +07:00
parent 9c20a44a04
commit 30c245b401
3 changed files with 168 additions and 16 deletions

View File

@@ -64,9 +64,9 @@ class AuthState {
class Auth extends _$Auth { class Auth extends _$Auth {
@override @override
AuthState build() { AuthState build() {
// Don't call async operations in build // Start with loading state to show splash screen
// Use a separate method to initialize auth state // Use a separate method to initialize auth state
return const AuthState(); return const AuthState(isLoading: true);
} }
AuthRepository get _repository => ref.read(authRepositoryProvider); AuthRepository get _repository => ref.read(authRepositoryProvider);
@@ -74,7 +74,9 @@ class Auth extends _$Auth {
/// Initialize auth state - call this on app start /// Initialize auth state - call this on app start
Future<void> initialize() async { Future<void> initialize() async {
print('🚀 Initializing auth state...'); print('🚀 Initializing auth state...');
state = state.copyWith(isLoading: true);
// Minimum loading time for smooth UX (prevent flashing)
final minimumLoadingTime = Future.delayed(const Duration(milliseconds: 800));
final isAuthenticated = await _repository.isAuthenticated(); final isAuthenticated = await _repository.isAuthenticated();
print('🚀 isAuthenticated result: $isAuthenticated'); print('🚀 isAuthenticated result: $isAuthenticated');
@@ -83,6 +85,10 @@ class Auth extends _$Auth {
print('🚀 Token found, fetching user profile...'); print('🚀 Token found, fetching user profile...');
// Get user profile // Get user profile
final result = await _repository.getProfile(); final result = await _repository.getProfile();
// Wait for minimum loading time to complete
await minimumLoadingTime;
result.fold( result.fold(
(failure) { (failure) {
print('❌ Failed to get profile: ${failure.message}'); print('❌ Failed to get profile: ${failure.message}');
@@ -103,6 +109,10 @@ class Auth extends _$Auth {
); );
} else { } else {
print('❌ No token found, user needs to login'); print('❌ No token found, user needs to login');
// Wait for minimum loading time even when not authenticated
await minimumLoadingTime;
state = const AuthState( state = const AuthState(
isAuthenticated: false, isAuthenticated: false,
isLoading: false, isLoading: false,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/auth_provider.dart'; import '../providers/auth_provider.dart';
import '../pages/login_page.dart'; import '../pages/login_page.dart';
import 'splash_screen.dart';
/// Wrapper widget that checks authentication status /// Wrapper widget that checks authentication status
/// Shows login page if not authenticated, otherwise shows child widget /// Shows login page if not authenticated, otherwise shows child widget
@@ -17,20 +18,26 @@ class AuthWrapper extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider); final authState = ref.watch(authProvider);
print('AuthWrapper build: isAuthenticated=${authState.isAuthenticated}, isLoading=${authState.isLoading}'); print('AuthWrapper build: isAuthenticated=${authState.isAuthenticated}, isLoading=${authState.isLoading}');
// Show loading indicator while checking auth status
// Show splash screen while checking auth status
if (authState.isLoading && authState.user == null) { if (authState.isLoading && authState.user == null) {
return const Scaffold( return const SplashScreen();
body: Center( }
child: CircularProgressIndicator(),
// Smooth fade transition between screens
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeInOut,
child: authState.isAuthenticated
? KeyedSubtree(
key: const ValueKey('main_app'),
child: child,
)
: const KeyedSubtree(
key: ValueKey('login_page'),
child: LoginPage(),
), ),
); );
} }
// Show child widget if authenticated, otherwise show login page
if (authState.isAuthenticated) {
return child;
} else {
return const LoginPage();
}
}
} }

View File

@@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
/// Splash screen shown while checking authentication status
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
);
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeOutBack,
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
backgroundColor: theme.colorScheme.primary,
body: SafeArea(
child: Center(
child: FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// App Icon/Logo
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Icon(
Icons.point_of_sale_rounded,
size: 64,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 32),
// App Name
Text(
'Retail POS',
style: theme.textTheme.headlineMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 8),
// Subtitle
Text(
'Point of Sale System',
style: theme.textTheme.bodyLarge?.copyWith(
color: Colors.white.withOpacity(0.9),
letterSpacing: 0.5,
),
),
const SizedBox(height: 48),
// Loading Indicator
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white.withOpacity(0.9),
),
),
),
const SizedBox(height: 16),
// Loading Text
Text(
'Loading...',
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.8),
),
),
],
),
),
),
),
),
);
}
}