ventilacion_estacionamientos.dart 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'dart:isolate';
  4. import 'dart:ui';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter_downloader/flutter_downloader.dart';
  7. import 'package:fluttertoast/fluttertoast.dart';
  8. import 'package:google_fonts/google_fonts.dart';
  9. import 'package:localstore/localstore.dart';
  10. import 'package:path_provider/path_provider.dart';
  11. import '../providers/proyectos_provider.dart';
  12. import '../widgets/encabezado_pais.dart';
  13. import 'package:http/http.dart' as http;
  14. import 'calculadora_estacionamiento.dart';
  15. import 'interfaz_cuadricula_page.dart';
  16. class VentilacionEstacionamientosPage extends StatefulWidget {
  17. const VentilacionEstacionamientosPage({Key? key}) : super(key: key);
  18. @override
  19. State<VentilacionEstacionamientosPage> createState() => _VentilacionEstacionamientosPageState();
  20. }
  21. class _VentilacionEstacionamientosPageState extends State<VentilacionEstacionamientosPage> {
  22. final db = Localstore.instance;
  23. final _proyectosProvider = ProyectosProvider();
  24. final List<Map<String, String?>> _projects = [];
  25. final Map<String, String> _estatusCodigos = {
  26. "C" : "Cálculo de caudal y equipos",
  27. "F" : "Finalizado"
  28. };
  29. String _idUsuario = "";
  30. String _token = "";
  31. String _pais = "";
  32. String _code = "";
  33. String _estado = "";
  34. String _ciudad = "";
  35. bool _isLoading = true;
  36. double _fontSize = 16.0;
  37. final ReceivePort _port = ReceivePort();
  38. List<TaskInfo>? _tasks;
  39. late String _localPath;
  40. @override
  41. void initState() {
  42. super.initState();
  43. _bindBackgroundIsolate();
  44. _prepareSaveDir();
  45. FlutterDownloader.registerCallback(downloadCallback);
  46. _fetchProjects().then((value) => {
  47. if(mounted){
  48. setState(() => _isLoading = false)
  49. }
  50. });
  51. }
  52. @override
  53. void dispose(){
  54. _unbindBackgroundIsolate();
  55. super.dispose();
  56. }
  57. Future<void> _fetchProjects() async{
  58. final items = await db.collection('login').get();
  59. final lastID = items?.keys.last;
  60. final data = items?[lastID];
  61. _pais = data['pais'];
  62. _idUsuario = data['idUsuario'];
  63. _token = data['token'];
  64. _estado = data['estado'];
  65. _ciudad = data['municipio'];
  66. final paisArr = data['pais'].split(" ");
  67. _code = paisArr.last.replaceAll("(", "").replaceAll(")", "");
  68. final proyectos = await _proyectosProvider.fetchProyectos(_idUsuario, "estacionamientos", _token);
  69. proyectos.result.forEach((element) {
  70. final timestamp = int.parse(element.fmodificacion);
  71. final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
  72. final proyecto = {
  73. "idProyecto": element.id,
  74. "nombreProyecto": element.nombre,
  75. "estatus" : element.estatus,
  76. "ulModificacion" : date.toString(),
  77. "datos": element.datos,
  78. "archpdf": element.archpdf
  79. };
  80. _projects.add(proyecto);
  81. });
  82. }
  83. void _bindBackgroundIsolate(){
  84. bool isSuccess = IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');
  85. if(!isSuccess){
  86. _unbindBackgroundIsolate();
  87. _bindBackgroundIsolate();
  88. return;
  89. }
  90. _port.listen((data) {
  91. print('UI Isolate Callback: $data');
  92. String? id = data[0];
  93. DownloadTaskStatus? status = data [1];
  94. int? progress = data[2];
  95. if(_tasks != null && _tasks!.isNotEmpty){
  96. final task = _tasks!.firstWhere((task) => task.taskId == id);
  97. setState(() {
  98. task.status = status;
  99. task.progress = progress;
  100. });
  101. }
  102. });
  103. }
  104. void _unbindBackgroundIsolate() {
  105. IsolateNameServer.removePortNameMapping('downloader_send_port');
  106. }
  107. void _requestDownload(TaskInfo task) async{
  108. task.taskId = await FlutterDownloader.enqueue(
  109. url: task.link!,
  110. savedDir: _localPath,
  111. showNotification: true,
  112. openFileFromNotification: true,
  113. saveInPublicStorage: true,
  114. );
  115. }
  116. Future<void> _prepareSaveDir() async {
  117. _localPath = (await _findLocalPath())!;
  118. final savedDir = Directory(_localPath);
  119. bool hasExisted = await savedDir.exists();
  120. if (!hasExisted) {
  121. savedDir.create();
  122. }
  123. }
  124. Future<String?> _findLocalPath() async{
  125. String externalStorageDirPath;
  126. if(Platform.isAndroid){
  127. final directory = await getExternalStorageDirectory();
  128. externalStorageDirPath = directory!.path;
  129. }else{
  130. final directory = await getApplicationDocumentsDirectory();
  131. externalStorageDirPath = directory.path;
  132. }
  133. return externalStorageDirPath;
  134. }
  135. static void downloadCallback(String id, DownloadTaskStatus status, int progress){
  136. print("Task $id: status: $status, progress: $progress");
  137. final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port')!;
  138. send.send([id, status, progress]);
  139. }
  140. @override
  141. Widget build(BuildContext context) {
  142. final _screenSize = MediaQuery.of(context).size;
  143. if(_screenSize.width <= 360){
  144. _fontSize = 14.0;
  145. }else if(_screenSize.width > 360 && _screenSize.width <= 400){
  146. _fontSize = 16.0;
  147. }else{
  148. _fontSize = 18.0;
  149. }
  150. return Scaffold(
  151. body: _isLoading ? const Center(
  152. child: CircularProgressIndicator(),
  153. ) : LayoutBuilder(
  154. builder: (context, constraints) {
  155. return SafeArea(
  156. child: SingleChildScrollView(
  157. child: Column(
  158. children: [
  159. const SizedBox(height: 8.0),
  160. Container(
  161. margin: const EdgeInsets.symmetric(horizontal: 32.0),
  162. child: Row(
  163. children: [
  164. TextButton(
  165. child: Text(
  166. "Portal de memorias de cálculo >",
  167. style: GoogleFonts.roboto(
  168. fontSize: 16.0,
  169. fontWeight: FontWeight.bold,
  170. fontStyle: FontStyle.italic,
  171. ),
  172. ),
  173. onPressed: () => Navigator.pop(context),
  174. style: TextButton.styleFrom(
  175. padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
  176. minimumSize: const Size(0.0, 0.0),
  177. tapTargetSize: MaterialTapTargetSize.shrinkWrap,
  178. ),
  179. ),
  180. const SizedBox(width: 8.0),
  181. Expanded(
  182. child: Container(
  183. padding: const EdgeInsets.fromLTRB(0.0, 0.0, 8.0, 0.0),
  184. child: Text(
  185. "Memoria de cálculo de ventilación de estacionamientos",
  186. maxLines: 1,
  187. overflow: TextOverflow.ellipsis,
  188. style: GoogleFonts.roboto(
  189. fontSize: 16.0,
  190. color: Colors.black54,
  191. fontWeight: FontWeight.bold,
  192. fontStyle: FontStyle.italic,
  193. ),
  194. ),
  195. ),
  196. )
  197. ],
  198. ),
  199. ),
  200. const SizedBox(height: 8.0),
  201. EncabezadoPais(paisCode: _code, paisName: _pais),
  202. const SizedBox(height: 32.0),
  203. Image.asset(
  204. "assets/estacionamientos.png",
  205. width: 88.0,
  206. ),
  207. const SizedBox(height: 16.0),
  208. Container(
  209. width: double.infinity,
  210. padding: const EdgeInsets.symmetric(horizontal: 32.0),
  211. child: const Text(
  212. "MEMORIA DE CÁLCULO VENTILACIÓN ESTACIONAMIENTOS",
  213. textAlign: TextAlign.center,
  214. style: TextStyle(
  215. fontFamily: "Helvetica Neue Black Cond",
  216. fontSize: 28.0,
  217. ),
  218. ),
  219. ),
  220. const SizedBox(height: 16.0),
  221. Container(
  222. margin: const EdgeInsets.symmetric(horizontal: 32.0),
  223. child: Text(
  224. "NUEVO PROYECTO",
  225. textAlign: TextAlign.center,
  226. style: GoogleFonts.robotoSlab(
  227. fontWeight: FontWeight.bold,
  228. fontSize: 18.0,
  229. ),
  230. ),
  231. ),
  232. const SizedBox(height: 16.0),
  233. Container(
  234. width: double.infinity,
  235. margin: const EdgeInsets.symmetric(
  236. horizontal: 32.0,
  237. ),
  238. padding: const EdgeInsets.all(32.0),
  239. color: const Color(0x0F000000),
  240. child: Row(
  241. children: [
  242. Expanded(
  243. child: ElevatedButton(
  244. style: ElevatedButton.styleFrom(
  245. primary: Colors.white,
  246. onPrimary: const Color.fromRGBO(180, 4, 4, 0.25),
  247. padding: const EdgeInsets.all(16.0),
  248. ),
  249. child: Column(
  250. children: [
  251. Image.asset(
  252. "assets/nuevo2.png",
  253. width: 64.0,
  254. ),
  255. const SizedBox(height: 8.0),
  256. Text(
  257. "Crear nuevo proyecto",
  258. style: GoogleFonts.roboto(
  259. fontWeight: FontWeight.bold,
  260. fontSize: 16.0,
  261. color: const Color(0xFFB40404),
  262. ),
  263. ),
  264. ],
  265. ),
  266. onPressed: () async{
  267. final controller = TextEditingController();
  268. final shouldCreate = await showDialog(
  269. context: context,
  270. barrierDismissible: false,
  271. builder: (context) => AlertDialog(
  272. title: Text(
  273. "Nombre del proyecto",
  274. textAlign: TextAlign.center,
  275. style: GoogleFonts.roboto(
  276. fontWeight: FontWeight.bold,
  277. ),
  278. ),
  279. content: TextField(
  280. controller: controller,
  281. decoration: InputDecoration(
  282. hintStyle: GoogleFonts.roboto(
  283. color: Colors.grey,
  284. fontStyle: FontStyle.italic,
  285. ),
  286. hintText: "Ingresa el nombre del proyecto",
  287. ),
  288. style: GoogleFonts.roboto(),
  289. maxLines: 1,
  290. maxLength: 100,
  291. onSubmitted: (val){
  292. if(val.isNotEmpty){
  293. Navigator.pop(context, true);
  294. }
  295. },
  296. ),
  297. actions: [
  298. TextButton(
  299. child: const Text("Cancelar"),
  300. onPressed: () => Navigator.pop(context, false),
  301. ),
  302. TextButton(
  303. child: const Text("Aceptar"),
  304. onPressed: (){
  305. if(controller.text.isNotEmpty){
  306. Navigator.pop(context, true);
  307. }else{
  308. Fluttertoast.showToast(
  309. msg: "EL nombre del proyecto no debe estar vacío",
  310. toastLength: Toast.LENGTH_SHORT,
  311. timeInSecForIosWeb: 3,
  312. gravity: ToastGravity.CENTER,
  313. webBgColor: "linear-gradient(to right, #b40404, #ff0000)",
  314. );
  315. }
  316. },
  317. )
  318. ],
  319. ),
  320. );
  321. if(shouldCreate){
  322. final url = Uri.https("smart.solerpalau.mx", "PR/api/v1/quiosco/matrizMemoriasCalculo/nuevoProyectoVentilacion");
  323. final res = await http.post(url,
  324. headers: {
  325. "Authorization": "Bearer $_token"
  326. },
  327. body: {
  328. "NOMBREPROYECTO": controller.text,
  329. "MEMORIA": "estacionamientos",
  330. "FCREACION": DateTime.now().toIso8601String().substring(0, 19),
  331. "FMODIF": DateTime.now().toIso8601String().substring(0, 19),
  332. "IDUSUARIO": _idUsuario,
  333. "ESTATUS": "C",
  334. },
  335. );
  336. final decoded = jsonDecode(res.body);
  337. if(!decoded['response']){
  338. Fluttertoast.showToast(
  339. msg: decoded['message'],
  340. toastLength: Toast.LENGTH_SHORT,
  341. timeInSecForIosWeb: 3,
  342. gravity: ToastGravity.CENTER,
  343. webBgColor: "linear-gradient(to right, #b40404, #ff0000)",
  344. );
  345. }else{
  346. String idProyecto = decoded['result']['idProyecto'];
  347. await Navigator.push(context, MaterialPageRoute(
  348. builder: (context) => CalculadoraEstacionamientoPage(
  349. ciudad: _ciudad,
  350. estado: _estado,
  351. pais: _pais,
  352. code: _code,
  353. idProyecto: idProyecto,
  354. idUsuario: _idUsuario,
  355. token: _token,
  356. datos: "",
  357. noProyecto: controller.text,
  358. archpdf: "",
  359. ),
  360. ));
  361. setState(() => _isLoading = true);
  362. _projects.clear();
  363. await _fetchProjects();
  364. setState(() => _isLoading = false);
  365. }
  366. }
  367. },
  368. ),
  369. flex: 1,
  370. ),
  371. ],
  372. ),
  373. ),
  374. const SizedBox(height: 16.0),
  375. Container(
  376. margin: const EdgeInsets.symmetric(horizontal: 32.0),
  377. child: Text(
  378. "HISTORIAL DE PROYECTOS",
  379. textAlign: TextAlign.center,
  380. style: GoogleFonts.robotoSlab(
  381. fontWeight: FontWeight.bold,
  382. fontSize: 18.0,
  383. ),
  384. ),
  385. ),
  386. const SizedBox(height: 16.0),
  387. if(_projects.isEmpty)
  388. Container(
  389. width: double.infinity,
  390. margin: const EdgeInsets.symmetric(horizontal: 32.0),
  391. child: Text(
  392. "Aún no has creado ningún proyecto.",
  393. style: GoogleFonts.roboto(
  394. fontWeight: FontWeight.bold,
  395. fontSize: 18.0,
  396. ),
  397. ),
  398. ),
  399. if(_projects.isNotEmpty)
  400. Container(
  401. height: 256.0,
  402. width: double.infinity,
  403. margin: const EdgeInsets.symmetric(horizontal: 32.0),
  404. child: Column(
  405. children: [
  406. Container(
  407. child: Row(
  408. children: [
  409. Expanded(
  410. child: Text(
  411. "Nombre",
  412. style: GoogleFonts.roboto(
  413. fontWeight: FontWeight.bold,
  414. fontSize: _fontSize + 2,
  415. ),
  416. ),
  417. flex: 5,
  418. ),
  419. Expanded(
  420. child: Text(
  421. "Estatus",
  422. style: GoogleFonts.roboto(
  423. fontWeight: FontWeight.bold,
  424. fontSize: _fontSize + 2,
  425. ),
  426. ),
  427. flex: 5,
  428. ),
  429. Expanded(
  430. child: Text(
  431. "Última Modificación",
  432. style: GoogleFonts.roboto(
  433. fontSize: _fontSize + 2,
  434. fontWeight: FontWeight.bold,
  435. ),
  436. maxLines: 1,
  437. overflow: TextOverflow.ellipsis,
  438. ),
  439. flex: 3,
  440. ),
  441. Expanded(
  442. child: Container(),
  443. flex: 1,
  444. ),
  445. ],
  446. ),
  447. padding: const EdgeInsets.only(right: 16.0),
  448. decoration: const BoxDecoration(
  449. border: Border(
  450. bottom: BorderSide(
  451. color: Colors.black54,
  452. width: 2.0,
  453. ),
  454. ),
  455. ),
  456. ),
  457. Expanded(
  458. child: ListView.separated(
  459. itemCount: _projects.length,
  460. itemBuilder: (context, index){
  461. return ListTile(
  462. title: Row(
  463. children: [
  464. Expanded(
  465. child: Text(
  466. _projects[index]["nombreProyecto"]!,
  467. style: GoogleFonts.roboto(
  468. fontSize: _fontSize,
  469. ),
  470. maxLines: 1,
  471. overflow: TextOverflow.ellipsis,
  472. ),
  473. flex: 5,
  474. ),
  475. Expanded(
  476. child: Text(
  477. _estatusCodigos[_projects[index]["estatus"]!]!,
  478. style: GoogleFonts.roboto(
  479. fontSize: _fontSize,
  480. ),
  481. maxLines: 1,
  482. overflow: TextOverflow.ellipsis,
  483. ),
  484. flex: 5,
  485. ),
  486. Expanded(
  487. child: Text(
  488. _buildDate(_projects[index]["ulModificacion"]!),
  489. style: GoogleFonts.roboto(
  490. fontSize: _fontSize,
  491. ),
  492. maxLines: 1,
  493. overflow: TextOverflow.ellipsis,
  494. ),
  495. flex: 3,
  496. ),
  497. Expanded(
  498. child: PopupMenuButton<String>(
  499. onSelected: (val) async{
  500. const auth = "smart.solerpalau.mx";
  501. if(val == "Eliminar"){
  502. final url = Uri.https(auth, "PR/api/v1/quiosco/matrizMemoriasCalculo/borrarProyectos");
  503. final res = await http.post(url,
  504. headers: {
  505. "Authorization": "Bearer $_token",
  506. },
  507. body: {
  508. "IDUSUARIO": _idUsuario,
  509. "IDPROYECTO": _projects[index]["idProyecto"],
  510. },
  511. );
  512. final decoded = jsonDecode(res.body);
  513. if(!decoded['response']){
  514. Fluttertoast.showToast(
  515. msg: decoded['message'],
  516. toastLength: Toast.LENGTH_SHORT,
  517. timeInSecForIosWeb: 4,
  518. gravity: ToastGravity.CENTER,
  519. webBgColor: "linear-gradient(to right, #b40404, #ff0000)",
  520. );
  521. }else{
  522. Fluttertoast.showToast(
  523. msg: decoded['message'],
  524. toastLength: Toast.LENGTH_SHORT,
  525. timeInSecForIosWeb: 4,
  526. gravity: ToastGravity.CENTER,
  527. webBgColor: "linear-gradient(to right, #266B1F, #98F58F)",
  528. );
  529. setState(() => _isLoading = true);
  530. _projects.clear();
  531. await _fetchProjects();
  532. setState(() => _isLoading = false);
  533. }
  534. }else if(val == "Descargar PDF"){
  535. Fluttertoast.showToast(
  536. msg: "Descargando archivo...",
  537. toastLength: Toast.LENGTH_SHORT,
  538. );
  539. final task = TaskInfo(name: _projects[index]["nombreProyecto"]!, link: _projects[index]["archpdf"]!);
  540. _requestDownload(task);
  541. }
  542. },
  543. itemBuilder: (context) => ["Eliminar", if(_projects[index]["archpdf"]!.isNotEmpty)"Descargar PDF"].map((e) => PopupMenuItem(
  544. value: e,
  545. child: Row(
  546. children: [
  547. Text(
  548. e,
  549. style: GoogleFonts.roboto(
  550. fontSize: 16.0,
  551. color: Colors.black54,
  552. ),
  553. ),
  554. const SizedBox(width: 8.0),
  555. Icon(
  556. e == "Eliminar" ? Icons.delete : Icons.file_download,
  557. color: Colors.black54,
  558. )
  559. ],
  560. ),
  561. )).toList(),
  562. ),
  563. flex: 1,
  564. ),
  565. ],
  566. ),
  567. onTap: () async{
  568. await Navigator.push(context, MaterialPageRoute(
  569. builder: (context) => CalculadoraEstacionamientoPage(
  570. ciudad: _ciudad,
  571. estado: _estado,
  572. pais: _pais,
  573. code: _code,
  574. idProyecto: _projects[index]["idProyecto"]!,
  575. idUsuario: _idUsuario,
  576. token: _token,
  577. datos: _projects[index]["datos"]!,
  578. noProyecto: _projects[index]["nombreProyecto"]!,
  579. archpdf: "",
  580. ),
  581. ));
  582. setState(() => _isLoading = true);
  583. _projects.clear();
  584. await _fetchProjects();
  585. setState(() => _isLoading = false);
  586. },
  587. contentPadding: EdgeInsets.zero,
  588. );
  589. },
  590. separatorBuilder: (context, index){
  591. return const Divider();
  592. },
  593. ),
  594. )
  595. ],
  596. ),
  597. ),
  598. const SizedBox(height: 32.0),
  599. ],
  600. ),
  601. ),
  602. );
  603. },
  604. ),
  605. );
  606. }
  607. String _buildDate(String date){
  608. final parsed = DateTime.parse(date);
  609. final now = DateTime.now();
  610. final diff = now.difference(parsed).inDays;
  611. if(diff == 0){
  612. return "Hoy";
  613. }else if(diff == 1){
  614. return "Ayer";
  615. }else if(diff >= 2 && diff < 31){
  616. return "Hace $diff días";
  617. }else if(diff >= 31 && diff <= 359){
  618. final diffMes = diff~/30;
  619. return "Hace $diffMes mes(es)";
  620. }else if(diff > 359){
  621. final diffAnio = diff~/359;
  622. return "Hace $diffAnio año(s)";
  623. }else{
  624. return "Hace mucho tiempo";
  625. }
  626. }
  627. }