import 'dart:ui'; import 'package:flutter/material.dart'; import '../../../services/auth_service.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; class ProfileAdminScreen extends StatefulWidget { const ProfileAdminScreen({super.key}); @override State createState() => _ProfileAdminScreenState(); } class _ProfileAdminScreenState extends State { bool isLoading = true; bool isProcessing = false; Map userData = {}; File? _imageFile; final ImagePicker _picker = ImagePicker(); bool showOldPassword = false; bool showNewPassword = false; bool showConfirmPassword = false; // ── Design tokens ────────────────────────────────────────────── static const _blue = Color(0xFF3B82F6); static const _bgPage = Color(0xFFF0F4FA); static const _cardBg = Colors.white; static const _textPrimary = Color(0xFF0F172A); static const _textSecondary = Color(0xFF64748B); static const _textHint = Color(0xFF94A3B8); static const _border = Color(0xFFE2E8F0); @override void initState() { super.initState(); loadProfile(); } Future loadProfile() async { final result = await AuthService.getProfile(); if (!mounted) return; if (result['success']) { setState(() { userData = result['data']; isLoading = false; }); } else { setState(() => isLoading = false); } } Future pickImage() async { try { final XFile? pickedFile = await _picker.pickImage( source: ImageSource.gallery, imageQuality: 85, ); if (pickedFile == null) return; final file = File(pickedFile.path); setState(() => _imageFile = file); await uploadPhoto(file); } catch (e) { if (mounted) { setState(() { isProcessing = false; _imageFile = null; }); _showSnack('Gagal memilih foto: $e', Colors.red); } } } Future uploadPhoto(File image) async { setState(() => isProcessing = true); try { final result = await AuthService.uploadPhoto(image); if (!mounted) return; if (result['status'] == true) { setState(() { userData['foto'] = result['data']['foto']; _imageFile = null; }); _showSnack("Foto profil berhasil diperbarui", Colors.green); } else { _showSnack("Upload gagal: ${result['message'] ?? 'Coba lagi'}", Colors.red); } } catch (e) { if (!mounted) return; _showSnack("Error: $e", Colors.red); } finally { if (mounted) setState(() => isProcessing = false); } } Future deletePhoto() async { setState(() => isProcessing = true); try { final result = await AuthService.deletePhoto(); if (!mounted) return; if (result['status'] == true) { setState(() { userData['foto'] = null; _imageFile = null; }); _showSnack("Foto profil berhasil dihapus", Colors.green); } else { _showSnack("Gagal hapus foto: ${result['message'] ?? 'Coba lagi'}", Colors.red); } } catch (e) { _showSnack("Error: $e", Colors.red); } finally { if (mounted) setState(() => isProcessing = false); } } void _showSnack(String msg, Color color) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(msg), backgroundColor: color), ); } Widget buildLoading() { if (!isProcessing) return const SizedBox(); return Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Container( color: Colors.black.withValues(alpha: 0.25), child: const Center( child: CircularProgressIndicator(color: Colors.white), ), ), ), ); } @override Widget build(BuildContext context) { if (isLoading) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( backgroundColor: _bgPage, body: Stack( children: [ SingleChildScrollView( child: Column(children: [_buildHeader(), _buildCard()]), ), buildLoading(), ], ), ); } Widget _buildHeader() { return Container( width: double.infinity, height: 300, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/gedunga3.jpg'), fit: BoxFit.cover, ), ), child: Container( decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.5)), child: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 16), GestureDetector( onTap: () { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (_) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), padding: const EdgeInsets.symmetric(vertical: 12), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), const Text( "Foto Profil", style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(height: 8), ListTile( leading: const Icon(Icons.image_rounded, color: _blue), title: const Text("Ganti Foto"), onTap: () { Navigator.pop(context); pickImage(); }, ), if (userData['foto'] != null && userData['foto'] != '') ListTile( leading: const Icon(Icons.delete_rounded, color: Colors.red), title: const Text("Hapus Foto", style: TextStyle(color: Colors.red)), onTap: () { Navigator.pop(context); _confirmDeletePhoto(); }, ), const SizedBox(height: 8), ], ), ); }, ); }, child: Stack( children: [ Container( width: 98, height: 98, decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( colors: [ _blue, Color(0xFF60A5FA), Color(0xFF93C5FD) ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), ), Positioned( top: 3, left: 3, right: 3, bottom: 3, child: CircleAvatar( backgroundColor: const Color(0xFF1E3A5F), backgroundImage: _imageFile != null ? FileImage(_imageFile!) : (userData['foto'] != null && userData['foto'] != '') ? NetworkImage(userData['foto']) : null, child: (_imageFile == null && (userData['foto'] == null || userData['foto'] == '')) ? const Icon(Icons.person_rounded, size: 44, color: _blue) : null, ), ), Positioned( bottom: 2, right: 2, child: Container( width: 26, height: 26, decoration: BoxDecoration( shape: BoxShape.circle, color: _blue, border: Border.all(color: Colors.white, width: 2), ), child: const Icon(Icons.edit_rounded, size: 12, color: Colors.white), ), ), ], ), ), const SizedBox(height: 14), Text( userData['name'] ?? '-', style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w700, letterSpacing: 0.2, ), ), const SizedBox(height: 4), Text( userData['email'] ?? '-', style: TextStyle( color: Colors.white.withValues(alpha: 0.6), fontSize: 13), ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5), decoration: BoxDecoration( color: _blue.withValues(alpha: 0.25), borderRadius: BorderRadius.circular(20), border: Border.all( color: _blue.withValues(alpha: 0.55), width: 0.5), ), child: Text( userData['role'] == 'super_admin' ? 'SUPER ADMIN' : 'ADMINISTRATOR', style: const TextStyle( color: Color(0xFF93C5FD), fontSize: 10.5, fontWeight: FontWeight.w600, letterSpacing: 1.2, ), ), ), ], ), ), ), ); } Widget _buildCard() { return Transform.translate( offset: const Offset(0, -28), child: Container( decoration: const BoxDecoration( color: _bgPage, borderRadius: BorderRadius.vertical(top: Radius.circular(28)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Container( margin: const EdgeInsets.only(top: 10, bottom: 20), width: 38, height: 4, decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(2), ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ _buildInfoTile('Nama', userData['name'] ?? '-', const Color(0xFFEFF6FF), _blue), const SizedBox(width: 10), _buildInfoTile( 'Role', userData['role'] == 'super_admin' ? 'Super Admin' : 'Administrator', const Color(0xFFF0FDFA), const Color(0xFF0D9488), ), ], ), ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.symmetric(horizontal: 22), child: Text( 'PENGATURAN AKUN', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _textHint, letterSpacing: 1.3, ), ), ), const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ _buildMenuItem( icon: Icons.person_outline_rounded, iconBg: const Color(0xFFEFF6FF), iconColor: _blue, title: 'Edit Nama', subtitle: userData['name'] ?? '-', onTap: editName, ), _buildMenuItem( icon: Icons.email_outlined, iconBg: const Color(0xFFF0FDFA), iconColor: const Color(0xFF0D9488), title: 'Ubah Email', subtitle: userData['email'] ?? '-', onTap: changeEmail, ), _buildMenuItem( icon: Icons.lock_outline_rounded, iconBg: const Color(0xFFFFF7ED), iconColor: const Color(0xFFF97316), title: 'Ubah Password', subtitle: 'Diperbarui baru-baru ini', onTap: changePassword, ), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Divider(color: _border, thickness: 0.5), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildMenuItem( icon: Icons.logout_rounded, iconBg: const Color(0xFFFEE2E2), iconColor: const Color(0xFFEF4444), title: 'Keluar Akun', subtitle: 'Sesi akan dihapus', onTap: confirmLogout, isLogout: true, ), ), const SizedBox(height: 32), ], ), ), ); } Widget _buildInfoTile( String label, String value, Color bg, Color valueColor) { return Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( color: _cardBg, borderRadius: BorderRadius.circular(16), border: Border.all(color: _border, width: 0.5), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label.toUpperCase(), style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _textHint, letterSpacing: 0.8, ), ), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: valueColor), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ); } Widget _buildMenuItem({ required IconData icon, required Color iconBg, required Color iconColor, required String title, required String subtitle, required VoidCallback onTap, bool isLogout = false, }) { return GestureDetector( onTap: onTap, child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: isLogout ? const Color(0xFFFFF5F5) : _cardBg, borderRadius: BorderRadius.circular(16), border: Border.all( color: isLogout ? const Color(0xFFEF4444).withValues(alpha: 0.15) : _border, width: 0.5, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: iconBg, borderRadius: BorderRadius.circular(13)), child: Icon(icon, color: iconColor, size: 18), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 13.5, fontWeight: FontWeight.w600, color: isLogout ? const Color(0xFFEF4444) : _textPrimary, ), ), const SizedBox(height: 2), Text( subtitle, style: TextStyle( fontSize: 11, color: isLogout ? const Color(0xFFFCA5A5) : _textHint, ), ), ], ), ), Container( width: 28, height: 28, decoration: BoxDecoration( color: isLogout ? const Color(0xFFFEE2E2) : const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(9), ), child: Icon( Icons.chevron_right_rounded, size: 16, color: isLogout ? const Color(0xFFEF4444) : _textHint, ), ), ], ), ), ); } // ══════════════════════════════════════════════════════════════════ // SHARED WIDGETS // ══════════════════════════════════════════════════════════════════ Widget _styledField({ required TextEditingController controller, required String label, required IconData prefixIcon, Color prefixColor = _blue, bool obscure = false, bool showToggle = false, bool toggleValue = false, VoidCallback? onToggle, String? hint, TextInputType? keyboardType, int? maxLength, ValueChanged? onChanged, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: _textSecondary, letterSpacing: 0.3, ), ), const SizedBox(height: 6), TextField( controller: controller, obscureText: obscure && !toggleValue, keyboardType: keyboardType, maxLength: maxLength, onChanged: onChanged, style: const TextStyle(fontSize: 13, color: _textPrimary), decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle(color: _textHint, fontSize: 13), prefixIcon: Icon(prefixIcon, color: prefixColor, size: 18), counterText: '', suffixIcon: showToggle ? IconButton( icon: Icon( toggleValue ? Icons.visibility_rounded : Icons.visibility_off_rounded, color: _textHint, size: 18, ), onPressed: onToggle, ) : null, filled: true, fillColor: const Color(0xFFFAFBFC), contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: _border, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: _border, width: 1.5), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: _blue, width: 1.5), ), ), ), ], ); } Widget _dialogShell({ required Color iconBg, required Color iconColor, required IconData icon, required String title, required String subtitle, required List fields, required String saveLabel, required VoidCallback onSave, Color? saveBtnColor, VoidCallback? onCancel, }) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), elevation: 0, backgroundColor: Colors.white, child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: iconBg, borderRadius: BorderRadius.circular(12)), child: Icon(icon, color: iconColor, size: 18), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: _textPrimary)), Text(subtitle, style: const TextStyle( fontSize: 11.5, color: _textHint)), ], ), ], ), const SizedBox(height: 20), ...fields, const SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton( onPressed: onCancel ?? () => Navigator.pop(context), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), side: const BorderSide(color: _border, width: 1.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: const Text('Batal', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: _textSecondary)), ), ), const SizedBox(width: 10), Expanded( child: ElevatedButton( onPressed: onSave, style: ElevatedButton.styleFrom( backgroundColor: saveBtnColor ?? _blue, padding: const EdgeInsets.symmetric(vertical: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: Text(saveLabel, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white)), ), ), ], ), ], ), ), ); } // ══════════════════════════════════════════════════════════════════ // DIALOGS // ══════════════════════════════════════════════════════════════════ void _confirmDeletePhoto() { showDialog( context: context, builder: (_) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 60, height: 60, decoration: const BoxDecoration( color: Color(0xFFFEE2E2), shape: BoxShape.circle), child: const Icon(Icons.delete_forever_rounded, color: Colors.red, size: 30), ), const SizedBox(height: 16), const Text("Hapus Foto?", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700)), const SizedBox(height: 6), const Text( "Foto profil kamu akan dihapus secara permanen.", textAlign: TextAlign.center, style: TextStyle(fontSize: 12, color: Colors.grey), ), const SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(context), child: const Text("Batal"), ), ), const SizedBox(width: 10), Expanded( child: ElevatedButton( onPressed: () { Navigator.pop(context); deletePhoto(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red), child: const Text("Hapus"), ), ), ], ), ], ), ), ), ); } void editName() { final controller = TextEditingController(text: userData['name']); showDialog( context: context, builder: (_) => _dialogShell( icon: Icons.person_outline_rounded, iconBg: const Color(0xFFEFF6FF), iconColor: _blue, title: 'Edit Nama', subtitle: 'Perbarui nama tampilan kamu', saveLabel: 'Simpan', fields: [ _styledField( controller: controller, label: 'Nama Lengkap', prefixIcon: Icons.person_outline_rounded, ), const SizedBox(height: 6), Row( children: [ Icon(Icons.info_outline_rounded, size: 12, color: _textHint), const SizedBox(width: 4), const Text('Nama ditampilkan di seluruh sistem', style: TextStyle(fontSize: 10.5, color: _textHint)), ], ), ], onSave: () async { setState(() => isProcessing = true); final result = await AuthService.updateProfile( name: controller.text, email: userData['email'], ); setState(() => isProcessing = false); if (result['success']) { setState(() => userData['name'] = controller.text); if (!mounted) return; Navigator.pop(context); } }, ), ); } // ══════════════════════════════════════════════════════════════════ // UBAH EMAIL — dengan alur OTP // ══════════════════════════════════════════════════════════════════ void changeEmail() { final emailController = TextEditingController(); showDialog( context: context, builder: (dialogContext) => _dialogShell( icon: Icons.email_outlined, iconBg: const Color(0xFFF0FDFA), iconColor: const Color(0xFF0D9488), title: 'Ubah Email', subtitle: 'Kode OTP akan dikirim ke email baru', saveLabel: 'Kirim OTP', fields: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: const Color(0xFFF0FDF4), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFF86EFAC), width: 0.5), ), child: Row( children: [ const Icon(Icons.check_circle_rounded, size: 14, color: Color(0xFF16A34A)), const SizedBox(width: 6), Text( 'Email aktif: ${userData['email'] ?? '-'}', style: const TextStyle( fontSize: 11, color: Color(0xFF166534), fontWeight: FontWeight.w500, ), ), ], ), ), const SizedBox(height: 14), _styledField( controller: emailController, label: 'Email Baru', prefixIcon: Icons.email_outlined, prefixColor: const Color(0xFF0D9488), hint: 'Masukkan email baru...', keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 6), Row( children: const [ Icon(Icons.send_rounded, size: 12, color: _textHint), SizedBox(width: 4), Text('Kode OTP 6 digit dikirim ke email baru', style: TextStyle(fontSize: 10.5, color: _textHint)), ], ), ], onSave: () async { final newEmail = emailController.text.trim(); if (newEmail.isEmpty) { _showSnack('Email tidak boleh kosong', Colors.red); return; } setState(() => isProcessing = true); final result = await AuthService.sendEmailOtp(newEmail); setState(() => isProcessing = false); if (!dialogContext.mounted) return; if (result['success'] == true) { Navigator.pop(dialogContext); _showOtpDialog(newEmail); } else { _showSnack( result['message'] ?? 'Gagal mengirim OTP, coba lagi', Colors.red, ); } }, ), ); } // ── Dialog OTP ────────────────────────────────────────────────── void _showOtpDialog(String newEmail) { final List otpControllers = List.generate(6, (_) => TextEditingController()); final List focusNodes = List.generate(6, (_) => FocusNode()); // FIX #1: deklarasikan isSending di luar StatefulBuilder // supaya nilainya tidak reset tiap rebuild dan tidak dianggap dead code bool isSending = false; showDialog( context: context, barrierDismissible: false, builder: (dialogContext) => StatefulBuilder( builder: (context, setStateDialog) { String getOtpCode() => otpControllers.map((c) => c.text).join(); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24)), backgroundColor: Colors.white, child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: const Color(0xFFF0FDFA), borderRadius: BorderRadius.circular(16), ), child: const Icon(Icons.mark_email_read_outlined, color: Color(0xFF0D9488), size: 26), ), const SizedBox(height: 14), const Text( 'Verifikasi Email', style: TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: _textPrimary), ), const SizedBox(height: 6), RichText( textAlign: TextAlign.center, text: TextSpan( style: const TextStyle( fontSize: 12.5, color: _textHint, height: 1.5), children: [ const TextSpan(text: 'Kode OTP telah dikirim ke\n'), TextSpan( text: newEmail, style: const TextStyle( color: Color(0xFF0D9488), fontWeight: FontWeight.w600), ), ], ), ), const SizedBox(height: 24), // 6 kotak OTP Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(6, (i) { return SizedBox( width: 42, height: 50, child: TextField( controller: otpControllers[i], focusNode: focusNodes[i], textAlign: TextAlign.center, keyboardType: TextInputType.number, maxLength: 1, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: _textPrimary), decoration: InputDecoration( counterText: '', filled: true, fillColor: const Color(0xFFF8FAFC), contentPadding: EdgeInsets.zero, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide( color: _border, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide( color: _border, width: 1.5), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide( color: _blue, width: 2), ), ), onChanged: (val) { if (val.isNotEmpty && i < 5) { FocusScope.of(context) .requestFocus(focusNodes[i + 1]); } else if (val.isEmpty && i > 0) { FocusScope.of(context) .requestFocus(focusNodes[i - 1]); } setStateDialog(() {}); }, ), ); }), ), const SizedBox(height: 20), // Tombol Verifikasi SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isSending ? null : () async { final otp = getOtpCode(); if (otp.length < 6) { _showSnack('Masukkan 6 digit kode OTP', Colors.orange); return; } setStateDialog(() => isSending = true); final result = await AuthService.verifyEmailOtp( newEmail, otp); // FIX #2: guard pakai dialogContext.mounted if (!dialogContext.mounted) return; setStateDialog(() => isSending = false); if (result['success'] == true) { setState( () => userData['email'] = newEmail); Navigator.pop(dialogContext); _showSnack('Email berhasil diperbarui', Colors.green); } else { _showSnack( result['message'] ?? 'Kode OTP salah atau sudah kadaluarsa', Colors.red, ); } }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0D9488), padding: const EdgeInsets.symmetric(vertical: 13), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: isSending ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white), ) : const Text( 'Verifikasi', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white), ), ), ), const SizedBox(height: 10), // Tombol Kirim Ulang + Batal Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () async { final result = await AuthService.sendEmailOtp(newEmail); if (!dialogContext.mounted) return; if (result['success'] == true) { _showSnack( 'OTP baru telah dikirim', Colors.blue); } else { _showSnack( 'Gagal mengirim ulang OTP', Colors.red); } }, child: const Text( 'Kirim Ulang OTP', style: TextStyle( fontSize: 12, color: _blue, fontWeight: FontWeight.w600), ), ), TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text( 'Batal', style: TextStyle(fontSize: 12, color: _textHint), ), ), ], ), ], ), ), ); }, ), ); } void changePassword() { final oldPass = TextEditingController(); final newPass = TextEditingController(); final confirmPass = TextEditingController(); showDialog( context: context, builder: (dialogContext) => StatefulBuilder( builder: (context, setStateDialog) => _dialogShell( icon: Icons.lock_outline_rounded, iconBg: const Color(0xFFFFF7ED), iconColor: const Color(0xFFF97316), title: 'Ubah Password', subtitle: 'Gunakan password yang kuat', saveLabel: 'Perbarui', saveBtnColor: const Color(0xFFEF4444), fields: [ _styledField( controller: oldPass, label: 'Password Lama', prefixIcon: Icons.lock_outline_rounded, prefixColor: const Color(0xFFF97316), obscure: true, showToggle: true, toggleValue: showOldPassword, onToggle: () => setStateDialog(() => showOldPassword = !showOldPassword), ), const SizedBox(height: 14), _styledField( controller: newPass, label: 'Password Baru', prefixIcon: Icons.lock_outline_rounded, prefixColor: _blue, obscure: true, showToggle: true, toggleValue: showNewPassword, onToggle: () => setStateDialog(() => showNewPassword = !showNewPassword), ), const SizedBox(height: 14), _styledField( controller: confirmPass, label: 'Konfirmasi Password', prefixIcon: Icons.check_circle_outline_rounded, prefixColor: const Color(0xFF22C55E), obscure: true, showToggle: true, toggleValue: showConfirmPassword, onToggle: () => setStateDialog( () => showConfirmPassword = !showConfirmPassword), ), ], onSave: () async { if (newPass.text != confirmPass.text) { _showSnack('Password tidak sama', Colors.red); return; } setState(() => isProcessing = true); // FIX #3: tidak simpan ke `result` karena tidak dipakai, // langsung await tanpa assignment variabel await AuthService.updatePassword(oldPass.text, newPass.text); setState(() => isProcessing = false); // FIX #3: guard pakai dialogContext.mounted if (!dialogContext.mounted) return; Navigator.pop(dialogContext); }, ), ), ); } void confirmLogout() { showDialog( context: context, builder: (_) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), backgroundColor: Colors.white, child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: const BoxDecoration( color: Color(0xFFFEE2E2), shape: BoxShape.circle), child: const Icon(Icons.logout_rounded, color: Color(0xFFEF4444), size: 26), ), const SizedBox(height: 16), const Text('Keluar Akun?', style: TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: _textPrimary)), const SizedBox(height: 6), const Text( 'Sesi kamu akan dihapus dan kamu\nharus login kembali.', textAlign: TextAlign.center, style: TextStyle( fontSize: 12.5, color: _textHint, height: 1.5), ), const SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), side: const BorderSide(color: _border, width: 1.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: const Text('Batal', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: _textSecondary)), ), ), const SizedBox(width: 10), Expanded( child: ElevatedButton( onPressed: () async { await AuthService.logout(); if (!mounted) return; Navigator.pushNamedAndRemoveUntil( context, '/login', (route) => false, ); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFEF4444), padding: const EdgeInsets.symmetric(vertical: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: const Text('Keluar', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white)), ), ), ], ), ], ), ), ), ); } }