import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:localstore/localstore.dart'; import 'package:path_provider/path_provider.dart'; import '../providers/proyectos_provider.dart'; import '../widgets/encabezado_pais.dart'; import 'package:http/http.dart' as http; import 'calculadora_estacionamiento.dart'; import 'interfaz_cuadricula_page.dart'; class VentilacionEstacionamientosPage extends StatefulWidget { const VentilacionEstacionamientosPage({Key? key}) : super(key: key); @override State createState() => _VentilacionEstacionamientosPageState(); } class _VentilacionEstacionamientosPageState extends State { final db = Localstore.instance; final _proyectosProvider = ProyectosProvider(); final List> _projects = []; final Map _estatusCodigos = { "C" : "Cálculo de caudal y equipos", "F" : "Finalizado" }; String _idUsuario = ""; String _token = ""; String _pais = ""; String _code = ""; String _estado = ""; String _ciudad = ""; bool _isLoading = true; double _fontSize = 16.0; final ReceivePort _port = ReceivePort(); List? _tasks; late String _localPath; @override void initState() { super.initState(); _bindBackgroundIsolate(); _prepareSaveDir(); FlutterDownloader.registerCallback(downloadCallback); _fetchProjects().then((value) => { if(mounted){ setState(() => _isLoading = false) } }); } @override void dispose(){ _unbindBackgroundIsolate(); super.dispose(); } Future _fetchProjects() async{ final items = await db.collection('login').get(); final lastID = items?.keys.last; final data = items?[lastID]; _pais = data['pais']; _idUsuario = data['idUsuario']; _token = data['token']; _estado = data['estado']; _ciudad = data['municipio']; final paisArr = data['pais'].split(" "); _code = paisArr.last.replaceAll("(", "").replaceAll(")", ""); final proyectos = await _proyectosProvider.fetchProyectos(_idUsuario, "estacionamientos", _token); proyectos.result.forEach((element) { final timestamp = int.parse(element.fmodificacion); final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); final proyecto = { "idProyecto": element.id, "nombreProyecto": element.nombre, "estatus" : element.estatus, "ulModificacion" : date.toString(), "datos": element.datos, "archpdf": element.archpdf }; _projects.add(proyecto); }); } void _bindBackgroundIsolate(){ bool isSuccess = IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); if(!isSuccess){ _unbindBackgroundIsolate(); _bindBackgroundIsolate(); return; } _port.listen((data) { print('UI Isolate Callback: $data'); String? id = data[0]; DownloadTaskStatus? status = data [1]; int? progress = data[2]; if(_tasks != null && _tasks!.isNotEmpty){ final task = _tasks!.firstWhere((task) => task.taskId == id); setState(() { task.status = status; task.progress = progress; }); } }); } void _unbindBackgroundIsolate() { IsolateNameServer.removePortNameMapping('downloader_send_port'); } void _requestDownload(TaskInfo task) async{ task.taskId = await FlutterDownloader.enqueue( url: task.link!, savedDir: _localPath, showNotification: true, openFileFromNotification: true, saveInPublicStorage: true, ); } Future _prepareSaveDir() async { _localPath = (await _findLocalPath())!; final savedDir = Directory(_localPath); bool hasExisted = await savedDir.exists(); if (!hasExisted) { savedDir.create(); } } Future _findLocalPath() async{ String externalStorageDirPath; if(Platform.isAndroid){ final directory = await getExternalStorageDirectory(); externalStorageDirPath = directory!.path; }else{ final directory = await getApplicationDocumentsDirectory(); externalStorageDirPath = directory.path; } return externalStorageDirPath; } static void downloadCallback(String id, DownloadTaskStatus status, int progress){ print("Task $id: status: $status, progress: $progress"); final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port')!; send.send([id, status, progress]); } @override Widget build(BuildContext context) { final _screenSize = MediaQuery.of(context).size; if(_screenSize.width <= 360){ _fontSize = 14.0; }else if(_screenSize.width > 360 && _screenSize.width <= 400){ _fontSize = 16.0; }else{ _fontSize = 18.0; } return Scaffold( body: _isLoading ? const Center( child: CircularProgressIndicator(), ) : LayoutBuilder( builder: (context, constraints) { return SafeArea( child: SingleChildScrollView( child: Column( children: [ const SizedBox(height: 8.0), Container( margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Row( children: [ TextButton( child: Text( "Portal de memorias de cálculo >", style: GoogleFonts.roboto( fontSize: 16.0, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, ), ), onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0), minimumSize: const Size(0.0, 0.0), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), const SizedBox(width: 8.0), Expanded( child: Container( padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0), child: Text( "Memoria de cálculo de ventilación de estacionamientos", maxLines: 1, overflow: TextOverflow.ellipsis, style: GoogleFonts.roboto( fontSize: 16.0, color: Colors.black54, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, ), ), ), ) ], ), ), const SizedBox(height: 8.0), EncabezadoPais(paisCode: _code, paisName: _pais), const SizedBox(height: 32.0), Image.asset( "assets/estacionamientos.png", width: 88.0, ), const SizedBox(height: 16.0), Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 32.0), child: const Text( "MEMORIA DE CÁLCULO VENTILACIÓN ESTACIONAMIENTOS", textAlign: TextAlign.center, style: TextStyle( fontFamily: "Helvetica Neue Black Cond", fontSize: 28.0, ), ), ), const SizedBox(height: 16.0), Container( margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Text( "NUEVO PROYECTO", textAlign: TextAlign.center, style: GoogleFonts.robotoSlab( fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), const SizedBox(height: 16.0), Container( width: double.infinity, margin: const EdgeInsets.symmetric( horizontal: 32.0, ), padding: const EdgeInsets.all(32.0), color: const Color(0x0F000000), child: Row( children: [ Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( primary: Colors.white, onPrimary: const Color.fromRGBO(180, 4, 4, 0.25), padding: const EdgeInsets.all(16.0), ), child: Column( children: [ Image.asset( "assets/nuevo2.png", width: 64.0, ), const SizedBox(height: 8.0), Text( "Crear nuevo proyecto", style: GoogleFonts.roboto( fontWeight: FontWeight.bold, fontSize: 16.0, color: const Color(0xFFB40404), ), ), ], ), onPressed: () async{ final controller = TextEditingController(); final shouldCreate = await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text( "Nombre del proyecto", textAlign: TextAlign.center, style: GoogleFonts.roboto( fontWeight: FontWeight.bold, ), ), content: TextField( controller: controller, decoration: InputDecoration( hintStyle: GoogleFonts.roboto( color: Colors.grey, fontStyle: FontStyle.italic, ), hintText: "Ingresa el nombre del proyecto", ), style: GoogleFonts.roboto(), maxLines: 1, maxLength: 100, onSubmitted: (val){ if(val.isNotEmpty){ Navigator.pop(context, true); } }, ), actions: [ TextButton( child: const Text("Cancelar"), onPressed: () => Navigator.pop(context, false), ), TextButton( child: const Text("Aceptar"), onPressed: (){ if(controller.text.isNotEmpty){ Navigator.pop(context, true); }else{ Fluttertoast.showToast( msg: "EL nombre del proyecto no debe estar vacío", toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 3, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); } }, ) ], ), ); if(shouldCreate){ final url = Uri.https("smart.solerpalau.mx", "PR/api/v1/quiosco/matrizMemoriasCalculo/nuevoProyectoVentilacion"); final res = await http.post(url, headers: { "Authorization": "Bearer $_token" }, body: { "NOMBREPROYECTO": controller.text, "MEMORIA": "estacionamientos", "FCREACION": DateTime.now().toIso8601String().substring(0, 19), "FMODIF": DateTime.now().toIso8601String().substring(0, 19), "IDUSUARIO": _idUsuario, "ESTATUS": "C", }, ); final decoded = jsonDecode(res.body); if(!decoded['response']){ Fluttertoast.showToast( msg: decoded['message'], toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 3, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); }else{ String idProyecto = decoded['result']['idProyecto']; await Navigator.push(context, MaterialPageRoute( builder: (context) => CalculadoraEstacionamientoPage( ciudad: _ciudad, estado: _estado, pais: _pais, code: _code, idProyecto: idProyecto, idUsuario: _idUsuario, token: _token, datos: "", noProyecto: controller.text, archpdf: "", ), )); setState(() => _isLoading = true); _projects.clear(); await _fetchProjects(); setState(() => _isLoading = false); } } }, ), flex: 1, ), ], ), ), const SizedBox(height: 16.0), Container( margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Text( "HISTORIAL DE PROYECTOS", textAlign: TextAlign.center, style: GoogleFonts.robotoSlab( fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), const SizedBox(height: 16.0), if(_projects.isEmpty) Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Text( "Aún no has creado ningún proyecto.", style: GoogleFonts.roboto( fontWeight: FontWeight.bold, fontSize: 18.0, ), ), ), if(_projects.isNotEmpty) Container( height: 256.0, width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 32.0), child: Column( children: [ Container( child: Row( children: [ Expanded( child: Text( "Nombre", style: GoogleFonts.roboto( fontWeight: FontWeight.bold, fontSize: _fontSize + 2, ), ), flex: 5, ), Expanded( child: Text( "Estatus", style: GoogleFonts.roboto( fontWeight: FontWeight.bold, fontSize: _fontSize + 2, ), ), flex: 5, ), Expanded( child: Text( "Última Modificación", style: GoogleFonts.roboto( fontSize: _fontSize + 2, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), flex: 3, ), Expanded( child: Container(), flex: 1, ), ], ), padding: const EdgeInsets.only(right: 16.0), decoration: const BoxDecoration( border: Border( bottom: BorderSide( color: Colors.black54, width: 2.0, ), ), ), ), Expanded( child: ListView.separated( itemCount: _projects.length, itemBuilder: (context, index){ return ListTile( title: Row( children: [ Expanded( child: Text( _projects[index]["nombreProyecto"]!, style: GoogleFonts.roboto( fontSize: _fontSize, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), flex: 5, ), Expanded( child: Text( _estatusCodigos[_projects[index]["estatus"]!]!, style: GoogleFonts.roboto( fontSize: _fontSize, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), flex: 5, ), Expanded( child: Text( _buildDate(_projects[index]["ulModificacion"]!), style: GoogleFonts.roboto( fontSize: _fontSize, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), flex: 3, ), Expanded( child: PopupMenuButton( onSelected: (val) async{ const auth = "smart.solerpalau.mx"; if(val == "Eliminar"){ final url = Uri.https(auth, "PR/api/v1/quiosco/matrizMemoriasCalculo/borrarProyectos"); final res = await http.post(url, headers: { "Authorization": "Bearer $_token", }, body: { "IDUSUARIO": _idUsuario, "IDPROYECTO": _projects[index]["idProyecto"], }, ); final decoded = jsonDecode(res.body); if(!decoded['response']){ Fluttertoast.showToast( msg: decoded['message'], toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #b40404, #ff0000)", ); }else{ Fluttertoast.showToast( msg: decoded['message'], toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 4, gravity: ToastGravity.CENTER, webBgColor: "linear-gradient(to right, #266B1F, #98F58F)", ); setState(() => _isLoading = true); _projects.clear(); await _fetchProjects(); setState(() => _isLoading = false); } }else if(val == "Descargar PDF"){ Fluttertoast.showToast( msg: "Descargando archivo...", toastLength: Toast.LENGTH_SHORT, ); final task = TaskInfo(name: _projects[index]["nombreProyecto"]!, link: _projects[index]["archpdf"]!); _requestDownload(task); } }, itemBuilder: (context) => ["Eliminar", if(_projects[index]["archpdf"]!.isNotEmpty)"Descargar PDF"].map((e) => PopupMenuItem( value: e, child: Row( children: [ Text( e, style: GoogleFonts.roboto( fontSize: 16.0, color: Colors.black54, ), ), const SizedBox(width: 8.0), Icon( e == "Eliminar" ? Icons.delete : Icons.file_download, color: Colors.black54, ) ], ), )).toList(), ), flex: 1, ), ], ), onTap: () async{ await Navigator.push(context, MaterialPageRoute( builder: (context) => CalculadoraEstacionamientoPage( ciudad: _ciudad, estado: _estado, pais: _pais, code: _code, idProyecto: _projects[index]["idProyecto"]!, idUsuario: _idUsuario, token: _token, datos: _projects[index]["datos"]!, noProyecto: _projects[index]["nombreProyecto"]!, archpdf: "", ), )); setState(() => _isLoading = true); _projects.clear(); await _fetchProjects(); setState(() => _isLoading = false); }, contentPadding: EdgeInsets.zero, ); }, separatorBuilder: (context, index){ return const Divider(); }, ), ) ], ), ), const SizedBox(height: 32.0), ], ), ), ); }, ), ); } String _buildDate(String date){ final parsed = DateTime.parse(date); final now = DateTime.now(); final diff = now.difference(parsed).inDays; if(diff == 0){ return "Hoy"; }else if(diff == 1){ return "Ayer"; }else if(diff >= 2 && diff < 31){ return "Hace $diff días"; }else if(diff >= 31 && diff <= 359){ final diffMes = diff~/30; return "Hace $diffMes mes(es)"; }else if(diff > 359){ final diffAnio = diff~/359; return "Hace $diffAnio año(s)"; }else{ return "Hace mucho tiempo"; } } }