Home pagedart » History » Version 6
cristobal hernandez, 12/18/2025 01:39 AM
| 1 | 1 | cristobal hernandez | --- |
|---|---|---|---|
| 2 | 1 | cristobal hernandez | |
| 3 | 4 | cristobal hernandez | | [[Interfaz|⌂]] | [[history_page.dart]] | [[feeding_page.dart]] | [[connection_page.dart]] | [[config_page.dart]] | |
| 4 | 1 | cristobal hernandez | |
| 5 | 1 | cristobal hernandez | --- |
| 6 | 5 | cristobal hernandez | |
| 7 | 5 | cristobal hernandez | h1. home_page.dart |
| 8 | 5 | cristobal hernandez | |
| 9 | 5 | cristobal hernandez | <pre><code class="dart"> |
| 10 | 5 | cristobal hernandez | |
| 11 | 6 | cristobal hernandez | import 'package:flutter/material.dart'; |
| 12 | 6 | cristobal hernandez | import 'package:cloud_firestore/cloud_firestore.dart'; |
| 13 | 6 | cristobal hernandez | import 'config_page.dart'; |
| 14 | 6 | cristobal hernandez | import 'feeding_page.dart'; |
| 15 | 6 | cristobal hernandez | import 'history_page.dart'; |
| 16 | 5 | cristobal hernandez | |
| 17 | 6 | cristobal hernandez | class SaveNemoHomePage extends StatefulWidget { |
| 18 | 6 | cristobal hernandez | final String aquariumId; |
| 19 | 6 | cristobal hernandez | |
| 20 | 6 | cristobal hernandez | const SaveNemoHomePage({super.key, required this.aquariumId}); |
| 21 | 6 | cristobal hernandez | |
| 22 | 6 | cristobal hernandez | @override |
| 23 | 6 | cristobal hernandez | State<SaveNemoHomePage> createState() => _SaveNemoHomePageState(); |
| 24 | 6 | cristobal hernandez | } |
| 25 | 6 | cristobal hernandez | |
| 26 | 6 | cristobal hernandez | class _SaveNemoHomePageState extends State<SaveNemoHomePage> { |
| 27 | 6 | cristobal hernandez | late Stream<DocumentSnapshot> _sensorStream; |
| 28 | 6 | cristobal hernandez | String _selectedFish = 'Cargando...'; |
| 29 | 6 | cristobal hernandez | |
| 30 | 6 | cristobal hernandez | @override |
| 31 | 6 | cristobal hernandez | void initState() { |
| 32 | 6 | cristobal hernandez | super.initState(); |
| 33 | 6 | cristobal hernandez | _sensorStream = FirebaseFirestore.instance |
| 34 | 6 | cristobal hernandez | .collection('acuarios') |
| 35 | 6 | cristobal hernandez | .doc(widget.aquariumId) |
| 36 | 6 | cristobal hernandez | .collection('data') |
| 37 | 6 | cristobal hernandez | .doc('estado') |
| 38 | 6 | cristobal hernandez | .snapshots(); |
| 39 | 6 | cristobal hernandez | } |
| 40 | 6 | cristobal hernandez | |
| 41 | 6 | cristobal hernandez | void _onTapNavigation(int index) { |
| 42 | 6 | cristobal hernandez | if (index == 0) { |
| 43 | 6 | cristobal hernandez | // Alimentación |
| 44 | 6 | cristobal hernandez | Navigator.push( |
| 45 | 6 | cristobal hernandez | context, |
| 46 | 6 | cristobal hernandez | MaterialPageRoute(builder: (context) => FeedingPage(aquariumId: widget.aquariumId)) |
| 47 | 6 | cristobal hernandez | ); |
| 48 | 6 | cristobal hernandez | } else if (index == 1) { |
| 49 | 6 | cristobal hernandez | // Alertas (Historial) |
| 50 | 6 | cristobal hernandez | Navigator.push( |
| 51 | 6 | cristobal hernandez | context, |
| 52 | 6 | cristobal hernandez | MaterialPageRoute( |
| 53 | 6 | cristobal hernandez | builder: (context) => HistoryPage(aquariumId: widget.aquariumId), |
| 54 | 6 | cristobal hernandez | ), |
| 55 | 6 | cristobal hernandez | ); |
| 56 | 6 | cristobal hernandez | } else if (index == 2) { |
| 57 | 6 | cristobal hernandez | // Configuración |
| 58 | 6 | cristobal hernandez | Navigator.push( |
| 59 | 6 | cristobal hernandez | context, |
| 60 | 6 | cristobal hernandez | MaterialPageRoute( |
| 61 | 6 | cristobal hernandez | builder: (context) => ConfigPage( |
| 62 | 6 | cristobal hernandez | aquariumId: widget.aquariumId, |
| 63 | 6 | cristobal hernandez | onFishSelected: (name, params) {}, |
| 64 | 6 | cristobal hernandez | currentFish: _selectedFish, |
| 65 | 6 | cristobal hernandez | ), |
| 66 | 6 | cristobal hernandez | ), |
| 67 | 6 | cristobal hernandez | ); |
| 68 | 6 | cristobal hernandez | } |
| 69 | 6 | cristobal hernandez | } |
| 70 | 6 | cristobal hernandez | |
| 71 | 6 | cristobal hernandez | @override |
| 72 | 6 | cristobal hernandez | Widget build(BuildContext context) { |
| 73 | 6 | cristobal hernandez | return Scaffold( |
| 74 | 6 | cristobal hernandez | appBar: AppBar( |
| 75 | 6 | cristobal hernandez | title: Column( |
| 76 | 6 | cristobal hernandez | children: [ |
| 77 | 6 | cristobal hernandez | const Text('SaveNemo', |
| 78 | 6 | cristobal hernandez | style: TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.2, color: Colors.white)), |
| 79 | 6 | cristobal hernandez | Text("ID: ${widget.aquariumId}", |
| 80 | 6 | cristobal hernandez | style: const TextStyle(fontSize: 12, color: Colors.white54, letterSpacing: 1.0)), |
| 81 | 6 | cristobal hernandez | ], |
| 82 | 6 | cristobal hernandez | ), |
| 83 | 6 | cristobal hernandez | centerTitle: true, |
| 84 | 6 | cristobal hernandez | backgroundColor: Colors.transparent, |
| 85 | 6 | cristobal hernandez | elevation: 0, |
| 86 | 6 | cristobal hernandez | iconTheme: const IconThemeData(color: Colors.white), |
| 87 | 6 | cristobal hernandez | ), |
| 88 | 6 | cristobal hernandez | |
| 89 | 6 | cristobal hernandez | body: StreamBuilder<DocumentSnapshot>( |
| 90 | 6 | cristobal hernandez | stream: _sensorStream, |
| 91 | 6 | cristobal hernandez | builder: (context, snapshot) { |
| 92 | 6 | cristobal hernandez | if (snapshot.hasError) return const Center(child: Text("Error de conexión", style: TextStyle(color: Colors.white))); |
| 93 | 6 | cristobal hernandez | if (snapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator(color: Color(0xFFFF5400))); |
| 94 | 6 | cristobal hernandez | |
| 95 | 6 | cristobal hernandez | Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>? ?? {}; |
| 96 | 6 | cristobal hernandez | |
| 97 | 6 | cristobal hernandez | double currentTemp = (data['temp_actual'] ?? 0.0).toDouble(); |
| 98 | 6 | cristobal hernandez | double currentPh = (data['ph_actual'] ?? 7.0).toDouble(); |
| 99 | 6 | cristobal hernandez | int currentLight = (data['luz_actual'] ?? 0).toInt(); |
| 100 | 6 | cristobal hernandez | int currentWater = (data['agua_nivel'] ?? 0).toInt(); |
| 101 | 6 | cristobal hernandez | |
| 102 | 6 | cristobal hernandez | List<dynamic> alertas = data['alertas'] ?? []; |
| 103 | 6 | cristobal hernandez | String alertasTexto = alertas.toString(); |
| 104 | 6 | cristobal hernandez | bool hayProblemas = alertas.isNotEmpty; |
| 105 | 6 | cristobal hernandez | |
| 106 | 6 | cristobal hernandez | return Column( |
| 107 | 6 | cristobal hernandez | children: [ |
| 108 | 6 | cristobal hernandez | Expanded( |
| 109 | 6 | cristobal hernandez | child: SingleChildScrollView( |
| 110 | 6 | cristobal hernandez | padding: const EdgeInsets.all(16.0), |
| 111 | 6 | cristobal hernandez | child: Column( |
| 112 | 6 | cristobal hernandez | children: <Widget>[ |
| 113 | 6 | cristobal hernandez | // TARJETA TEMPERATURA |
| 114 | 6 | cristobal hernandez | _buildNemoCard( |
| 115 | 6 | cristobal hernandez | context, |
| 116 | 6 | cristobal hernandez | icon: Icons.thermostat_rounded, |
| 117 | 6 | cristobal hernandez | value: '${currentTemp.toStringAsFixed(1)}°C', |
| 118 | 6 | cristobal hernandez | label: 'Temperatura', |
| 119 | 6 | cristobal hernandez | alert: alertasTexto.contains("Temp") || alertasTexto.contains("T."), |
| 120 | 6 | cristobal hernandez | alertText: "Fuera de Rango", |
| 121 | 6 | cristobal hernandez | ), |
| 122 | 6 | cristobal hernandez | const SizedBox(height: 20), |
| 123 | 6 | cristobal hernandez | |
| 124 | 6 | cristobal hernandez | // TARJETA PH |
| 125 | 6 | cristobal hernandez | _buildNemoCard( |
| 126 | 6 | cristobal hernandez | context, |
| 127 | 6 | cristobal hernandez | customIcon: Container( |
| 128 | 6 | cristobal hernandez | alignment: Alignment.center, |
| 129 | 6 | cristobal hernandez | decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(color: Colors.black87, width: 2.5)), |
| 130 | 6 | cristobal hernandez | width: 50, height: 50, |
| 131 | 6 | cristobal hernandez | child: const Text('pH', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: Colors.black87)), |
| 132 | 6 | cristobal hernandez | ), |
| 133 | 6 | cristobal hernandez | value: currentPh.toStringAsFixed(1), |
| 134 | 6 | cristobal hernandez | label: 'pH Actual', |
| 135 | 6 | cristobal hernandez | alert: alertasTexto.contains("pH"), |
| 136 | 6 | cristobal hernandez | alertText: "Peligro Químico", |
| 137 | 6 | cristobal hernandez | ), |
| 138 | 6 | cristobal hernandez | const SizedBox(height: 20), |
| 139 | 6 | cristobal hernandez | |
| 140 | 6 | cristobal hernandez | // TARJETA LUZ |
| 141 | 6 | cristobal hernandez | _buildNemoCard( |
| 142 | 6 | cristobal hernandez | context, |
| 143 | 6 | cristobal hernandez | icon: Icons.lightbulb_circle, |
| 144 | 6 | cristobal hernandez | value: '$currentLight%', |
| 145 | 6 | cristobal hernandez | label: 'Iluminación', |
| 146 | 6 | cristobal hernandez | alert: alertasTexto.contains("Luz") || alertasTexto.contains("Iluminación"), |
| 147 | 6 | cristobal hernandez | alertText: "Problema Luz", |
| 148 | 6 | cristobal hernandez | ), |
| 149 | 6 | cristobal hernandez | const SizedBox(height: 20), |
| 150 | 6 | cristobal hernandez | |
| 151 | 6 | cristobal hernandez | // TARJETA AGUA |
| 152 | 6 | cristobal hernandez | _buildNemoCard( |
| 153 | 6 | cristobal hernandez | context, |
| 154 | 6 | cristobal hernandez | icon: Icons.waves, |
| 155 | 6 | cristobal hernandez | value: '$currentWater%', |
| 156 | 6 | cristobal hernandez | label: 'Nivel Agua', |
| 157 | 6 | cristobal hernandez | alert: alertasTexto.contains("Agua"), |
| 158 | 6 | cristobal hernandez | alertText: "Rellenar Tanque", |
| 159 | 6 | cristobal hernandez | ), |
| 160 | 6 | cristobal hernandez | ], |
| 161 | 6 | cristobal hernandez | ), |
| 162 | 6 | cristobal hernandez | ), |
| 163 | 6 | cristobal hernandez | ), |
| 164 | 6 | cristobal hernandez | |
| 165 | 6 | cristobal hernandez | // BARRA INFERIOR (CON ALERTAS) |
| 166 | 6 | cristobal hernandez | Container( |
| 167 | 6 | cristobal hernandez | decoration: const BoxDecoration( |
| 168 | 6 | cristobal hernandez | border: Border(top: BorderSide(color: Colors.white24, width: 0.5)) |
| 169 | 6 | cristobal hernandez | ), |
| 170 | 6 | cristobal hernandez | child: BottomNavigationBar( |
| 171 | 6 | cristobal hernandez | backgroundColor: const Color(0xFF00101C), |
| 172 | 6 | cristobal hernandez | selectedItemColor: Colors.white, |
| 173 | 6 | cristobal hernandez | unselectedItemColor: Colors.white54, |
| 174 | 6 | cristobal hernandez | showSelectedLabels: true, |
| 175 | 6 | cristobal hernandez | showUnselectedLabels: true, |
| 176 | 6 | cristobal hernandez | type: BottomNavigationBarType.fixed, |
| 177 | 6 | cristobal hernandez | onTap: _onTapNavigation, |
| 178 | 6 | cristobal hernandez | items: <BottomNavigationBarItem>[ |
| 179 | 6 | cristobal hernandez | const BottomNavigationBarItem( |
| 180 | 6 | cristobal hernandez | icon: Icon(Icons.set_meal_rounded, color: Color(0xFFFF5400)), |
| 181 | 6 | cristobal hernandez | label: 'Alimentación' |
| 182 | 6 | cristobal hernandez | ), |
| 183 | 6 | cristobal hernandez | |
| 184 | 6 | cristobal hernandez | // CAMPANA DE ALERTAS (Se marca si hay problemas) |
| 185 | 6 | cristobal hernandez | BottomNavigationBarItem( |
| 186 | 6 | cristobal hernandez | icon: Stack( |
| 187 | 6 | cristobal hernandez | children: [ |
| 188 | 6 | cristobal hernandez | Icon(Icons.notifications, |
| 189 | 6 | cristobal hernandez | color: hayProblemas ? Colors.redAccent : Colors.white |
| 190 | 6 | cristobal hernandez | ), |
| 191 | 6 | cristobal hernandez | if (hayProblemas) |
| 192 | 6 | cristobal hernandez | Positioned( |
| 193 | 6 | cristobal hernandez | right: 0, top: 0, |
| 194 | 6 | cristobal hernandez | child: Container( |
| 195 | 6 | cristobal hernandez | padding: const EdgeInsets.all(2), |
| 196 | 6 | cristobal hernandez | decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle), |
| 197 | 6 | cristobal hernandez | constraints: const BoxConstraints(minWidth: 10, minHeight: 10), |
| 198 | 6 | cristobal hernandez | ), |
| 199 | 6 | cristobal hernandez | ) |
| 200 | 6 | cristobal hernandez | ], |
| 201 | 6 | cristobal hernandez | ), |
| 202 | 6 | cristobal hernandez | label: 'Alertas', |
| 203 | 6 | cristobal hernandez | ), |
| 204 | 6 | cristobal hernandez | |
| 205 | 6 | cristobal hernandez | const BottomNavigationBarItem( |
| 206 | 6 | cristobal hernandez | icon: Icon(Icons.settings_rounded, color: Color(0xFFFF5400)), |
| 207 | 6 | cristobal hernandez | label: 'Configuración' |
| 208 | 6 | cristobal hernandez | ), |
| 209 | 6 | cristobal hernandez | ], |
| 210 | 6 | cristobal hernandez | ), |
| 211 | 6 | cristobal hernandez | ), |
| 212 | 6 | cristobal hernandez | ], |
| 213 | 6 | cristobal hernandez | ); |
| 214 | 6 | cristobal hernandez | } |
| 215 | 6 | cristobal hernandez | ), |
| 216 | 6 | cristobal hernandez | ); |
| 217 | 6 | cristobal hernandez | } |
| 218 | 6 | cristobal hernandez | |
| 219 | 6 | cristobal hernandez | Widget _buildNemoCard(BuildContext context, {IconData? icon, Widget? customIcon, required String value, required String label, bool alert = false, String? alertText}) { |
| 220 | 6 | cristobal hernandez | Color mainColor = alert ? const Color(0xFFD32F2F) : const Color(0xFFFF5400); |
| 221 | 6 | cristobal hernandez | Color stripeColor = Colors.white; |
| 222 | 6 | cristobal hernandez | Color borderColor = Colors.black; |
| 223 | 6 | cristobal hernandez | |
| 224 | 6 | cristobal hernandez | return AnimatedContainer( |
| 225 | 6 | cristobal hernandez | duration: const Duration(milliseconds: 500), |
| 226 | 6 | cristobal hernandez | height: 100, |
| 227 | 6 | cristobal hernandez | decoration: BoxDecoration( |
| 228 | 6 | cristobal hernandez | color: mainColor, |
| 229 | 6 | cristobal hernandez | borderRadius: BorderRadius.circular(20), |
| 230 | 6 | cristobal hernandez | boxShadow: [BoxShadow(color: alert ? Colors.red.withOpacity(0.6) : Colors.black.withOpacity(0.5), blurRadius: alert ? 20 : 10, offset: const Offset(0, 5))], |
| 231 | 6 | cristobal hernandez | gradient: LinearGradient( |
| 232 | 6 | cristobal hernandez | colors: alert ? [Colors.red.shade900, Colors.red.shade600] : [const Color(0xFFFF5400), const Color(0xFFFF8E00)], |
| 233 | 6 | cristobal hernandez | begin: Alignment.topLeft, end: Alignment.bottomRight, |
| 234 | 6 | cristobal hernandez | ), |
| 235 | 6 | cristobal hernandez | ), |
| 236 | 6 | cristobal hernandez | child: Row( |
| 237 | 6 | cristobal hernandez | children: [ |
| 238 | 6 | cristobal hernandez | Container( |
| 239 | 6 | cristobal hernandez | width: 90, |
| 240 | 6 | cristobal hernandez | decoration: BoxDecoration( |
| 241 | 6 | cristobal hernandez | color: stripeColor, |
| 242 | 6 | cristobal hernandez | borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), bottomLeft: Radius.circular(20)), |
| 243 | 6 | cristobal hernandez | border: Border(right: BorderSide(color: borderColor, width: 6)), |
| 244 | 6 | cristobal hernandez | ), |
| 245 | 6 | cristobal hernandez | child: Center(child: customIcon ?? Icon(icon, size: 40, color: Colors.black87)), |
| 246 | 6 | cristobal hernandez | ), |
| 247 | 6 | cristobal hernandez | Expanded( |
| 248 | 6 | cristobal hernandez | child: Padding( |
| 249 | 6 | cristobal hernandez | padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10), |
| 250 | 6 | cristobal hernandez | child: Column( |
| 251 | 6 | cristobal hernandez | mainAxisAlignment: MainAxisAlignment.center, |
| 252 | 6 | cristobal hernandez | crossAxisAlignment: CrossAxisAlignment.start, |
| 253 | 6 | cristobal hernandez | children: [ |
| 254 | 6 | cristobal hernandez | Text(value, style: const TextStyle(fontSize: 34, fontWeight: FontWeight.w900, color: Colors.white, height: 1.0, shadows: [Shadow(color: Colors.black45, offset: Offset(1,1), blurRadius: 2)])), |
| 255 | 6 | cristobal hernandez | Text(alert ? ' $alertText' : label, style: TextStyle(fontSize: alert ? 14 : 16, fontWeight: FontWeight.bold, color: alert ? Colors.white : Colors.black.withOpacity(0.4))), |
| 256 | 6 | cristobal hernandez | ], |
| 257 | 6 | cristobal hernandez | ), |
| 258 | 6 | cristobal hernandez | ), |
| 259 | 6 | cristobal hernandez | ), |
| 260 | 6 | cristobal hernandez | ], |
| 261 | 6 | cristobal hernandez | ), |
| 262 | 6 | cristobal hernandez | ); |
| 263 | 6 | cristobal hernandez | } |
| 264 | 6 | cristobal hernandez | } |
| 265 | 5 | cristobal hernandez | |
| 266 | 5 | cristobal hernandez | </code></pre> |