import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:dio/dio.dart'; import '../../../core/api_service.dart'; import '../../../core/secure_storage.dart'; import '../../home/presentation/guardian_dashboard_screen.dart'; import '../../home/presentation/user_dashboard_screen.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final ApiService _apiService = ApiService(); final SecureStorage _secureStorage = SecureStorage(); bool _isLoading = false; bool _isPasswordVisible = false; Future _handleLogin() async { setState(() => _isLoading = true); try { final response = await _apiService.post('/auth/login', { 'email': _emailController.text.trim(), 'password': _passwordController.text.trim(), }); if (response.statusCode == 200) { // Ekstraksi data dari ApiResponse wrapper (success, data, message) final responseBody = response.data; if (responseBody['success'] == true) { final userData = responseBody['data']; final String token = userData['token']; final String role = userData['role']; // Simpan token ke storage aman await _secureStorage.saveToken(token); // Routing berdasarkan Role if (mounted) { if (role == 'ROLE_GUARDIAN') { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const GuardianDashboardScreen()), ); } else { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const UserDashboardScreen()), ); } } } else { throw Exception(responseBody['message'] ?? 'Login gagal'); } } } on DioException catch (e) { if (mounted) { // Ambil pesan error dari GlobalExceptionHandler di Spring Boot String errorMsg = 'Gagal Terhubung ke Server'; if (e.response?.data != null && e.response?.data is Map) { errorMsg = e.response?.data['message'] ?? errorMsg; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(errorMsg), backgroundColor: Colors.redAccent, behavior: SnackBarBehavior.floating, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString())), ); } } finally { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF8FAFC), body: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Logo/Icon Header Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF2563EB).withValues(alpha: 0.1), shape: BoxShape.circle, ), child: const Icon( Icons.navigation_rounded, size: 48, color: Color(0xFF2563EB) ), ), const SizedBox(height: 24), Text( 'Walk Guide', style: GoogleFonts.outfit( fontSize: 32, fontWeight: FontWeight.bold, color: const Color(0xFF0F172A) ), ), const SizedBox(height: 8), Text( 'Masuk untuk mulai navigasi', style: GoogleFonts.inter( fontSize: 15, color: const Color(0xFF64748B) ), ), const SizedBox(height: 40), // Input Fields _buildTextField( _emailController, Icons.alternate_email, 'Email', false ), const SizedBox(height: 20), _buildTextField( _passwordController, Icons.lock_outline, 'Password', true ), const SizedBox(height: 48), // Button Masuk SizedBox( width: double.infinity, height: 56, child: ElevatedButton( onPressed: _isLoading ? null : _handleLogin, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2563EB), foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16) ), ), child: _isLoading ? const SizedBox( height: 24, width: 24, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2 ), ) : Text( 'Masuk', style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600 ) ), ), ), ], ), ), ), ); } Widget _buildTextField( TextEditingController controller, IconData icon, String hint, bool isPassword ) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.02), blurRadius: 10, offset: const Offset(0, 4) ) ], ), child: TextField( controller: controller, obscureText: isPassword && !_isPasswordVisible, style: GoogleFonts.inter(fontSize: 15), decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle(color: Color(0xFF94A3B8)), prefixIcon: Icon(icon, color: const Color(0xFF94A3B8)), suffixIcon: isPassword ? IconButton( icon: Icon( _isPasswordVisible ? Icons.visibility_off : Icons.visibility, color: const Color(0xFF94A3B8) ), onPressed: () => setState(() => _isPasswordVisible = !_isPasswordVisible), ) : null, border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), ), ), ); } }