import 'dart:io'; import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:image_picker/image_picker.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; import 'package:tugas_akhir_supabase/services/session_manager.dart'; import 'package:tugas_akhir_supabase/services/auth_services.dart'; import 'package:tugas_akhir_supabase/utils/session_checker_mixin.dart'; import 'package:get_it/get_it.dart'; class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key}); @override _ProfileScreenState createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { final _formKey = GlobalKey(); final _usernameController = TextEditingController(); final _emailController = TextEditingController(); final _phoneController = TextEditingController(); final _addressController = TextEditingController(); final _farmNameController = TextEditingController(); final SupabaseClient _supabase = Supabase.instance.client; final ImagePicker _picker = ImagePicker(); String? _avatarUrl; bool _isLoading = false; User? _user; // Statistics data int _totalFields = 0; int _activeSchedules = 0; int _completedHarvests = 0; double _averageYield = 0; final String _mostPlantedCrop = '-'; @override void initState() { super.initState(); debugPrint('ProfileScreen: initState called'); // Get user immediately and load profile _user = _supabase.auth.currentUser; debugPrint('ProfileScreen: User from Supabase: ${_user?.id}'); if (_user != null) { _loadProfile(); _loadStatistics(); } else { setState(() { _isLoading = false; }); } } @override void dispose() { _usernameController.dispose(); _emailController.dispose(); _phoneController.dispose(); _addressController.dispose(); _farmNameController.dispose(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); // Refresh admin status setiap kali halaman dimuat ulang // _checkAdminStatus(); // Removed as per new_code } Future _loadProfile() async { if (_user == null) return; setState(() => _isLoading = true); try { final response = await _supabase .from('profiles') .select( 'user_id, username, email, phone, address, avatar_url, farm_name', ) .eq('user_id', _user!.id) .maybeSingle(); if (response == null) { await _createProfile(); // Pastikan ini membuat semua field yang dibutuhkan final newProfile = await _supabase .from('profiles') .select( 'user_id, username, email, phone, address, avatar_url, farm_name', ) .eq('user_id', _user!.id) .single(); _updateControllers(newProfile); } else { _updateControllers(response); } } catch (e) { debugPrint('Error loading profile: $e'); _showErrorSnackbar('Gagal memuat profil: ${e.toString()}'); } finally { if (mounted) setState(() => _isLoading = false); } } Future _loadStatistics() async { if (_user == null) return; try { // Reset values first setState(() { _totalFields = 0; _activeSchedules = 0; _completedHarvests = 0; _averageYield = 0.0; }); // Fetch fields count safely final fieldsResponse = await _supabase .from('fields') .select('id') .eq('user_id', _user!.id); _totalFields = fieldsResponse.length; // Fetch active schedules safely final now = DateTime.now().toIso8601String(); final schedulesResponse = await _supabase .from('crop_schedules') .select() .eq('user_id', _user!.id) .gt('end_date', now); _activeSchedules = schedulesResponse.length; // Fetch harvest results safely final harvestResponse = await _supabase .from('harvest_results') .select('productivity') .eq('user_id', _user!.id); if (harvestResponse.isNotEmpty) { _completedHarvests = harvestResponse.length; // Calculate average yield safely double totalYield = 0; int validRecords = 0; for (final harvest in harvestResponse) { final productivity = harvest['productivity'] as num?; if (productivity != null) { totalYield += productivity.toDouble(); validRecords++; } } _averageYield = validRecords > 0 ? totalYield / validRecords : 0.0; } if (mounted) setState(() {}); } catch (e) { debugPrint('Error loading statistics: $e'); if (mounted) { setState(() { _totalFields = 0; _activeSchedules = 0; _completedHarvests = 0; _averageYield = 0.0; }); } } } Future _createProfile() async { if (_user == null) return; try { final username = _user!.email?.split('@').first ?? 'user_${DateTime.now().millisecondsSinceEpoch}'; await _supabase.from('profiles').insert({ 'user_id': _user!.id, 'username': username, 'email': _user!.email ?? '', 'created_at': DateTime.now().toUtc().toIso8601String(), 'updated_at': DateTime.now().toUtc().toIso8601String(), }); } catch (e) { debugPrint('Error creating profile: $e'); _showErrorSnackbar('Error membuat profil: ${e.toString()}'); rethrow; } } void _updateControllers(Map data) { _usernameController.text = data['username'] ?? ''; _emailController.text = data['email'] ?? ''; _phoneController.text = data['phone'] ?? ''; _addressController.text = data['address'] ?? ''; _farmNameController.text = data['farm_name'] ?? ''; setState(() { _avatarUrl = data['avatar_url'] ?? ''; }); } Future _updateProfile() async { if (!_formKey.currentState!.validate() || _user == null) return; setState(() => _isLoading = true); try { final updates = { 'username': _usernameController.text.trim(), 'phone': _phoneController.text.trim(), 'address': _addressController.text.trim(), 'farm_name': _farmNameController.text.trim(), 'updated_at': DateTime.now().toUtc().toIso8601String(), }; await _supabase .from('profiles') .update(updates) .eq('user_id', _user!.id) .select(); _showSuccessSnackbar('Profil berhasil diperbarui'); } catch (e) { debugPrint('Error updating profile: $e'); _showErrorSnackbar('Error memperbarui profil: ${e.toString()}'); } finally { if (mounted) setState(() => _isLoading = false); } } Future _uploadAvatar() async { final picked = await _picker.pickImage(source: ImageSource.gallery); if (picked == null) return; try { final file = File(picked.path); final fileExt = picked.path.split('.').last; final filePath = 'avatars/${_user!.id}/avatar.$fileExt'; await _supabase.storage .from('avatars') .upload( filePath, file, fileOptions: FileOptions( upsert: true, contentType: 'image/$fileExt', ), ); // Get the public URL instead of a signed URL final avatarUrl = _supabase.storage .from('avatars') .getPublicUrl(filePath); await _supabase .from('profiles') .update({'avatar_url': avatarUrl}) .eq('user_id', _user!.id); setState(() { _avatarUrl = avatarUrl; }); _showSuccessSnackbar('Avatar berhasil diunggah'); } catch (e) { debugPrint('Upload error: $e'); _showErrorSnackbar('Gagal mengunggah avatar'); } } void _showErrorSnackbar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.red), ); } void _showSuccessSnackbar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.green), ); } Future _signOut() async { try { final authServices = GetIt.instance(); await authServices.signOut(); if (mounted) { Navigator.of(context).pushReplacementNamed('/login'); } } catch (e) { debugPrint('Error during sign out: $e'); // Fallback to direct sign out await _supabase.auth.signOut(); if (mounted) { Navigator.of(context).pushReplacementNamed('/login'); } } } // Simple admin check without complex session management Future _isUserAdmin() async { try { if (_user == null) return false; final authServices = GetIt.instance(); return await authServices.isAdmin(); } catch (e) { debugPrint('Error checking admin status: $e'); return false; } } @override Widget build(BuildContext context) { debugPrint( 'ProfileScreen: build called, _user: ${_user != null}, _isLoading: $_isLoading', ); if (_user == null) { debugPrint('ProfileScreen: Showing no user screen'); return _buildNoUserScreen(); } if (_isLoading) { debugPrint('ProfileScreen: Showing loading screen'); return Scaffold( backgroundColor: const Color(0xFFF8F9FA), body: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(const Color(0xFF056839)), ), ), ); } debugPrint('ProfileScreen: Showing main profile screen'); return Scaffold( backgroundColor: const Color(0xFFF8F9FA), appBar: _buildAppBar(), body: SingleChildScrollView( child: Column( children: [ _buildProfileHeader(), const SizedBox(height: 20), _buildFarmStatsSummary(), const SizedBox(height: 20), _buildProfileForm(), ], ), ), ); } PreferredSizeWidget _buildAppBar() { return AppBar( elevation: 0, backgroundColor: Colors.white, foregroundColor: Colors.black87, centerTitle: false, title: Text( 'Profil Saya', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, color: Colors.black87, ), ), actions: [ // Tombol admin dengan tooltip yang sesuai FutureBuilder( future: _isUserAdmin(), builder: (context, snapshot) { final isAdmin = snapshot.data ?? false; return IconButton( icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: isAdmin ? Colors.blue[100] : Colors.grey[100], shape: BoxShape.circle, ), child: Icon( Icons.admin_panel_settings, size: 18, color: isAdmin ? Colors.blue[700] : Colors.grey[700], ), ), onPressed: () async { // Double check admin status before allowing access if (_user != null) { final authServices = GetIt.instance(); final isAdmin = await authServices.isAdmin(); if (isAdmin) { // Jika admin, buka dashboard admin if (mounted) { Navigator.of(context).pushNamed('/admin'); } } else { // Jika bukan admin, tampilkan dialog untuk mengelola role if (mounted) { _showRoleManagementDialog(); } } } else { // User not logged in, show login prompt if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Silakan login terlebih dahulu untuk mengakses fitur admin', ), backgroundColor: Colors.orange, ), ); } } }, tooltip: isAdmin ? 'Kelola Admin' : 'Akses Admin', ); }, ), IconButton( icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey[100], shape: BoxShape.circle, ), child: Icon(Icons.logout, size: 18, color: Colors.red[700]), ), onPressed: _signOut, tooltip: 'Keluar', ), const SizedBox(width: 8), ], ); } Widget _buildProfileHeader() { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), offset: const Offset(0, 2), blurRadius: 8, ), ], ), child: Column( children: [ _buildAvatarWithEditButton(), const SizedBox(height: 16), Text( _usernameController.text, style: GoogleFonts.poppins( fontSize: 24, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), Text( _emailController.text, style: GoogleFonts.poppins(fontSize: 14, color: Colors.grey[600]), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 16), _buildActionButtons(), ], ), ); } Widget _buildAvatarWithEditButton() { return Stack( children: [ Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: const Color(0xFF056839), width: 2), ), child: CircleAvatar( radius: 60, backgroundColor: Colors.grey[200], backgroundImage: _avatarUrl != null && _avatarUrl!.isNotEmpty ? NetworkImage(_avatarUrl!) : null, child: _avatarUrl == null || _avatarUrl!.isEmpty ? const Icon(Icons.person, size: 60, color: Colors.grey) : null, ), ), Positioned( bottom: 0, right: 0, child: GestureDetector( onTap: _uploadAvatar, child: Container( height: 40, width: 40, decoration: BoxDecoration( color: const Color(0xFF056839), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: const Icon( Icons.camera_alt, color: Colors.white, size: 20, ), ), ), ), ], ); } Widget _buildActionButtons() { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // Tombol Kelola Role dihapus, fungsinya dipindahkan ke tombol admin di AppBar ], ), ); } void _showRoleManagementDialog() async { // Check admin status first final isAdmin = await _isUserAdmin(); // Jika bukan admin, tampilkan pesan error if (!isAdmin) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Anda tidak memiliki akses untuk mengelola role'), backgroundColor: Colors.red, ), ); // Debug: Tampilkan user ID debugPrint('Current user ID: ${_user?.id}'); return; } final userId = _user!.id; String? currentRole; try { // Ambil role pengguna saat ini final roleResponse = await Supabase.instance.client .from('user_roles') .select('role') .eq('user_id', userId) .maybeSingle(); if (roleResponse != null) { currentRole = roleResponse['role'] as String?; } } catch (e) { debugPrint('Error fetching user role: $e'); } if (!mounted) return; // Tampilkan dialog untuk mengelola role showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Kelola Role Pengguna'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('User ID: ${userId.substring(0, 8)}...'), Text('Email: ${_emailController.text}'), const SizedBox(height: 16), const Text('Pilih Role:'), const SizedBox(height: 8), _buildRoleOption('admin', 'Admin', currentRole == 'admin'), _buildRoleOption( 'user', 'User', currentRole == 'user' || currentRole == null, ), const SizedBox(height: 16), const Text( 'Catatan: Admin tetap memiliki akses ke semua fitur admin dan user.', style: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: Colors.grey, ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Batal'), ), ElevatedButton( onPressed: () async { // Simpan perubahan role final newRole = currentRole == 'admin' ? 'user' : 'admin'; // Jika sedang mengubah dari admin ke user final isDowngradingToUser = currentRole == 'admin' && newRole == 'user'; final isCurrentUser = _user!.id == userId; if (isDowngradingToUser) { // Periksa jumlah admin yang ada final authServices = GetIt.instance(); final adminCount = await authServices.countAdmins(); debugPrint('Current admin count: $adminCount'); // Jika hanya ada 1 admin dan kita mencoba menurunkan admin terakhir if (adminCount <= 1) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Tidak dapat menurunkan admin terakhir. Harus ada minimal satu admin dalam sistem.', ), backgroundColor: Colors.red, duration: Duration(seconds: 5), ), ); Navigator.of(context).pop(); } return; } // Jika ini adalah pengguna saat ini yang menurunkan dirinya sendiri if (isCurrentUser) { // Tampilkan konfirmasi khusus final confirmDowngrade = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Peringatan'), content: const Text( 'Anda akan menurunkan hak akses Anda sendiri dari admin menjadi user. ' 'Anda tidak akan dapat mengakses fitur admin lagi kecuali ada admin lain ' 'yang mengembalikan hak akses Anda.\n\n' 'Apakah Anda yakin?', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Batal'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), child: const Text('Ya, Saya Yakin'), ), ], ), ); if (confirmDowngrade != true) { return; // Batal jika pengguna tidak mengkonfirmasi } } } await _updateUserRole(userId, newRole); if (mounted) Navigator.of(context).pop(); }, child: Text( currentRole == 'admin' ? 'Jadikan User' : 'Jadikan Admin', ), ), ], ), ); } Widget _buildRoleOption(String role, String label, bool isSelected) { return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? Colors.blue : Colors.grey.shade300, width: isSelected ? 2 : 1, ), color: isSelected ? Colors.blue.withOpacity(0.1) : Colors.transparent, ), child: ListTile( title: Text(label), leading: Icon( role == 'admin' ? Icons.admin_panel_settings : Icons.person, color: isSelected ? Colors.blue : Colors.grey, ), trailing: isSelected ? const Icon(Icons.check_circle, color: Colors.blue) : null, dense: true, ), ); } Future _updateUserRole(String userId, String newRole) async { try { // Cek apakah pengguna sudah memiliki role final existingRole = await Supabase.instance.client .from('user_roles') .select() .eq('user_id', userId) .maybeSingle(); if (existingRole != null) { // Update role jika sudah ada await Supabase.instance.client .from('user_roles') .update({'role': newRole}) .eq('user_id', userId); } else { // Tambahkan role baru jika belum ada await Supabase.instance.client.from('user_roles').insert({ 'user_id': userId, 'role': newRole, }); } // Refresh status admin // _checkAdminStatus(); // Removed as per new_code if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Berhasil mengubah role menjadi $newRole'), backgroundColor: Colors.green, ), ); } } catch (e) { debugPrint('Error updating user role: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: ${e.toString()}'), backgroundColor: Colors.red, ), ); } } } Widget _buildFarmStatsSummary() { final currency = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp '); return Container( margin: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), spreadRadius: 1, blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Statistik Pertanian', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: const Color(0xFF056839).withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Text( 'Aktif', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: const Color(0xFF056839), ), ), ), ], ), const SizedBox(height: 20), // Performance indicators _buildPerformanceIndicators(), const Divider(height: 32), // Financial summary _buildFinancialSummary(currency), ], ), ); } Widget _buildPerformanceIndicators() { // Calculate percentage for circular indicator based on average yield double yieldPercentage = 0.0; if (_averageYield > 0) { // Assuming optimal yield is 8 ton/ha, calculate percentage yieldPercentage = (_averageYield / 8.0).clamp(0.0, 1.0); } return Row( children: [ Expanded( child: CircularPercentIndicator( radius: 60.0, lineWidth: 10.0, percent: yieldPercentage, center: Column( mainAxisSize: MainAxisSize.min, children: [ Text( _averageYield.toStringAsFixed(1), style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( 'ton/ha', style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey[600], ), ), ], ), progressColor: const Color(0xFF056839), backgroundColor: const Color(0xFF056839).withOpacity(0.2), animation: true, animationDuration: 1200, ), ), const SizedBox(width: 16), Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatRow('Total Lahan', '$_totalFields Lahan'), const SizedBox(height: 8), _buildStatRow('Tanaman Aktif', '$_activeSchedules Jenis'), const SizedBox(height: 8), _buildStatRow('Total Panen', '$_completedHarvests Kali'), const SizedBox(height: 8), _buildStatRow('Tanaman Terbanyak', _mostPlantedCrop), ], ), ), ], ); } Widget _buildFinancialSummary(NumberFormat currency) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Column( children: [ IntrinsicHeight( child: Row( children: [ Expanded( child: _buildMetricCard( 'Rata-rata Panen', '${(_averageYield * 10).toStringAsFixed(1)} kilogram/ha', Icons.trending_up, const Color(0xFF056839), 'Rata-rata hasil panen per hektar', ), ), const SizedBox(width: 12), Expanded( child: _buildMetricCard( 'Total Panen', '$_completedHarvests Kali', Icons.check_circle_outline, Colors.blue.shade700, 'Jumlah panen yang telah dilakukan', ), ), ], ), ), const SizedBox(height: 12), IntrinsicHeight( child: Row( children: [ Expanded( child: _buildMetricCard( 'Musim Tanam', '$_activeSchedules Aktif', Icons.calendar_today, Colors.orange.shade700, 'Jumlah tanaman yang sedang ditanam', ), ), const SizedBox(width: 12), Expanded( child: _buildMetricCard( 'Total Lahan', '$_totalFields Lahan', Icons.eco, Colors.green.shade700, 'Jumlah lahan yang dimiliki', ), ), ], ), ), ], ), ); } Widget _buildMetricCard( String title, String value, IconData icon, Color color, String tooltip, ) { return Container( constraints: const BoxConstraints(minHeight: 100), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 18, color: color), const SizedBox(width: 8), Expanded( child: Text( title, style: GoogleFonts.poppins( fontSize: 13, color: color, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ], ), const Spacer(), Text( value, style: GoogleFonts.poppins( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black87, ), overflow: TextOverflow.ellipsis, ), ], ), ); } Widget _buildStatRow(String label, String value) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 3, child: Text( label, style: GoogleFonts.poppins(fontSize: 14, color: Colors.grey[700]), ), ), Expanded( flex: 2, child: Text( value, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, ), textAlign: TextAlign.end, overflow: TextOverflow.ellipsis, ), ), ], ); } Widget _buildProfileForm() { return Container( margin: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), spreadRadius: 1, blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Informasi Pengguna', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 20), _buildInputField( controller: _usernameController, label: 'Nama Pengguna', icon: Icons.person_outlined, validator: (value) => value == null || value.isEmpty ? 'Nama pengguna wajib diisi' : null, ), const SizedBox(height: 16), _buildInputField( controller: _emailController, label: 'Email', icon: Icons.email_outlined, readOnly: true, ), // const SizedBox(height: 16), // _buildInputField( // controller: _farmNameController, // label: 'Nama Lahan', // icon: Icons.agriculture_outlined, // validator: (value) => // value == null || value.isEmpty // ? 'Nama lahan wajib diisi' // : null, // ), const SizedBox(height: 16), _buildInputField( controller: _phoneController, label: 'No. Telepon', icon: Icons.phone_outlined, keyboardType: TextInputType.phone, ), const SizedBox(height: 16), _buildInputField( controller: _addressController, label: 'Alamat', icon: Icons.location_on_outlined, maxLines: 3, ), const SizedBox(height: 30), _buildSaveButton(), ], ), ), ); } Widget _buildSaveButton() { return SizedBox( width: double.infinity, height: 55, child: ElevatedButton( onPressed: _updateProfile, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF056839), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 2, ), child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text( 'Simpan Perubahan', style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ); } Widget _buildInputField({ required TextEditingController controller, required String label, required IconData icon, String? Function(String?)? validator, bool readOnly = false, TextInputType? keyboardType, int? maxLines, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 4, bottom: 8), child: Text( label, style: GoogleFonts.poppins( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: TextFormField( controller: controller, decoration: InputDecoration( prefixIcon: Icon(icon, color: const Color(0xFF056839), size: 22), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( vertical: 16, horizontal: 8, ), fillColor: readOnly ? Colors.grey[50] : Colors.white, filled: true, ), style: GoogleFonts.poppins(fontSize: 15), readOnly: readOnly, keyboardType: keyboardType, maxLines: maxLines ?? 1, validator: validator, ), ), ], ); } Widget _buildNoUserScreen() { return Scaffold( backgroundColor: const Color(0xFFF8F9FA), body: SafeArea( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(30), decoration: BoxDecoration( color: Colors.grey[200], shape: BoxShape.circle, ), child: Icon( Icons.person_off_outlined, size: 70, color: Colors.grey[400], ), ), const SizedBox(height: 24), Text( 'Silakan masuk untuk melihat profil', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[700], ), ), const SizedBox(height: 12), Text( 'Anda perlu masuk ke akun untuk mengakses fitur ini', style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey[600], ), textAlign: TextAlign.center, ), const SizedBox(height: 40), Container( width: 200, height: 55, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: const Color(0xFF056839).withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: () => Navigator.of(context).pushReplacementNamed('/login'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF056839), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 15, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), child: Text( 'Masuk', style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ); } }