config_page.dart¶
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class FishParameters {
final double temperature_min;
final double temperature_max;
final double ph_min;
final double ph_max;
final double nivel_luz;
final double nivel_agua;
FishParameters(this.temperature_min, this.temperature_max, this.ph_min, this.ph_max, this.nivel_luz, this.nivel_agua);
}
Map<String, FishParameters> fishParameters = {
'Pez Platy (Xiphophorus maculatus)': FishParameters(20.0, 26.0, 7.0, 8.2, 2.0, 45.0),
'Guppy Cobra': FishParameters(22.0, 28.0, 7.0, 8.5, 2.0, 40.0),
'Tetra Neón Verde (Simulans)': FishParameters(23.0, 29.0, 4.0, 6.5, 1.0, 40.0),
'Tetra Luminoso (Hemigrammus erythrozonus)': FishParameters(24.0, 28.0, 6.0, 7.5, 1.0, 45.0),
'Pez Lápiz (Nannostomus beckfordi)': FishParameters(24.0, 28.0, 5.5, 7.5, 1.0, 45.0),
'Tetra Limón': FishParameters(23.0, 28.0, 6.0, 7.5, 1.0, 50.0),
'Gourami Pigmeo (Trichopsis pumila)': FishParameters(25.0, 28.0, 6.0, 7.0, 1.0, 30.0),
'Betta Imbellis (Betta Pacífico)': FishParameters(24.0, 28.0, 6.0, 7.5, 1.5, 30.0),
'Pseudomugil Furcatus (Ojos Azules)': FishParameters(24.0, 28.0, 7.0, 8.0, 1.0, 45.0),
'Gobio Pavo Real (Tateurndina ocellicauda)': FishParameters(22.0, 26.0, 6.5, 7.5, 1.5, 40.0),
'Killi Cabo Lopez (Aphyosemion australe)': FishParameters(21.0, 24.0, 6.0, 7.0, 1.5, 40.0),
'Nube Blanca (Tanichthys albonubes)': FishParameters(15.0, 24.0, 6.0, 8.0, 1.0, 40.0),
'Medaka (Oryzias latipes)': FishParameters(16.0, 28.0, 7.0, 8.0, 1.0, 30.0),
'Multifasciatus (Neolamprologus multifasciatus)': FishParameters(24.0, 27.0, 7.5, 9.0, 2.5, 45.0),
'Rasbora Esmeralda (Danio erythromicron)': FishParameters(21.0, 25.0, 7.0, 8.0, 1.5, 30.0),
'Tetra Cobre (Hasemania nana)': FishParameters(22.0, 28.0, 6.0, 7.5, 1.0, 45.0),
'Pez Hacha Mármol (Carnegiella strigata)': FishParameters(24.0, 28.0, 5.5, 7.0, 2.0, 45.0),
'Corydora Habrosus': FishParameters(22.0, 26.0, 6.0, 7.5, 1.0, 35.0),
'Gamba Amano (Caridina multidentata)': FishParameters(18.0, 28.0, 6.5, 8.0, 1.5, 20.0),
'Gamba Crystal Red (Caridina cantonensis)': FishParameters(20.0, 24.0, 5.5, 6.8, 3.0, 20.0),
'Crayfish Enano (Cambarellus patzcuarensis)': FishParameters(15.0, 25.0, 6.5, 8.0, 2.0, 40.0),
'Personalizado': FishParameters(24.0, 26.0, 6.5, 7.5, 2.0, 50.0),
};
class ConfigPage extends StatefulWidget {
final String aquariumId;
final Function(String, FishParameters) onFishSelected;
final String currentFish;
const ConfigPage({super.key, required this.onFishSelected, required this.currentFish, required this.aquariumId});
@override
State<ConfigPage> createState() => _ConfigPageState();
}
class _ConfigPageState extends State<ConfigPage> {
String _selectedFish = 'Personalizado';
late TextEditingController _minTempCtrl;
late TextEditingController _maxTempCtrl;
late TextEditingController _minPhCtrl;
late TextEditingController _maxPhCtrl;
late TextEditingController _aguaCtrl;
double _nivelLuz = 2.0;
late DocumentReference _configRef;
@override
void initState() {
super.initState();
_configRef = FirebaseFirestore.instance
.collection('acuarios')
.doc(widget.aquariumId)
.collection('data')
.doc('config');
FishParameters params = fishParameters['Personalizado']!;
_setupControllers(params);
_cargarDatosDeNube();
}
void _setupControllers(FishParameters params) {
_minTempCtrl = TextEditingController(text: params.temperature_min.toString());
_maxTempCtrl = TextEditingController(text: params.temperature_max.toString());
_minPhCtrl = TextEditingController(text: params.ph_min.toString());
_maxPhCtrl = TextEditingController(text: params.ph_max.toString());
_aguaCtrl = TextEditingController(text: params.nivel_agua.toStringAsFixed(0));
_nivelLuz = params.nivel_luz;
}
void _cargarDatosDeNube() async {
try {
DocumentSnapshot doc = await _configRef.get();
if(doc.exists) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
setState(() {
_selectedFish = data['nombre_pez'] ?? 'Personalizado';
_minTempCtrl.text = (data['temp_min'] ?? 24.0).toString();
_maxTempCtrl.text = (data['temp_max'] ?? 28.0).toString();
_minPhCtrl.text = (data['ph_min'] ?? 6.5).toString();
_maxPhCtrl.text = (data['ph_max'] ?? 7.5).toString();
_aguaCtrl.text = (data['nivel_agua'] ?? 80).toString();
int luzPorcentaje = data['nivel_luz'] ?? 50;
if (luzPorcentaje <= 40) _nivelLuz = 1.0;
else if (luzPorcentaje <= 70) _nivelLuz = 2.0;
else _nivelLuz = 3.0;
});
}
} catch (e) { print(e); }
}
void _loadFishData(String fishName) {
if (fishParameters.containsKey(fishName)) {
FishParameters params = fishParameters[fishName]!;
setState(() {
_selectedFish = fishName;
_minTempCtrl.text = params.temperature_min.toString();
_maxTempCtrl.text = params.temperature_max.toString();
_minPhCtrl.text = params.ph_min.toString();
_maxPhCtrl.text = params.ph_max.toString();
_aguaCtrl.text = params.nivel_agua.toStringAsFixed(0);
_nivelLuz = params.nivel_luz;
});
}
}
void _saveAndExit() async {
double minT = double.tryParse(_minTempCtrl.text) ?? 24.0;
double maxT = double.tryParse(_maxTempCtrl.text) ?? 28.0;
double minPh = double.tryParse(_minPhCtrl.text) ?? 6.5;
double maxPh = double.tryParse(_maxPhCtrl.text) ?? 7.5;
double agua = double.tryParse(_aguaCtrl.text) ?? 80.0;
int luzPorcentaje = 50;
if (_nivelLuz == 1.0) luzPorcentaje = 30;
if (_nivelLuz == 2.0) luzPorcentaje = 60;
if (_nivelLuz == 3.0) luzPorcentaje = 100;
await _configRef.update({
'nombre_pez': _selectedFish,
'temp_min': minT,
'temp_max': maxT,
'ph_min': minPh,
'ph_max': maxPh,
'nivel_agua': agua,
'nivel_luz': luzPorcentaje,
'ultima_modificacion': DateTime.now().toString()
});
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Configuración enviada')));
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
List<String> dropdownItems = fishParameters.keys.toList();
bool isEditable = _selectedFish == 'Personalizado';
return Scaffold(
appBar: AppBar(
title: const Text('Configuración', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
backgroundColor: Colors.transparent,
elevation: 0,
iconTheme: const IconThemeData(color: Colors.white),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _saveAndExit,
backgroundColor: const Color(0xFFFF5400),
icon: const Icon(Icons.cloud_upload, color: Colors.white),
label: const Text("ENVIAR A PECERA", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white)),
),
body: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Perfil del Acuario:', style: TextStyle(fontSize: 16, color: Colors.white70)),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(30), border: Border.all(color: const Color(0xFFFF5400), width: 3)),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
dropdownColor: Colors.white,
value: dropdownItems.contains(_selectedFish) ? _selectedFish : 'Personalizado',
isExpanded: true,
icon: const Icon(Icons.arrow_drop_down_circle, color: Color(0xFFFF5400)),
style: const TextStyle(fontSize: 18, color: Colors.black87, fontWeight: FontWeight.bold),
items: dropdownItems.map((String item) {
return DropdownMenuItem<String>(value: item, child: Text(item));
}).toList(),
onChanged: (val) => _loadFishData(val!),
),
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.3), borderRadius: BorderRadius.circular(20), border: Border.all(color: isEditable ? const Color(0xFFFF5400) : Colors.white12)),
child: IgnorePointer(
ignoring: !isEditable,
child: Opacity(
opacity: isEditable ? 1.0 : 0.6,
child: Column(
children: [
_buildRangeInput(icon: Icons.thermostat, label: 'Temperatura (°C)', minCtrl: _minTempCtrl, maxCtrl: _maxTempCtrl, enabled: isEditable),
_buildRangeInput(icon: Icons.science, label: 'Rango de pH', minCtrl: _minPhCtrl, maxCtrl: _maxPhCtrl, enabled: isEditable),
_buildSingleInput(icon: Icons.waves, label: 'Nivel Agua %', ctrl: _aguaCtrl, enabled: isEditable),
const SizedBox(height: 15),
const Text('Nivel de Luz', style: TextStyle(color: Colors.white70)),
Slider(
value: _nivelLuz,
min: 1.0, max: 3.0, divisions: 2,
activeColor: const Color(0xFFFF5400),
inactiveColor: Colors.grey,
onChanged: isEditable ? (val) => setState(() => _nivelLuz = val) : null,
),
],
),
),
),
),
],
),
),
);
}
Widget _buildRangeInput({required IconData icon, required String label, required TextEditingController minCtrl, required TextEditingController maxCtrl, required bool enabled}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
children: [
Icon(icon, color: Colors.white54),
const SizedBox(width: 10),
Expanded(child: Text(label, style: const TextStyle(color: Colors.white70))),
SizedBox(width: 60, child: _buildTextField(minCtrl, enabled)),
const Text(' - ', style: TextStyle(color: Colors.white)),
SizedBox(width: 60, child: _buildTextField(maxCtrl, enabled)),
],
),
);
}
Widget _buildSingleInput({required IconData icon, required String label, required TextEditingController ctrl, required bool enabled}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
children: [
Icon(icon, color: Colors.white54),
const SizedBox(width: 10),
Expanded(child: Text(label, style: const TextStyle(color: Colors.white70))),
SizedBox(width: 80, child: _buildTextField(ctrl, enabled)),
],
),
);
}
Widget _buildTextField(TextEditingController controller, bool enabled) {
return TextFormField(
controller: controller,
enabled: enabled,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white10,
contentPadding: const EdgeInsets.all(5),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
);
}
}