import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tugas_akhir_supabase/core/theme/app_colors.dart'; import 'package:tugas_akhir_supabase/domain/entities/field.dart'; // Import the Field entity import 'package:tugas_akhir_supabase/screens/calendar/add_field_bottom_sheet.dart'; import 'package:tugas_akhir_supabase/screens/calendar/field_converter.dart'; import 'package:tugas_akhir_supabase/screens/calendar/fix_fields_table.dart'; import 'package:tugas_akhir_supabase/screens/calendar/location_picker_dialog.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'dart:math' as math; // final supabase = Supabase.instance.client; // Sebaiknya akses via Supabase.instance.client di dalam method class FieldManagementScreen extends StatefulWidget { const FieldManagementScreen({super.key}); @override State createState() => _FieldManagementScreenState(); } class _FieldManagementScreenState extends State { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _locationController = TextEditingController(); final _areaSizeController = TextEditingController(); final _ownerNameController = TextEditingController(); final _customRegionController = TextEditingController(); final _customSystemTypeController = TextEditingController(); final _customLandFeatureController = TextEditingController(); int _plotCount = 1; // Default plot count List _fields = []; Field? _editingField; bool _isLoadingFields = true; bool _isSaving = false; // Koordinat lokasi double? _latitude; double? _longitude; // Region selection String _selectedRegion = 'Jawa'; final List _regions = [ 'Jawa', 'Sumatera', 'Kalimantan', 'Sulawesi', 'Bali & Nusa Tenggara', 'Maluku & Papua', 'Lainnya', ]; // Ownership type selection String _selectedOwnershipType = 'Milik Sendiri'; final List _ownershipTypes = [ 'Milik Sendiri', 'Sewa', 'Bagi Hasil', 'Lainnya', ]; // Region-specific form fields final Map _regionSpecificData = {}; // Jawa specific fields String _selectedPetakSystem = 'Petak Sawah'; final List _petakSystems = [ 'Petak Sawah', 'Tegal', 'Kebun', 'Campuran', ]; String _selectedIrigationType = 'Teknis'; final List _irigationTypes = [ 'Teknis', 'Semi Teknis', 'Sederhana', 'Tadah Hujan', ]; // Sumatera specific fields String _selectedBlokSystem = 'Ladang'; final List _blokSystems = ['Ladang', 'Kebun', 'Plasma', 'Campuran']; String _selectedSoilType = 'Mineral'; final List _soilTypes = [ 'Mineral', 'Gambut', 'Pasir', 'Liat', 'Campuran', ]; // Kalimantan specific fields String _selectedLadangSystem = 'Tetap'; final List _ladangSystems = ['Tetap', 'Berpindah', 'Semi Permanen']; String _riverDistance = ''; // Sulawesi specific fields String _selectedKebunSystem = 'Permanen'; final List _kebunSystems = ['Permanen', 'Rotasi', 'Campuran']; String _selectedTerrainType = 'Datar'; final List _terrainTypes = [ 'Datar', 'Berbukit', 'Terasering', 'Lereng', ]; // Bali & Nusa Tenggara specific fields String _selectedSubakSystem = 'Subak Tradisional'; final List _subakSystems = [ 'Subak Tradisional', 'Subak Modern', 'Non-Subak', 'Campuran', ]; String _selectedWaterSourceType = 'Mata Air'; final List _waterSourceTypes = [ 'Mata Air', 'Sungai', 'Bendungan', 'Air Tanah', 'Tadah Hujan', ]; // Maluku & Papua specific fields String _selectedGardenSystem = 'Kebun Sagu'; final List _gardenSystems = [ 'Kebun Sagu', 'Perkebunan Kelapa', 'Agroforestri', 'Ladang', ]; String _selectedForestType = 'Hutan Primer'; final List _forestTypes = [ 'Hutan Primer', 'Hutan Sekunder', 'Bekas Tebangan', 'Lahan Konversi', ]; // Tambahkan MapController di _FieldManagementScreenState final MapController _detailMapController = MapController(); @override void initState() { super.initState(); _loadFields(); } @override void dispose() { // Hapus kode yang mungkin mengganggu keyboard _nameController.dispose(); _locationController.dispose(); _areaSizeController.dispose(); _ownerNameController.dispose(); _customRegionController.dispose(); _customSystemTypeController.dispose(); _customLandFeatureController.dispose(); super.dispose(); } // Update region-specific data based on selected region void _updateRegionSpecificData() { // Pastikan _regionSpecificData tidak null dengan aman switch (_selectedRegion) { case 'Jawa': _regionSpecificData.clear(); _regionSpecificData['sistem_petak'] = _selectedPetakSystem; _regionSpecificData['jenis_irigasi'] = _selectedIrigationType; break; case 'Sumatera': _regionSpecificData.clear(); _regionSpecificData['sistem_blok'] = _selectedBlokSystem; _regionSpecificData['jenis_tanah'] = _selectedSoilType; break; case 'Kalimantan': _regionSpecificData.clear(); _regionSpecificData['sistem_ladang'] = _selectedLadangSystem; _regionSpecificData['jarak_sungai'] = _riverDistance; break; case 'Sulawesi': _regionSpecificData.clear(); _regionSpecificData['sistem_kebun'] = _selectedKebunSystem; _regionSpecificData['kontur_lahan'] = _selectedTerrainType; break; case 'Bali & Nusa Tenggara': _regionSpecificData.clear(); _regionSpecificData['sistem_subak'] = _selectedSubakSystem; _regionSpecificData['sumber_air'] = _selectedWaterSourceType; break; case 'Maluku & Papua': _regionSpecificData.clear(); _regionSpecificData['sistem_kebun'] = _selectedGardenSystem; _regionSpecificData['tipe_hutan'] = _selectedForestType; break; case 'Lainnya': _regionSpecificData.clear(); _regionSpecificData['nama_wilayah'] = _customRegionController.text; _regionSpecificData['sistem_lahan'] = _customSystemTypeController.text; _regionSpecificData['fitur_lahan'] = _customLandFeatureController.text; break; default: _regionSpecificData.clear(); } // Tambahkan log untuk debugging debugPrint('Updated region specific data: $_regionSpecificData'); } Future _loadFields() async { if (!mounted) return; setState(() => _isLoadingFields = true); try { final userId = Supabase.instance.client.auth.currentUser?.id; if (userId == null) { _showError('User ID is null'); return; } // Batasi jumlah field yang diambil untuk mengurangi beban final response = await Supabase.instance.client .from('fields') .select( 'id, name, user_id, plot_count, region, location, area_size, area_unit, ownership_type, owner_name, latitude, longitude, created_at', ) .eq('user_id', userId) .order('created_at', ascending: false) .limit(20) // Batasi jumlah data .timeout(const Duration(seconds: 5)); // Batasi jumlah field yang ditampilkan final limitedFields = (response as List).take(10).toList(); if (mounted) { setState(() { _fields = List.from( limitedFields.map((field) => FieldConverter.fromJson(field)), ); _isLoadingFields = false; }); } } catch (e) { if (mounted) { setState(() => _isLoadingFields = false); String errorMessage = 'Gagal memuat data lahan'; if (e.toString().contains('timeout')) { errorMessage = 'Koneksi timeout. Coba lagi nanti.'; } else if (e.toString().contains('network')) { errorMessage = 'Masalah koneksi jaringan. Periksa koneksi internet Anda.'; } else if (e.toString().contains('permission')) { errorMessage = 'Masalah izin akses database. Coba klik tombol Perbaiki Database.'; } else { errorMessage = 'Terjadi kesalahan: ${e.toString().substring(0, math.min(100, e.toString().length))}'; } _showError(errorMessage); } } } Future _saveField() async { if (!_formKey.currentState!.validate()) return; // Dismiss keyboard immediately when saving FocusScope.of(context).unfocus(); final userId = Supabase.instance.client.auth.currentUser?.id; if (userId == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('User tidak ditemukan, silakan login ulang.'), backgroundColor: Colors.red, ), ); return; } try { // Update region-specific data _updateRegionSpecificData(); // Log data yang akan disimpan untuk debugging debugPrint('Saving field with name: ${_nameController.text}'); debugPrint('Region: $_selectedRegion'); debugPrint('Plot count: $_plotCount'); debugPrint('Region specific data: $_regionSpecificData'); debugPrint('Coordinates: $_latitude, $_longitude'); if (_editingField == null) { // Create final id = const Uuid().v4(); await Supabase.instance.client .from('fields') .insert({ 'id': id, 'user_id': userId, 'name': _nameController.text, 'plot_count': _plotCount, 'region': _selectedRegion == 'Lainnya' ? _customRegionController.text : _selectedRegion, 'location': _locationController.text, 'latitude': _latitude, 'longitude': _longitude, 'area_size': _areaSizeController.text.isNotEmpty ? double.parse(_areaSizeController.text) : null, 'area_unit': 'm²', 'ownership_type': _selectedOwnershipType, 'owner_name': _selectedOwnershipType != 'Milik Sendiri' ? _ownerNameController.text : null, 'region_specific_data': _regionSpecificData, }) .timeout(const Duration(seconds: 10)); } else { // Update - Perbaikan untuk masalah trigger // Hapus field updated_at dari data yang dikirim final updateData = { 'name': _nameController.text, 'plot_count': _plotCount, 'region': _selectedRegion == 'Lainnya' ? _customRegionController.text : _selectedRegion, 'location': _locationController.text, 'latitude': _latitude, 'longitude': _longitude, 'area_size': _areaSizeController.text.isNotEmpty ? double.parse(_areaSizeController.text) : null, 'area_unit': 'm²', 'ownership_type': _selectedOwnershipType, 'owner_name': _selectedOwnershipType != 'Milik Sendiri' ? _ownerNameController.text : null, 'region_specific_data': _regionSpecificData, }; // Log data yang akan diupdate debugPrint('Updating field with ID: ${_editingField!.id}'); debugPrint('Update data: $updateData'); try { final response = await Supabase.instance.client .from('fields') .update(updateData) .eq('id', _editingField!.id) .timeout(const Duration(seconds: 10)); debugPrint('Update response: $response'); } catch (updateError) { // Jika update gagal karena trigger, coba dengan pendekatan delete dan insert ulang if (updateError.toString().contains('updated_at')) { debugPrint( 'Update error due to updated_at field, trying delete and reinsert approach', ); // Simpan ID lama final oldId = _editingField!.id; // Hapus data lama await Supabase.instance.client .from('fields') .delete() .eq('id', oldId) .timeout(const Duration(seconds: 10)); // Insert data baru dengan ID yang sama await Supabase.instance.client .from('fields') .insert({ 'id': oldId, 'user_id': userId, 'name': _nameController.text, 'plot_count': _plotCount, 'region': _selectedRegion == 'Lainnya' ? _customRegionController.text : _selectedRegion, 'location': _locationController.text, 'latitude': _latitude, 'longitude': _longitude, 'area_size': _areaSizeController.text.isNotEmpty ? double.parse(_areaSizeController.text) : null, 'area_unit': 'm²', 'ownership_type': _selectedOwnershipType, 'owner_name': _selectedOwnershipType != 'Milik Sendiri' ? _ownerNameController.text : null, 'region_specific_data': _regionSpecificData, }) .timeout(const Duration(seconds: 10)); } else { // Jika bukan masalah updated_at, rethrow error rethrow; } } } // Reset form tanpa menggunakan setState _resetForm(); } catch (e) { debugPrint('Error saving field: $e'); rethrow; // Rethrow exception untuk ditangkap oleh caller } } Future _deleteField(Field field) async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Konfirmasi Hapus'), content: Text('Yakin ingin menghapus lahan "${field.name}"?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Batal'), ), TextButton( onPressed: () => Navigator.pop(context, true), style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('Hapus'), ), ], ), ); if (confirm != true) return; setState(() => _isSaving = true); try { await Supabase.instance.client .from('fields') .delete() .eq('id', field.id) .timeout(const Duration(seconds: 10)); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Lahan berhasil dihapus'), backgroundColor: Colors.green, ), ); } await _loadFields(); } catch (e) { if (mounted) { setState(() => _isSaving = false); _showError('Gagal menghapus lahan: ${e.toString()}'); } } } void _resetForm() { // Dismiss keyboard when resetting the form FocusScope.of(context).unfocus(); _formKey.currentState?.reset(); // Ini akan mereset state validator _nameController.clear(); _locationController.clear(); _areaSizeController.clear(); _ownerNameController.clear(); _customRegionController.clear(); _customSystemTypeController.clear(); _customLandFeatureController.clear(); _plotCount = 1; // Reset ke nilai default _editingField = null; _latitude = null; // Reset koordinat _longitude = null; // Reset koordinat if (mounted) { setState(() { _selectedRegion = 'Jawa'; _selectedOwnershipType = 'Milik Sendiri'; _regionSpecificData.clear(); }); // Update UI untuk membersihkan form dan kembali ke mode "Tambah" } } void _startEdit(Field field) { // Dismiss keyboard when starting an edit FocusScope.of(context).unfocus(); // Set field values _nameController.text = field.name; _plotCount = field.plotCount; // Set region-specific fields if (field.region != null && _regions.contains(field.region)) { _selectedRegion = field.region!; } else { _selectedRegion = 'Lainnya'; _customRegionController.text = field.region ?? ''; } _locationController.text = field.location ?? ''; _latitude = field.latitude; // Set latitude _longitude = field.longitude; // Set longitude _areaSizeController.text = field.areaSize?.toString() ?? ''; _selectedOwnershipType = field.ownershipType ?? 'Milik Sendiri'; _ownerNameController.text = field.ownerName ?? ''; // Set region-specific data _regionSpecificData.clear(); // Pastikan clear dulu if (field.regionSpecificData != null) { _regionSpecificData.addAll(field.regionSpecificData!); // Set specific region values switch (_selectedRegion) { case 'Jawa': _selectedPetakSystem = field.regionSpecificData!['sistem_petak'] ?? _petakSystems[0]; _selectedIrigationType = field.regionSpecificData!['jenis_irigasi'] ?? _irigationTypes[0]; break; case 'Sumatera': _selectedBlokSystem = field.regionSpecificData!['sistem_blok'] ?? _blokSystems[0]; _selectedSoilType = field.regionSpecificData!['jenis_tanah'] ?? _soilTypes[0]; break; case 'Kalimantan': _selectedLadangSystem = field.regionSpecificData!['sistem_ladang'] ?? _ladangSystems[0]; _riverDistance = field.regionSpecificData!['jarak_sungai'] ?? ''; break; case 'Sulawesi': _selectedKebunSystem = field.regionSpecificData!['sistem_kebun'] ?? _kebunSystems[0]; _selectedTerrainType = field.regionSpecificData!['kontur_lahan'] ?? _terrainTypes[0]; break; case 'Bali & Nusa Tenggara': _selectedSubakSystem = field.regionSpecificData!['sistem_subak'] ?? _subakSystems[0]; _selectedWaterSourceType = field.regionSpecificData!['sumber_air'] ?? _waterSourceTypes[0]; break; case 'Maluku & Papua': _selectedGardenSystem = field.regionSpecificData!['sistem_kebun'] ?? _gardenSystems[0]; _selectedForestType = field.regionSpecificData!['tipe_hutan'] ?? _forestTypes[0]; break; } } else { // Inisialisasi dengan data default sesuai region _updateRegionSpecificData(); } _editingField = field; // Reset saving state setState(() { _isSaving = false; }); // Tampilkan bottom sheet untuk edit showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (BuildContext bottomSheetContext) => StatefulBuilder( builder: (BuildContext context, StateSetter setModalState) { // Fungsi untuk menyimpan data void saveData() { // Update region-specific data _updateRegionSpecificData(); // Set loading state setModalState(() { _isSaving = true; }); // Save field _saveField() .then((_) { setModalState(() { _isSaving = false; }); Navigator.pop(bottomSheetContext); }) .catchError((error) { setModalState(() { _isSaving = false; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal menyimpan: ${error.toString()}'), backgroundColor: Colors.red, ), ); }); } return DraggableScrollableSheet( initialChildSize: 0.9, minChildSize: 0.7, maxChildSize: 0.95, builder: (_, scrollController) => Container( decoration: BoxDecoration( color: AppColors.primary, borderRadius: const BorderRadius.vertical( top: Radius.circular(20), ), ), padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: ListView( controller: scrollController, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Edit Lahan', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), IconButton( icon: const Icon( Icons.close, color: Colors.white, ), onPressed: () => Navigator.pop(bottomSheetContext), ), ], ), const SizedBox(height: 16), // Nama lahan TextFormField( controller: _nameController, decoration: InputDecoration( labelText: 'Nama Lahan', labelStyle: TextStyle( color: Colors.white.withOpacity(0.9), ), filled: true, fillColor: Colors.white.withOpacity(0.2), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), prefixIcon: Icon( Icons.eco, color: Colors.white.withOpacity(0.9), ), ), style: const TextStyle(color: Colors.white), validator: (value) { if (value == null || value.isEmpty) { return 'Nama lahan harus diisi'; } return null; }, ), const SizedBox(height: 16), // Lokasi - Diganti dengan input dan tombol pilih lokasi Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Lokasi', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 16, ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular( 12, ), ), child: TextFormField( controller: _locationController, readOnly: true, decoration: InputDecoration( hintText: 'Pilih lokasi di peta', hintStyle: TextStyle( color: Colors.white.withOpacity( 0.7, ), ), border: InputBorder.none, prefixIcon: Icon( Icons.location_on, color: Colors.white.withOpacity( 0.9, ), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), style: const TextStyle( color: Colors.white, ), ), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: () { _showLocationPicker(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12, ), ), padding: const EdgeInsets.all(12), ), child: const Icon(Icons.map), ), ], ), if (_latitude != null && _longitude != null) Padding( padding: const EdgeInsets.only(top: 8), child: Text( 'Koordinat: ${_latitude!.toStringAsFixed(6)}, ${_longitude!.toStringAsFixed(6)}', style: TextStyle( color: Colors.white.withOpacity(0.7), fontSize: 12, ), ), ), ], ), const SizedBox(height: 16), // Jumlah petak Row( children: [ Text( 'Jumlah Petak:', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 16, ), ), const SizedBox(width: 16), IconButton( icon: const Icon( Icons.remove_circle, color: Colors.white, ), onPressed: _plotCount > 1 ? () => setModalState(() => _plotCount--) : null, ), Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: Text( '$_plotCount', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16, ), ), ), IconButton( icon: const Icon( Icons.add_circle, color: Colors.white, ), onPressed: () => setModalState(() => _plotCount++), ), ], ), const SizedBox(height: 16), // Wilayah DropdownButtonFormField( value: _selectedRegion, decoration: InputDecoration( labelText: 'Wilayah', labelStyle: TextStyle( color: Colors.white.withOpacity(0.9), ), prefixIcon: Icon( Icons.map, color: Colors.white.withOpacity(0.9), ), filled: true, fillColor: Colors.white.withOpacity(0.2), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), dropdownColor: AppColors.primary, style: const TextStyle(color: Colors.white), items: _regions.map((String item) { return DropdownMenuItem( value: item, child: Text(item), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setModalState(() { _selectedRegion = newValue; }); } }, ), const SizedBox(height: 16), // Region-specific fields based on selected region Builder( builder: (context) { switch (_selectedRegion) { case 'Jawa': return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Informasi Khusus Jawa', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 8), DropdownButtonFormField( value: _selectedPetakSystem, decoration: InputDecoration( labelText: 'Sistem Petak', labelStyle: TextStyle( color: Colors.white.withOpacity( 0.9, ), ), prefixIcon: Icon( Icons.grid_on, color: Colors.white.withOpacity( 0.9, ), ), filled: true, fillColor: Colors.white.withOpacity( 0.2, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), dropdownColor: AppColors.primary, style: const TextStyle( color: Colors.white, ), items: _petakSystems.map((String item) { return DropdownMenuItem( value: item, child: Text(item), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setModalState(() { _selectedPetakSystem = newValue; }); } }, ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedIrigationType, decoration: InputDecoration( labelText: 'Jenis Irigasi', labelStyle: TextStyle( color: Colors.white.withOpacity( 0.9, ), ), prefixIcon: Icon( Icons.water, color: Colors.white.withOpacity( 0.9, ), ), filled: true, fillColor: Colors.white.withOpacity( 0.2, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), dropdownColor: AppColors.primary, style: const TextStyle( color: Colors.white, ), items: _irigationTypes.map(( String item, ) { return DropdownMenuItem( value: item, child: Text(item), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setModalState(() { _selectedIrigationType = newValue; }); } }, ), ], ); // Tambahkan case untuk wilayah lain jika diperlukan default: return Container(); } }, ), const SizedBox(height: 16), // Luas lahan TextFormField( controller: _areaSizeController, decoration: InputDecoration( labelText: 'Luas Lahan (m²)', labelStyle: TextStyle( color: Colors.white.withOpacity(0.9), ), filled: true, fillColor: Colors.white.withOpacity(0.2), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), prefixIcon: Icon( Icons.straighten, color: Colors.white.withOpacity(0.9), ), ), style: const TextStyle(color: Colors.white), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return 'Masukkan angka yang valid'; } } return null; }, ), const SizedBox(height: 16), // Jenis kepemilikan DropdownButtonFormField( value: _selectedOwnershipType, decoration: InputDecoration( labelText: 'Jenis Kepemilikan', labelStyle: TextStyle( color: Colors.white.withOpacity(0.9), ), prefixIcon: Icon( Icons.person, color: Colors.white.withOpacity(0.9), ), filled: true, fillColor: Colors.white.withOpacity(0.2), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), dropdownColor: AppColors.primary, style: const TextStyle(color: Colors.white), items: _ownershipTypes.map((String item) { return DropdownMenuItem( value: item, child: Text(item), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setModalState(() { _selectedOwnershipType = newValue; }); } }, ), const SizedBox(height: 16), // Nama pemilik (jika bukan milik sendiri) if (_selectedOwnershipType != 'Milik Sendiri') TextFormField( controller: _ownerNameController, decoration: InputDecoration( labelText: 'Nama Pemilik', labelStyle: TextStyle( color: Colors.white.withOpacity(0.9), ), filled: true, fillColor: Colors.white.withOpacity(0.2), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), prefixIcon: Icon( Icons.person_outline, color: Colors.white.withOpacity(0.9), ), ), style: const TextStyle(color: Colors.white), ), const SizedBox(height: 32), // Tombol aksi Row( children: [ Expanded( child: ElevatedButton( onPressed: () => Navigator.pop(bottomSheetContext), style: ElevatedButton.styleFrom( backgroundColor: Colors.white.withOpacity( 0.2, ), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: const Text('Batal'), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton( onPressed: _isSaving ? null : () { if (_formKey.currentState! .validate()) { // Update region-specific data _updateRegionSpecificData(); // Log data yang akan disimpan debugPrint( 'Saving data for field: ${_nameController.text}', ); debugPrint( 'Region specific data: $_regionSpecificData', ); // Set loading state setModalState(() { _isSaving = true; }); // Save field _saveField() .then((_) { // Reset loading state setModalState(() { _isSaving = false; }); // Tutup form Navigator.pop( bottomSheetContext, ); // Reload data setelah form ditutup setState(() { _isLoadingFields = true; }); _loadFields().then((_) { if (mounted) { setState(() { _isLoadingFields = false; }); } }); // Tampilkan pesan sukses ScaffoldMessenger.of( context, ).showSnackBar( const SnackBar( content: Text( 'Lahan berhasil diperbarui', ), backgroundColor: Colors.green, ), ); }) .catchError((error) { debugPrint( 'Error saving field: $error', ); setModalState(() { _isSaving = false; }); ScaffoldMessenger.of( context, ).showSnackBar( SnackBar( content: Text( 'Gagal menyimpan: ${error.toString()}', ), backgroundColor: Colors.red, ), ); }); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: AppColors.primary, padding: const EdgeInsets.symmetric( vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: _isSaving ? SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, color: AppColors.primary, ), ) : const Text('Simpan'), ), ), ], ), ], ), ), ), ); }, ), ); } @override Widget build(BuildContext context) { final isEditing = _editingField != null; return Scaffold( resizeToAvoidBottomInset: true, appBar: AppBar( title: Text( 'Manajemen Lahan', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 20, letterSpacing: 0.5, ), ), backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 0, centerTitle: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)), ), bottom: PreferredSize( preferredSize: Size.fromHeight(6), child: _buildAppHeader(), ), actions: [ IconButton( icon: const Icon(Icons.refresh), tooltip: 'Refresh Data', onPressed: () { _loadFields(); }, ), ], ), body: _isLoadingFields ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: AppColors.primary, strokeWidth: 3, ), const SizedBox(height: 16), Text( 'Memuat data lahan...', style: TextStyle(color: Colors.grey[700], fontSize: 16), ), ], ), ) : _buildOptimizedContent(), floatingActionButton: FloatingActionButton.extended( backgroundColor: AppColors.primary, foregroundColor: Colors.white, onPressed: () { showAddFieldBottomSheet( context: context, onFieldAdded: () { _loadFields(); }, ); }, icon: const Icon(Icons.add), label: const Text('Add'), elevation: 4, ), ); } Widget _buildOptimizedContent() { if (_fields.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 120, height: 120, decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.landscape_outlined, size: 60, color: AppColors.primary.withOpacity(0.7), ), ), const SizedBox(height: 24), Text( 'Belum ada lahan tersimpan', style: TextStyle( color: Colors.grey[800], fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Text( 'Tambahkan lahan baru untuk mulai mengelola tanaman Anda dengan lebih efektif.', style: TextStyle(color: Colors.grey[600], fontSize: 16), textAlign: TextAlign.center, ), ), const SizedBox(height: 32), ElevatedButton.icon( onPressed: () { showAddFieldBottomSheet( context: context, onFieldAdded: () { _loadFields(); }, ); }, icon: const Icon(Icons.add), label: const Text('Tambah Lahan Baru'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ], ), ); } return _buildFieldsGrid(); } Widget _buildFieldsGrid() { return ListView.builder( padding: const EdgeInsets.all(16), itemCount: _fields.length, itemBuilder: (context, index) { final field = _fields[index]; return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Material( borderRadius: BorderRadius.circular(16), elevation: 0, color: Colors.white, clipBehavior: Clip.antiAlias, child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () => _showFieldDetails(field), child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.white, _getRegionColor(field.region).withOpacity(0.1), ], ), borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header dengan gradient sesuai region Container( height: 8, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.primary, _getRegionColor(field.region), ], begin: Alignment.centerLeft, end: Alignment.centerRight, ), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), ), Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Avatar lahan dengan efek glassmorphism Container( width: 60, height: 60, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.primary, _getRegionColor(field.region), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: _getRegionColor( field.region, ).withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Center( child: Icon( _getFieldIcon(field.region), color: Colors.white, size: 32, ), ), ), const SizedBox(width: 16), // Informasi lahan Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( field.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textSecondary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), if (field.location != null && field.location!.isNotEmpty) Row( children: [ Icon( Icons.location_on, size: 14, color: _getRegionColor( field.region, ).withOpacity(0.7), ), const SizedBox(width: 4), Expanded( child: Text( field.location!, style: TextStyle( color: Colors.grey[600], fontSize: 14, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), // Badges untuk informasi tambahan Wrap( spacing: 8, runSpacing: 8, children: [ _buildInfoBadge( Icons.grid_4x4, '${field.plotCount} petak', _getRegionColor( field.region, ).withOpacity(0.15), _getRegionColor(field.region), ), _buildInfoBadge( Icons.map, field.region ?? 'Wilayah tidak diketahui', _getRegionColor( field.region, ).withOpacity(0.15), _getRegionColor(field.region), ), ], ), ], ), ), ], ), ), // Footer dengan tombol aksi Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.grey[50]!, _getRegionColor(field.region).withOpacity(0.07), ], ), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(16), bottomRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( onPressed: () => _startEdit(field), icon: Icon( Icons.edit, size: 18, color: _getRegionColor(field.region), ), label: Text( 'Edit', style: TextStyle( color: _getRegionColor(field.region), fontWeight: FontWeight.w500, ), ), ), TextButton.icon( onPressed: () => _deleteField(field), icon: const Icon( Icons.delete, size: 18, color: AppColors.error, ), label: const Text( 'Hapus', style: TextStyle( color: AppColors.error, fontWeight: FontWeight.w500, ), ), ), const SizedBox(width: 8), ], ), ), ], ), ), ), ), ); }, ); } Widget _buildInfoBadge( IconData icon, String text, Color bgColor, Color iconColor, ) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(12), border: Border.all(color: iconColor.withOpacity(0.2), width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 12, color: iconColor), const SizedBox(width: 6), Text( text, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: iconColor, ), ), ], ), ); } IconData _getFieldIcon(String? region) { if (region == null) return Icons.landscape; switch (region) { case 'Jawa': return Icons.grass; case 'Sumatera': return Icons.terrain; case 'Kalimantan': return Icons.forest; case 'Sulawesi': return Icons.agriculture; case 'Bali & Nusa Tenggara': return Icons.water; case 'Maluku & Papua': return Icons.park; default: return Icons.landscape; } } Color _getRegionColor(String? region) { if (region == null) return Colors.grey; switch (region) { case 'Jawa': return Color(0xFF2E7D32); // Dark Green case 'Sumatera': return Color(0xFF1565C0); // Deep Blue case 'Kalimantan': return Color(0xFFEF6C00); // Deep Orange case 'Sulawesi': return Color(0xFF6A1B9A); // Deep Purple case 'Bali & Nusa Tenggara': return Color(0xFFC62828); // Deep Red case 'Maluku & Papua': return Color(0xFF00695C); // Deep Teal default: return Colors.grey[700]!; } } // Header gradasi yang lebih kontras untuk banner aplikasi Widget _buildAppHeader() { return Container( height: 6, width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Color(0xFF1B5E20), // Dark Green Color(0xFF1565C0), // Blue Color(0xFFEF6C00), // Orange Color(0xFF6A1B9A), // Purple Color(0xFFC62828), // Red Color(0xFF00695C), // Teal ], stops: [0.0, 0.2, 0.4, 0.6, 0.8, 1.0], ), ), ); } void _showFieldDetails(Field field) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => DraggableScrollableSheet( initialChildSize: 0.7, minChildSize: 0.5, maxChildSize: 0.95, builder: (_, scrollController) => Container( decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical( top: Radius.circular(20), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.white, _getRegionColor(field.region).withOpacity(0.07), ], ), ), child: Column( children: [ // Header dengan gradient region Container( height: 8, width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.primary, _getRegionColor(field.region), ], begin: Alignment.centerLeft, end: Alignment.centerRight, ), borderRadius: const BorderRadius.vertical( top: Radius.circular(20), ), ), ), // Content Expanded( child: ListView( controller: scrollController, padding: const EdgeInsets.all(20), children: [ // Header with field name and close button Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( field.name, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: _getRegionColor(field.region), ), overflow: TextOverflow.ellipsis, ), ), IconButton( icon: Icon( Icons.close, color: _getRegionColor( field.region, ).withOpacity(0.7), ), onPressed: () => Navigator.pop(context), ), ], ), // Hero section with location and visual Container( margin: const EdgeInsets.only( top: 16, bottom: 24, ), child: Stack( children: [ // Background card with gradient Container( height: 120, width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.primary, _getRegionColor(field.region), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: _getRegionColor( field.region, ).withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4), ), ], ), // Tambahkan pattern overlay untuk efek visual child: CustomPaint( painter: PatternPainter( color: Colors.white.withOpacity(0.1), ), ), ), // Content overlay Positioned.fill( child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // Icon Container( width: 64, height: 64, decoration: BoxDecoration( color: Colors.white.withOpacity( 0.2, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity( 0.3, ), width: 1, ), ), child: Center( child: Icon( _getFieldIcon(field.region), color: Colors.white, size: 36, ), ), ), const SizedBox(width: 16), // Info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ if (field.location != null && field.location!.isNotEmpty) Row( children: [ const Icon( Icons.location_on, size: 16, color: Colors.white, ), const SizedBox(width: 4), Expanded( child: Text( field.location!, style: const TextStyle( color: Colors .white, fontSize: 16, fontWeight: FontWeight .w500, ), maxLines: 1, overflow: TextOverflow .ellipsis, ), ), ], ), const SizedBox(height: 8), Row( children: [ _buildDetailBadge( Icons.grid_4x4, '${field.plotCount} petak', ), const SizedBox(width: 8), _buildDetailBadge( Icons.map, field.region ?? 'Wilayah tidak diketahui', ), ], ), ], ), ), ], ), ), ), ], ), ), // Field information _buildInfoSection('Informasi Umum', [ _buildInfoItem( Icons.straighten, 'Luas Lahan', field.areaSize != null ? '${field.areaSize} ${field.areaUnit}' : 'Tidak diketahui', field.region, ), _buildInfoItem( Icons.person, 'Kepemilikan', field.ownershipType ?? 'Milik Sendiri', field.region, ), if (field.ownerName != null && field.ownerName!.isNotEmpty) _buildInfoItem( Icons.person_outline, 'Nama Pemilik', field.ownerName!, field.region, ), ]), // Tampilkan peta jika ada koordinat if (field.latitude != null && field.longitude != null) ...[ const SizedBox(height: 24), _buildInfoSection('Lokasi Lahan', [ Container( height: 200, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.grey[300]!, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 2), ), ], ), clipBehavior: Clip.antiAlias, child: Stack( children: [ FlutterMap( mapController: _detailMapController, options: MapOptions( center: LatLng( field.latitude!, field.longitude!, ), zoom: 18.0, interactiveFlags: InteractiveFlag .all, // Aktifkan semua gesture ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.tanismart.tugas_akhir_supabase', ), MarkerLayer( markers: [ Marker( width: 40.0, height: 40.0, point: LatLng( field.latitude!, field.longitude!, ), child: Icon( Icons.location_on, color: Colors.red, size: 40, ), ), ], ), ], ), FutureBuilder( future: _getMapillaryThumbnail( field.latitude!, field.longitude!, ), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Positioned( bottom: 16, left: 16, child: Container( width: 120, height: 70, color: Colors.grey[300], child: const Center( child: CircularProgressIndicator( strokeWidth: 2, ), ), ), ); } if (snapshot.hasData && snapshot.data != null) { final thumbUrl = snapshot.data!; return Positioned( bottom: 16, left: 16, child: GestureDetector( onTap: () async { final url = await _getMapillaryViewerUrl( field.latitude!, field.longitude!, ); if (await canLaunch(url)) { await launch(url); } }, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( thumbUrl, width: 120, height: 70, fit: BoxFit.cover, errorBuilder: ( context, error, stack, ) => Container( color: Colors.grey[300], width: 120, height: 70, child: Icon( Icons .image_not_supported, ), ), ), ), ), ); } return SizedBox.shrink(); }, ), ], ), ), if (field.location != null && field.location!.isNotEmpty) _buildInfoItem( Icons.location_on, 'Alamat', field.location!, field.region, ), _buildInfoItem( Icons.gps_fixed, 'Koordinat', '${field.latitude!.toStringAsFixed(6)}, ${field.longitude!.toStringAsFixed(6)}', field.region, ), ]), ], // Region specific data if (field.regionSpecificData != null) ...[ const SizedBox(height: 16), _buildRegionSpecificSection(field), ], // Action buttons const SizedBox(height: 32), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.pop(context); _startEdit(field); }, icon: const Icon(Icons.edit), label: const Text('Edit Lahan'), style: ElevatedButton.styleFrom( backgroundColor: _getRegionColor( field.region, ), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.pop(context); _deleteField(field); }, icon: const Icon(Icons.delete), label: const Text('Hapus'), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: AppColors.error, padding: const EdgeInsets.symmetric( vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide( color: AppColors.error, ), ), elevation: 0, ), ), ), ], ), ], ), ), ], ), ), ), ); } Widget _buildDetailBadge(IconData icon, String text) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white.withOpacity(0.3), width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: Colors.white), const SizedBox(width: 6), Text( text, style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ); } Widget _buildInfoSection(String title, List items) { return Container( margin: const EdgeInsets.only(bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( margin: const EdgeInsets.only(bottom: 16), child: Row( children: [ Container( width: 4, height: 20, decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), Text( title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ), ), ...items, ], ), ); } Widget _buildInfoItem( IconData icon, String label, String value, [ String? region, ]) { final Color iconColor = region != null ? _getRegionColor(region) : AppColors.primary; return Container( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: iconColor.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, size: 22, color: iconColor), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 14, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey[800], ), ), ], ), ), ], ), ); } Widget _buildRegionSpecificSection(Field field) { final data = field.regionSpecificData!; final List items = []; // Common data if (data['topografi'] != null) { items.add( _buildInfoItem( Icons.terrain, 'Topografi', data['topografi'], field.region, ), ); } if (data['kemiringan_lahan'] != null) { items.add( _buildInfoItem( Icons.line_axis, 'Kemiringan Lahan', data['kemiringan_lahan'], field.region, ), ); } if (data['jenis_tanah'] != null) { items.add( _buildInfoItem( Icons.layers, 'Jenis Tanah', data['jenis_tanah'], field.region, ), ); } if (data['sumber_air'] != null) { items.add( _buildInfoItem( Icons.water, 'Sumber Air', data['sumber_air'], field.region, ), ); } if (data['jenis_irigasi'] != null) { items.add( _buildInfoItem( Icons.water_drop, 'Jenis Irigasi', data['jenis_irigasi'], field.region, ), ); } if (data['tanaman_sebelumnya'] != null && data['tanaman_sebelumnya'].isNotEmpty) { items.add( _buildInfoItem( Icons.history, 'Tanaman Sebelumnya', data['tanaman_sebelumnya'], field.region, ), ); } // Region specific data switch (field.region) { case 'Jawa': if (data['sistem_petak'] != null) { items.add( _buildInfoItem( Icons.grid_on, 'Sistem Petak', data['sistem_petak'], field.region, ), ); } break; case 'Sumatera': if (data['sistem_blok'] != null) { items.add( _buildInfoItem( Icons.dashboard, 'Sistem Blok', data['sistem_blok'], field.region, ), ); } break; case 'Kalimantan': if (data['sistem_ladang'] != null) { items.add( _buildInfoItem( Icons.agriculture, 'Sistem Ladang', data['sistem_ladang'], field.region, ), ); } if (data['jarak_sungai'] != null && data['jarak_sungai'].isNotEmpty) { items.add( _buildInfoItem( Icons.waves, 'Jarak dari Sungai', '${data['jarak_sungai']} meter', field.region, ), ); } break; case 'Sulawesi': if (data['sistem_kebun'] != null) { items.add( _buildInfoItem( Icons.eco, 'Sistem Kebun', data['sistem_kebun'], field.region, ), ); } if (data['kontur_lahan'] != null) { items.add( _buildInfoItem( Icons.terrain, 'Kontur Lahan', data['kontur_lahan'], field.region, ), ); } break; case 'Bali & Nusa Tenggara': if (data['sistem_subak'] != null) { items.add( _buildInfoItem( Icons.water_damage, 'Sistem Subak', data['sistem_subak'], field.region, ), ); } break; case 'Maluku & Papua': if (data['sistem_kebun'] != null) { items.add( _buildInfoItem( Icons.forest, 'Sistem Kebun', data['sistem_kebun'], field.region, ), ); } if (data['tipe_hutan'] != null) { items.add( _buildInfoItem( Icons.park, 'Tipe Hutan', data['tipe_hutan'], field.region, ), ); } break; case 'Lainnya': if (data['nama_wilayah'] != null) { items.add( _buildInfoItem( Icons.location_city, 'Nama Wilayah', data['nama_wilayah'], field.region, ), ); } if (data['sistem_lahan'] != null) { items.add( _buildInfoItem( Icons.agriculture, 'Sistem Lahan', data['sistem_lahan'], field.region, ), ); } if (data['fitur_lahan'] != null) { items.add( _buildInfoItem( Icons.landscape, 'Fitur Lahan', data['fitur_lahan'], field.region, ), ); } break; } return _buildInfoSection('Karakteristik Lahan', items); } String _getRegionSpecificInfo(Field field) { if (field.regionSpecificData == null) return ''; final data = field.regionSpecificData!; final List info = []; // Tambahkan informasi umum if (data['jenis_tanah'] != null) { info.add('Tanah: ${data['jenis_tanah']}'); } if (data['sumber_air'] != null) { info.add('Air: ${data['sumber_air']}'); } // Tambahkan informasi spesifik wilayah switch (field.region) { case 'Jawa': if (data['sistem_petak'] != null) { info.add('Sistem: ${data['sistem_petak']}'); } break; case 'Sumatera': if (data['sistem_blok'] != null) { info.add('Sistem: ${data['sistem_blok']}'); } break; case 'Kalimantan': if (data['sistem_ladang'] != null) { info.add('Sistem: ${data['sistem_ladang']}'); } break; case 'Sulawesi': if (data['sistem_kebun'] != null) { info.add('Sistem: ${data['sistem_kebun']}'); } break; case 'Bali & Nusa Tenggara': if (data['sistem_subak'] != null) { info.add('Sistem: ${data['sistem_subak']}'); } break; case 'Maluku & Papua': if (data['sistem_kebun'] != null) { info.add('Sistem: ${data['sistem_kebun']}'); } break; } return info.join(' • '); } void _showAddFieldBottomSheet() async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) => AddFieldBottomSheet(onFieldAdded: _loadFields), ); if (result == true) { _loadFields(); } } void _showError(String message) { // Dismiss any existing snackbars ScaffoldMessenger.of(context).hideCurrentSnackBar(); // Show error in snackbar ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: Colors.red, duration: const Duration(seconds: 5), action: SnackBarAction( label: 'Refresh', textColor: Colors.white, onPressed: () { _loadFields(); }, ), ), ); } // Tambahkan metode untuk memilih lokasi Future _showLocationPicker() async { final result = await showDialog( context: context, builder: (context) => LocationPickerDialog( initialAddress: _locationController.text, initialLatitude: _latitude, initialLongitude: _longitude, ), ); if (result != null) { setState(() { _locationController.text = result.address; _latitude = result.latitude; _longitude = result.longitude; }); } } // Fungsi untuk membuka URL Future _launchUrl(String url) async { final Uri uri = Uri.parse(url); if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) { throw Exception('Tidak dapat membuka URL: $url'); } } Future _getMapillaryThumbnail(double lat, double lng) async { const accessToken = 'MLY|24193737726928907|ec8688dd90eee0a7bd6231d281010d5c'; final url = 'https://graph.mapillary.com/images?access_token=$accessToken&fields=id,thumb_256_url&closeto=$lng,$lat&limit=1'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['data'] != null && data['data'].isNotEmpty) { return data['data'][0]['thumb_256_url'] as String?; } } return null; } Future _getMapillaryViewerUrl(double lat, double lng) async { // Cari id foto terdekat const accessToken = 'MLY|24193737726928907|ec8688dd90eee0a7bd6231d281010d5c'; final url = 'https://graph.mapillary.com/images?access_token=$accessToken&fields=id&closeto=$lng,$lat&limit=1'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['data'] != null && data['data'].isNotEmpty) { final id = data['data'][0]['id']; return 'https://www.mapillary.com/app/?pKey=$id&focus=photo'; } } // Fallback: buka mapillary di koordinat return 'https://www.mapillary.com/app/?lat=$lat&lng=$lng&z=17.5'; } } // Kelas untuk membuat pattern visual pada background hero card class PatternPainter extends CustomPainter { final Color color; PatternPainter({required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..strokeWidth = 1.0 ..style = PaintingStyle.stroke; final spacing = 15.0; // Gambar pola garis diagonal for (double i = 0; i < size.width + size.height; i += spacing) { canvas.drawLine( Offset(i < size.width ? i : 0, i < size.width ? 0 : i - size.width), Offset( i < size.height ? 0 : i - size.height, i < size.height ? i : size.height, ), paint, ); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }