import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:seedina/provider/rtdb_handler.dart'; import 'package:seedina/utils/style/gcolor.dart'; // class EditInfo extends StatefulWidget { const EditInfo({super.key}); @override State createState() => _EditInfoState(); } class _EditInfoState extends State { final _formKey = GlobalKey(); late TextEditingController _titleController; late TextEditingController _latinController; late TextEditingController _waktuSiramController; late TextEditingController _jedaSiramController; late TextEditingController _suhuAirMinController; late TextEditingController _suhuAirMaxController; late TextEditingController _nutrisiMinController; late TextEditingController _nutrisiMaxController; late TextEditingController _suhuLingMinController; late TextEditingController _suhuLingMaxController; late TextEditingController _humiLingMinController; late TextEditingController _humiLingMaxController; bool _isSaving = false; // bool _isLoadingForm = true; // Tidak lagi diperlukan jika Consumer menangani update String _currentPlantSelectionInProvider = ""; @override void initState() { super.initState(); _titleController = TextEditingController(); _latinController = TextEditingController(); _waktuSiramController = TextEditingController(); _jedaSiramController = TextEditingController(); _suhuAirMinController = TextEditingController(); _suhuAirMaxController = TextEditingController(); _nutrisiMinController = TextEditingController(); _nutrisiMaxController = TextEditingController(); _suhuLingMinController = TextEditingController(); _suhuLingMaxController = TextEditingController(); _humiLingMinController = TextEditingController(); _humiLingMaxController = TextEditingController(); // Ambil data awal saat initState // dan simpan tipe tanaman dari provider untuk deteksi perubahan nanti WidgetsBinding.instance.addPostFrameCallback((_) { final provider = Provider.of(context, listen: false); _currentPlantSelectionInProvider = provider.draftSelectedPlantForEditing; _loadParametersFromProviderDraft(provider.draftParametersForEditingReadonly); }); } // Tidak lagi menggunakan didChangeDependencies untuk reload form secara otomatis // karena bisa me-reset input pengguna. Kita akan mengandalkan Consumer. void _loadParametersFromProviderDraft(Map params) { if (!mounted) return; // setState(() { _isLoadingForm = true; }); // Tidak lagi diperlukan // Logika untuk memastikan title dan latin sesuai untuk Kustom saat load // Provider sekarang sudah memastikan _draftParametersForEditing['title'] dan ['latin'] // sudah benar untuk mode Kustom ("Tanaman Lain", "Parameter Kustom"). _titleController.text = params['title']?.toString() ?? ''; _latinController.text = params['latin']?.toString() ?? ''; _waktuSiramController.text = params['waktu_siram']?.toString() ?? ''; _jedaSiramController.text = params['jeda_siram']?.toString() ?? ''; _suhuAirMinController.text = params['min_suhuair']?.toString() ?? ''; _suhuAirMaxController.text = params['max_suhuair']?.toString() ?? ''; _nutrisiMinController.text = params['min_tdsair']?.toString() ?? ''; _nutrisiMaxController.text = params['max_tdsair']?.toString() ?? ''; _suhuLingMinController.text = params['min_suhuling']?.toString() ?? ''; _suhuLingMaxController.text = params['max_suhuling']?.toString() ?? ''; _humiLingMinController.text = params['min_humiling']?.toString() ?? ''; _humiLingMaxController.text = params['max_humiling']?.toString() ?? ''; // if (mounted) { // setState(() { _isLoadingForm = false; }); // } // Cukup panggil setState di build jika menggunakan Consumer untuk trigger rebuild } Future _submitCustomParameters() async { if (!_formKey.currentState!.validate()) { if(mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Harap perbaiki semua error pada form.'), backgroundColor: Colors.orangeAccent), ); } return; } if(!mounted) return; setState(() => _isSaving = true); final provider = Provider.of(context, listen: false); // Ambil default title Kustom jika input title kosong String customTitle = _titleController.text.trim(); if (customTitle.isEmpty) { customTitle = provider.parameters['Kustom']!['title'].toString(); } Map parametersToSaveFromForm = { 'title': customTitle, 'latin': _latinController.text.trim(), // Latin bisa kosong 'waktu_siram': int.tryParse(_waktuSiramController.text) ?? provider.parameters['Kustom']!['waktu_siram'], 'jeda_siram': int.tryParse(_jedaSiramController.text) ?? provider.parameters['Kustom']!['jeda_siram'], 'min_suhuair': double.tryParse(_suhuAirMinController.text) ?? provider.parameters['Kustom']!['min_suhuair'], 'max_suhuair': double.tryParse(_suhuAirMaxController.text) ?? provider.parameters['Kustom']!['max_suhuair'], 'min_tdsair': int.tryParse(_nutrisiMinController.text) ?? provider.parameters['Kustom']!['min_tdsair'], 'max_tdsair': int.tryParse(_nutrisiMaxController.text) ?? provider.parameters['Kustom']!['max_tdsair'], 'min_suhuling': double.tryParse(_suhuLingMinController.text) ?? provider.parameters['Kustom']!['min_suhuling'], 'max_suhuling': double.tryParse(_suhuLingMaxController.text) ?? provider.parameters['Kustom']!['max_suhuling'], 'min_humiling': int.tryParse(_humiLingMinController.text) ?? provider.parameters['Kustom']!['min_humiling'], 'max_humiling': int.tryParse(_humiLingMaxController.text) ?? provider.parameters['Kustom']!['max_humiling'], // Thumbnail akan diurus oleh provider.applyCustomDraftToActive }; // ignore: unused_local_variable bool success = await provider.applyCustomDraftToActive(context, parametersToSaveFromForm); // Pesan sukses/gagal sudah dihandle provider if (mounted) { setState(() => _isSaving = false); // Jika sukses, dan kita ingin form merefleksikan data yang baru disimpan (terutama jika title berubah) // Kita bisa panggil _loadParametersFromProviderDraft lagi dengan data terbaru dari provider. // Provider akan memanggil notifyListeners setelah applyCustomDraftToActive, // jadi Consumer di bawah akan otomatis memanggil _load... } } @override void dispose() { _titleController.dispose(); _latinController.dispose(); _waktuSiramController.dispose(); _jedaSiramController.dispose(); _suhuAirMinController.dispose(); _suhuAirMaxController.dispose(); _nutrisiMinController.dispose(); _nutrisiMaxController.dispose(); _suhuLingMinController.dispose(); _suhuLingMaxController.dispose(); _humiLingMinController.dispose(); _humiLingMaxController.dispose(); super.dispose(); } String? _validateRequired(String? value, String fieldName) { if (value == null || value.trim().isEmpty) { // Khusus untuk nama tanaman kustom, tidak wajib diisi (akan default ke "Tanaman Lain") // Validasi nama preset ada di TextFormField langsung. // if (fieldName.toLowerCase().contains("nama tanaman")) return null; // Untuk field lain tetap wajib: // return '$fieldName wajib diisi'; } // Untuk nama tanaman, cek apakah sama dengan nama preset (kecuali "Kustom") if (fieldName.toLowerCase().contains("nama tanaman") && value != null && value.trim().isNotEmpty) { final p = Provider.of(context, listen: false); // Cek apakah nama yang dimasukkan adalah salah satu key preset (case insensitive) // dan BUKAN merupakan nama default untuk Kustom itu sendiri ("Tanaman Lain") // atau nama latin default Kustom. String lowerValue = value.trim().toLowerCase(); bool isPresetName = p.parameters.keys.any((key) { return key != "Kustom" && key.toLowerCase() == lowerValue; }); if (isPresetName) { return "'$value' adalah nama preset, tidak bisa digunakan untuk Kustom."; } } // Untuk field selain nama tanaman, jika wajib: if (!fieldName.toLowerCase().contains("nama tanaman") && (value == null || value.trim().isEmpty)) { return '$fieldName wajib diisi'; } return null; } String? _validateInt(String? value, String fieldName, {int? minValAbs, int? maxValAbs, bool allowZero = true}) { String? requiredError = _validateRequired(value, fieldName); if (requiredError != null) return requiredError; final val = int.tryParse(value!); if (val == null) return '$fieldName harus berupa angka bulat'; if (!allowZero && val == 0) return '$fieldName tidak boleh nol'; if (val < 0 && !allowZero && (minValAbs == null || minValAbs >=0) ) return '$fieldName tidak boleh negatif'; if (minValAbs != null && val < minValAbs) return '$fieldName minimal $minValAbs'; if (maxValAbs != null && val > maxValAbs) return '$fieldName maksimal $maxValAbs'; return null; } String? _validateDouble(String? value, String fieldName, {double? minValAbs, double? maxValAbs}) { String? requiredError = _validateRequired(value, fieldName); if (requiredError != null) return requiredError; final val = double.tryParse(value!); if (val == null) return '$fieldName harus berupa angka desimal'; if (fieldName.toLowerCase().contains("kelembapan") && (val < 0.0 || val > 100.0)) return 'Kelembapan harus antara 0.0 - 100.0'; if (minValAbs != null && val < minValAbs) return '$fieldName minimal $minValAbs'; if (maxValAbs != null && val > maxValAbs) return '$fieldName maksimal $maxValAbs'; return null; } Widget _buildSingleTextFormField({ required String label, required TextEditingController controller, required String unit, bool isDecimal = false, num? absoluteMin, num? absoluteMax, bool allowZeroForInt = true, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 10.0), child: TextFormField( controller: controller, keyboardType: isDecimal ? const TextInputType.numberWithOptions(decimal: true, signed: true) : TextInputType.number, inputFormatters: isDecimal ? [FilteringTextInputFormatter.allow(RegExp(r'^-?\d*\.?\d*'))] : [FilteringTextInputFormatter.digitsOnly], decoration: InputDecoration( labelText: label, suffixText: unit, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.0)), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), isDense: true, helperText: isDecimal ? "Gunakan '.' untuk desimal" : "Angka bulat", helperStyle: TextStyle(fontSize: 11, color: Colors.grey.shade600) ), validator: (value) { return isDecimal ? _validateDouble(value, label, minValAbs: absoluteMin?.toDouble(), maxValAbs: absoluteMax?.toDouble()) : _validateInt(value, label, minValAbs: absoluteMin?.toInt(), maxValAbs: absoluteMax?.toInt(), allowZero: allowZeroForInt); }, ), ); } Widget _buildMinMaxTextFormFieldRow({ required String label, required TextEditingController minController, required TextEditingController maxController, required String unit, bool isDecimal = false, num? absoluteMin, num? absoluteMax, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), Row( children: [ Expanded( child: TextFormField( controller: minController, keyboardType: isDecimal ? const TextInputType.numberWithOptions(decimal: true, signed: true) : TextInputType.number, inputFormatters: isDecimal ? [FilteringTextInputFormatter.allow(RegExp(r'^-?\d*\.?\d*'))] : [FilteringTextInputFormatter.digitsOnly], decoration: InputDecoration( labelText: "Min", hintText: "Nilai Min", suffixText: unit, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), validator: (value) { String? commonError = isDecimal ? _validateDouble(value, "Min $label", minValAbs: absoluteMin?.toDouble(), maxValAbs: absoluteMax?.toDouble()) : _validateInt(value, "Min $label", minValAbs: absoluteMin?.toInt(), maxValAbs: absoluteMax?.toInt()); if (commonError != null) return commonError; final maxStr = maxController.text; if (maxStr.isNotEmpty && value != null && value.isNotEmpty) { final currentMin = isDecimal ? double.tryParse(value) : int.tryParse(value); final currentMax = isDecimal ? double.tryParse(maxStr) : int.tryParse(maxStr); if (currentMin != null && currentMax != null && currentMin > currentMax) { return 'Min harus <= Maks'; } } return null; }, ), ), const SizedBox(width: 16), Expanded( child: TextFormField( controller: maxController, keyboardType: isDecimal ? const TextInputType.numberWithOptions(decimal: true, signed: true) : TextInputType.number, inputFormatters: isDecimal ? [FilteringTextInputFormatter.allow(RegExp(r'^-?\d*\.?\d*'))] : [FilteringTextInputFormatter.digitsOnly], decoration: InputDecoration( labelText: "Maks", hintText: "Nilai Maks", suffixText: unit, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), validator: (value) { String? commonError = isDecimal ? _validateDouble(value, "Maks $label", minValAbs: absoluteMin?.toDouble(), maxValAbs: absoluteMax?.toDouble()) : _validateInt(value, "Maks $label", minValAbs: absoluteMin?.toInt(), maxValAbs: absoluteMax?.toInt()); if (commonError != null) return commonError; final minStr = minController.text; if (minStr.isNotEmpty && value != null && value.isNotEmpty) { final currentMin = isDecimal ? double.tryParse(minStr) : int.tryParse(minStr); final currentMax = isDecimal ? double.tryParse(value) : int.tryParse(value); if (currentMin != null && currentMax != null && currentMin > currentMax) { return 'Maks harus >= Min'; } } return null; }, ), ), ], ), if (isDecimal) Padding( padding: const EdgeInsets.only(top: 4.0), child: Text("Gunakan '.' untuk desimal", style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), ) else Padding( padding: const EdgeInsets.only(top: 4.0), child: Text("Angka bulat", style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), ), ], ), ); } Widget _buildSectionTitle(String title) { return Padding( padding: const EdgeInsets.only(top: 16.0, bottom: 4.0), child: Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: GColors.myBiru)), // ); } @override Widget build(BuildContext context) { // Gunakan Consumer untuk mendapatkan data draft terbaru dan re-load form jika perlu return Consumer( builder: (context, provider, child) { // Cek apakah tipe tanaman di provider berubah sejak widget ini dibangun // Jika berubah (misal dari preset ke Kustom atau sebaliknya karena interaksi di luar widget ini), // maka kita perlu me-reload controller dengan data draft baru. if (_currentPlantSelectionInProvider != provider.draftSelectedPlantForEditing) { _currentPlantSelectionInProvider = provider.draftSelectedPlantForEditing; // Panggil _loadParametersFromProviderDraft di post frame callback agar tidak error // saat build sedang berlangsung. WidgetsBinding.instance.addPostFrameCallback((_) { _loadParametersFromProviderDraft(provider.draftParametersForEditingReadonly); }); } final plantInfoForImage = provider.draftPlantInfoForEditingPage; return Container( width: MediaQuery.of(context).size.width, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), color: Colors.white, boxShadow: [ BoxShadow( color: GColors.shadowColor.withOpacity(0.15), // blurRadius: 6, spreadRadius: 2, offset: const Offset(0, 3)) ]), child: Padding( padding: const EdgeInsets.all(16.0), child: provider.isLoading && provider.draftParametersForEditingReadonly.isEmpty ? Center(child: CircularProgressIndicator()) : SingleChildScrollView( child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), image: DecorationImage( image: AssetImage(plantInfoForImage['thumbnail'] ?? 'assets/myicon/unknown.png'), // fit: BoxFit.cover), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( controller: _titleController, decoration: const InputDecoration( labelText: "Nama Tanaman Kustom", border: UnderlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.auto, // Biar label naik hintText: "Contoh: Cabe Rawit Saya" ), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700), validator: (value) { return _validateRequired(value, "Nama Tanaman Kustom"); } ), TextFormField( controller: _latinController, decoration: const InputDecoration( labelText: "Nama Latin/Deskripsi (Opsional)", border: UnderlineInputBorder(), floatingLabelBehavior: FloatingLabelBehavior.auto, hintText: "Contoh: Capsicum frutescens" ), style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic), ), ], ), ) ], ), const Divider(height: 32, thickness: 1), _buildSectionTitle("Pengaturan Penyiraman"), _buildSingleTextFormField( label: "Waktu Siram", controller: _waktuSiramController, unit: "Menit", isDecimal: false, absoluteMin: 1, allowZeroForInt: false, ), _buildSingleTextFormField( label: "Jeda Siram", controller: _jedaSiramController, unit: "Menit", isDecimal: false, absoluteMin: 1, allowZeroForInt: false, ), _buildSectionTitle("Parameter Air"), _buildMinMaxTextFormFieldRow( label: "Nutrisi (TDS)", minController: _nutrisiMinController, maxController: _nutrisiMaxController, unit: "ppm", isDecimal: false, absoluteMin: 0), _buildMinMaxTextFormFieldRow( label: "Suhu Air", minController: _suhuAirMinController, maxController: _suhuAirMaxController, unit: "°C", isDecimal: true), _buildSectionTitle("Parameter Lingkungan"), _buildMinMaxTextFormFieldRow( label: "Suhu Lingkungan", minController: _suhuLingMinController, maxController: _suhuLingMaxController, unit: "°C", isDecimal: true), _buildMinMaxTextFormFieldRow( label: "Kelembapan Udara", minController: _humiLingMinController, maxController: _humiLingMaxController, unit: "%", isDecimal: false, absoluteMin: 0, absoluteMax: 100 ), const SizedBox(height: 24), ElevatedButton( onPressed: _isSaving ? null : _submitCustomParameters, style: ElevatedButton.styleFrom( backgroundColor: GColors.myBiru, // foregroundColor: Colors.white, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12))), child: _isSaving ? const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5)) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.save_alt_outlined), SizedBox(width: 8), Text('Simpan Parameter Kustom', style: TextStyle(fontSize: 16)) ], ), ), const SizedBox(height: 16), ], ), ), ), ), ); } ); } }