import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'dashboard_admin.dart'; import 'dashboard_guru.dart'; import 'dashboard_ortu.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final email = TextEditingController(); final password = TextEditingController(); bool isLoading = false; bool obscurePassword = true; @override void dispose() { email.dispose(); password.dispose(); super.dispose(); } Future login() async { if (email.text.trim().isEmpty || password.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Email dan Password wajib diisi'), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), ), ); return; } setState(() => isLoading = true); try { final userCred = await FirebaseAuth.instance.signInWithEmailAndPassword( email: email.text.trim(), password: password.text.trim(), ); final doc = await FirebaseFirestore.instance .collection('users') .doc(userCred.user!.uid) .get(); final data = doc.data(); if (data == null) throw Exception("Data pengguna tidak ditemukan"); final role = data['role']; if (!mounted) return; if (role == 'admin') { Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const DashboardAdmin())); } else if (role == 'guru') { Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const DashboardGuru())); } else { Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const DashboardOrtu())); } } on FirebaseAuthException catch (e) { String pesan = "Login gagal"; if (e.code == "user-not-found") pesan = "Pengguna tidak ditemukan"; else if (e.code == "wrong-password") pesan = "Kata sandi salah"; else if (e.code == "invalid-email") pesan = "Format email tidak valid"; if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(pesan), behavior: SnackBarBehavior.floating, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Kesalahan: $e"), behavior: SnackBarBehavior.floating, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), ), ); } } if (mounted) setState(() => isLoading = false); } // 📝 Diperkecil tinggi container field dari 54 menjadi 48 Widget inputField({ required TextEditingController controller, required String hint, required IconData icon, bool obscure = false, Widget? suffixIcon, }) { return Container( height: 48, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Color(0x0F7F56D9), blurRadius: 8, offset: Offset(0, 3), ) ], border: Border.all(color: const Color(0xffE6E0FF), width: 1), ), child: TextField( controller: controller, obscureText: obscure, style: const TextStyle(fontSize: 14, color: Color(0xff2D2669)), decoration: InputDecoration( border: InputBorder.none, hintText: hint, hintStyle: const TextStyle(color: Color(0xffA098C7), fontSize: 13), prefixIcon: Icon(icon, color: const Color(0xff7F56D9), size: 18), suffixIcon: suffixIcon, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), ), ), ); } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; final screenHeight = screenSize.height; // ⚡ Penyesuaian proporsi tinggi komponen khusus layar Vivo Y02t agar lebih ringkas final double illustrationHeight = screenHeight * 0.14; // Diturunkan ke ~14% final double spacingTop = screenHeight * 0.015; final double spacingMiddle = screenHeight * 0.018; return Scaffold( backgroundColor: const Color(0xffF5F2FF), resizeToAvoidBottomInset: true, body: SafeArea( child: SingleChildScrollView( physics: const ClampingScrollPhysics(), child: Stack( children: [ // DEKORASI LATAR BELAKANG _buildBackgroundDecorations(screenHeight), // KONTEN UTAMA Center( child: Container( constraints: const BoxConstraints(maxWidth: 400), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: spacingTop), // Logo Sekolah (Diperkecil sedikit rasio layarnya) Image.asset( 'lib/assets/sekolah.png', height: screenHeight * 0.07, fit: BoxFit.contain, errorBuilder: (_, __, ___) => const Icon( Icons.school, size: 55, color: Color(0xff7F56D9), ), ), const SizedBox(height: 6), // Ukuran font judul dikurangi agar tidak memakan tempat banyak const Text( "ABSENSI", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xff2D2669), letterSpacing: 1.2, ), ), const Text( "— SISTEM ABSENSI PGRI —", style: TextStyle( fontSize: 11, color: Color(0xff7F56D9), fontWeight: FontWeight.w500, letterSpacing: 0.8, ), ), const SizedBox(height: 2), const Text( "TK PGRI BHAKTI LESTARI", textAlign: TextAlign.center, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Color(0xff2D2669), ), ), SizedBox(height: spacingMiddle), // ILUSTRASI ANAK (RESPONSIF & DIBATASI LEBIH KECIL) _buildStudentIllustration(illustrationHeight), SizedBox(height: spacingMiddle), // KOTAK LOGIN (Padding dikurangi) Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow( color: Color(0x127F56D9), blurRadius: 15, offset: Offset(0, 4), ) ], border: Border.all(color: const Color(0xffE6E0FF), width: 1.2), ), child: Column( children: [ const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("≡ ", style: TextStyle(fontSize: 16, color: Color(0xff7F56D9), fontWeight: FontWeight.bold)), Text("Login", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xff2D2669))), Text(" ≡", style: TextStyle(fontSize: 16, color: Color(0xff7F56D9), fontWeight: FontWeight.bold)), ], ), const SizedBox(height: 14), inputField( controller: email, hint: "Email", icon: Icons.email_outlined, ), const SizedBox(height: 10), inputField( controller: password, hint: "Password", icon: Icons.lock_outline, obscure: obscurePassword, suffixIcon: IconButton( icon: Icon( obscurePassword ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: const Color(0xffA098C7), size: 18, ), onPressed: () => setState(() => obscurePassword = !obscurePassword), ), ), const SizedBox(height: 16), // Tombol Login (Tinggi diturunkan dari 50 ke 44) SizedBox( width: double.infinity, height: 44, child: ElevatedButton( onPressed: isLoading ? null : login, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xff7F56D9), disabledBackgroundColor: const Color(0xffB7A3E8), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 1.5, shadowColor: const Color(0xff7F56D9).withOpacity(0.2), ), child: isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.2), ) : const Text( "Login", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white), ), ), ), const SizedBox(height: 10), const Text( "Gunakan akun yang telah terdaftar", style: TextStyle(fontSize: 10, color: Color(0xff7F56D9)), ), const SizedBox(height: 10), // Garis pemisah Row( children: [ const Expanded(child: Divider(color: Color(0xffE0DBF5), thickness: 1, indent: 6, endIndent: 6)), Container( padding: const EdgeInsets.all(5), decoration: const BoxDecoration(color: Color(0xffF5F2FF), shape: BoxShape.circle), child: const Icon(Icons.shield_outlined, color: Color(0xff7F56D9), size: 12), ), const Expanded(child: Divider(color: Color(0xffE0DBF5), thickness: 1, indent: 6, endIndent: 6)), ], ), ], ), ), const SizedBox(height: 12), // INFORMASI PENGEMBANG (Diperkecil padding dan ukuran teksnya) Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: const Color(0xffEDE9FF), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xffDCD4FF)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Dikembangkan oleh:", style: TextStyle(fontSize: 9, color: Color(0xff7F56D9), fontWeight: FontWeight.w500)), SizedBox(height: 1), Text("Quri Amaliatas Solehah", style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: Color(0xff2D2669))), SizedBox(height: 1), Text("Politeknik Negeri Jember", style: TextStyle(fontSize: 10, color: Color(0xff4A4385))), Text("Jurusan Teknologi Informasi | Teknik Komputer 2023", style: TextStyle(fontSize: 8, color: Color(0xff7F56D9))), ], ), ), const SizedBox(width: 4), Image.asset( 'lib/assets/jti.png', height: 18, fit: BoxFit.contain, errorBuilder: (_, __, ___) => const Icon(Icons.school_outlined, size: 18, color: Color(0xff7F56D9)), ), ], ), ), ], ), ), ), ], ), ), ), ); } Widget _buildBackgroundDecorations(double height) { return Positioned.fill( child: Stack( children: [ Positioned( top: -50, left: -50, child: Container(width: 140, height: 140, decoration: BoxDecoration(color: const Color(0xff7F56D9).withOpacity(0.05), shape: BoxShape.circle)), ), Positioned( top: 40, right: -25, child: Container(width: 80, height: 80, decoration: BoxDecoration(color: const Color(0xff7F56D9).withOpacity(0.04), shape: BoxShape.circle)), ), Positioned( bottom: -30, left: -15, child: Container(width: 100, height: 100, decoration: BoxDecoration(color: const Color(0xff7F56D9).withOpacity(0.04), shape: BoxShape.circle)), ), Positioned(top: 40, left: 20, child: _buildDotGrid()), Positioned(top: 60, right: 30, child: _buildDotGrid()), Positioned(bottom: 80, right: 20, child: _buildDotGrid()), ], ), ); } Widget _buildDotGrid() { return Opacity( opacity: 0.15, child: Column( children: List.generate(3, (_) => Row( children: List.generate(3, (_) => Container( width: 2.5, height: 2.5, margin: const EdgeInsets.all(2.0), decoration: const BoxDecoration(color: Color(0xff7F56D9), shape: BoxShape.circle), )), )), ), ); } Widget _buildStudentIllustration(double dynamicHeight) { return Container( width: double.infinity, height: dynamicHeight.clamp(110.0, 140.0), // Rentang clamp diturunkan agar pas di layar Vivo Y02t padding: const EdgeInsets.all(10), decoration: BoxDecoration( gradient: LinearGradient( colors: [const Color(0xffEDE9FF), const Color(0xffDCD4FF).withOpacity(0.5)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xffD1C8FF), width: 1), ), child: Stack( children: [ Positioned(top: 8, left: 12, child: Icon(Icons.cloud_outlined, color: const Color(0xff7F56D9).withOpacity(0.25), size: 18)), Positioned(bottom: 12, right: 16, child: Icon(Icons.star_border, color: const Color(0xff7F56D9).withOpacity(0.25), size: 16)), Center( child: Image.asset( 'lib/assets/anak_rfid.png', height: (dynamicHeight - 20).clamp(90.0, 120.0), fit: BoxFit.contain, errorBuilder: (_, __, ___) => const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.image_not_supported, size: 35, color: Color(0xff7F56D9)), SizedBox(height: 2), Text("Masukkan gambar ilustrasi_anak.png", style: TextStyle(color: Color(0xff7F56D9), fontSize: 10)), ], ), ), ), ], ), ); } }