MIF_E31222656/lib/screens/profile_screen.dart

1311 lines
40 KiB
Dart

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<ProfileScreen> {
final _formKey = GlobalKey<FormState>();
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<void> _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<void> _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<void> _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<String, dynamic> 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<void> _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<void> _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<void> _signOut() async {
try {
final authServices = GetIt.instance<AuthServices>();
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<bool> _isUserAdmin() async {
try {
if (_user == null) return false;
final authServices = GetIt.instance<AuthServices>();
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<Color>(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<bool>(
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<AuthServices>();
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<AuthServices>();
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<bool>(
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<void> _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<Color>(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,
),
),
),
),
],
),
),
),
);
}
}