widgets_components.dart 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. // ignore_for_file: use_build_context_synchronously, prefer_if_null_operators, prefer_conditional_assignment
  2. import 'dart:io';
  3. import 'dart:typed_data';
  4. import 'package:camera/camera.dart';
  5. import 'package:flutter/material.dart';
  6. //import 'package:yoshi_papas_app/viewmodels/media_view_model.dart';
  7. import 'package:intl/intl.dart';
  8. import 'package:omni_datetime_picker/omni_datetime_picker.dart';
  9. import 'package:flutter/material.dart' as d;
  10. import 'package:datetime_picker_formfield/datetime_picker_formfield.dart' as d2;
  11. import 'package:provider/provider.dart';
  12. import 'package:url_launcher/url_launcher.dart';
  13. import 'package:flutter/foundation.dart' show kIsWeb;
  14. import 'package:path_provider/path_provider.dart';
  15. import '../data/session/session_storage.dart';
  16. import '../models/media_model.dart';
  17. import '../themes/themes.dart';
  18. import 'package:timezone/timezone.dart' as tz;
  19. import 'package:http/http.dart' as http;
  20. import 'package:otp/otp.dart';
  21. import 'package:timezone/data/latest.dart' as timezone;
  22. import "package:universal_html/html.dart" as html;
  23. //import '../viewmodels/ordenes_view_model.dart';
  24. //import 'dart:html';
  25. //import 'dart:html' as html;
  26. encabezado(
  27. {double elevacion = 1,
  28. List<Widget>? acciones,
  29. PreferredSize? bottom,
  30. Color? backgroundColor,
  31. String? nombre,
  32. Widget? leading = null,
  33. String? titulo,
  34. bool centerTitle = true}) {
  35. TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
  36. if (acciones == null) {
  37. acciones = [];
  38. }
  39. return AppBar(
  40. leading: leading,
  41. elevation: elevacion,
  42. backgroundColor: backgroundColor,
  43. centerTitle: centerTitle,
  44. title: titulo == null
  45. ? Image.asset("assets/logo.png", width: 100)
  46. : Text(titulo.toString(), style: estilo),
  47. actions: [
  48. ...acciones,
  49. Image.asset("assets/logo.png", width: 100),
  50. ],
  51. bottom: bottom);
  52. }
  53. tarjeta(Widget item, {Color color = Colors.white, double padding = 0}) {
  54. if (padding > 0) {
  55. return Padding(
  56. padding: const EdgeInsets.all(0),
  57. child: Card(
  58. color: color,
  59. shape:
  60. RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
  61. margin: const EdgeInsets.only(bottom: 16),
  62. elevation: 0,
  63. borderOnForeground: true,
  64. child: Padding(padding: EdgeInsets.all(padding), child: item)));
  65. }
  66. return Card(
  67. color: color,
  68. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
  69. margin: const EdgeInsets.only(bottom: 8),
  70. elevation: 0,
  71. borderOnForeground: true,
  72. child: Padding(
  73. padding: const EdgeInsets.all(8),
  74. child: item,
  75. ));
  76. }
  77. class Cargando extends StatelessWidget {
  78. const Cargando({super.key});
  79. @override
  80. Widget build(BuildContext context) {
  81. return Scaffold(
  82. backgroundColor: const Color(0xFFE0E0E0),
  83. body: Center(
  84. child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
  85. Padding(
  86. padding: const EdgeInsets.fromLTRB(16, 0, 16, 10),
  87. child: Image.asset("assets/logo.png", height: 200)),
  88. const CircularProgressIndicator(backgroundColor: Colors.grey),
  89. Container(margin: const EdgeInsets.only(bottom: 8.0)),
  90. const Text("Cargando contenido...",
  91. style: TextStyle(
  92. fontWeight: FontWeight.bold,
  93. fontSize: 16,
  94. color: Colors.black)),
  95. const Text("Por favor espere.",
  96. style: TextStyle(
  97. fontWeight: FontWeight.bold,
  98. fontSize: 16,
  99. color: Colors.black),
  100. textAlign: TextAlign.center)
  101. ]),
  102. ));
  103. }
  104. }
  105. boton(String etiqueta, Function()? accion,
  106. {double height = 75, double width = 380}) {
  107. return Align(
  108. alignment: Alignment.topLeft,
  109. child: SizedBox(
  110. height: height,
  111. width: 380,
  112. child: ElevatedButton(
  113. style: ButtonStyle(
  114. shape: MaterialStatePropertyAll(
  115. RoundedRectangleBorder(
  116. borderRadius: BorderRadius.circular(10),
  117. ),
  118. ),
  119. backgroundColor: MaterialStatePropertyAll(AppTheme.tertiary),
  120. ),
  121. onPressed: accion,
  122. child: Row(
  123. mainAxisAlignment: MainAxisAlignment.center,
  124. children: [
  125. Text(etiqueta,
  126. style: TextStyle(fontSize: 18, color: AppTheme.quaternary)),
  127. ],
  128. ),
  129. ),
  130. ),
  131. );
  132. }
  133. Future<DateTime?> showHora(BuildContext context, DateTime? fecha) async {
  134. if (fecha == null) {
  135. return fecha;
  136. }
  137. final tiempo = await showTimePicker(
  138. useRootNavigator: false,
  139. helpText: "CAPTURAR HORA",
  140. confirmText: "ACEPTAR",
  141. cancelText: "CANCELAR",
  142. hourLabelText: "HORA",
  143. minuteLabelText: "MINUTO",
  144. initialEntryMode: TimePickerEntryMode.input,
  145. context: context,
  146. initialTime: TimeOfDay.fromDateTime(fecha),
  147. builder: (context, childs) {
  148. return MediaQuery(
  149. data: MediaQuery.of(context).copyWith(
  150. alwaysUse24HourFormat: true,
  151. padding: const EdgeInsets.all(1),
  152. size: const Size.square(1),
  153. textScaler: const TextScaler.linear(.8)),
  154. child: childs!);
  155. },
  156. );
  157. return d2.DateTimeField.combine(fecha!, tiempo);
  158. }
  159. Future<DateTime?> showDatetimePicker(BuildContext context, DateTime? fecha,
  160. {DateTime? inicia,
  161. DateTime? termina,
  162. OmniDateTimePickerType tipo = OmniDateTimePickerType.dateAndTime,
  163. bool solofecha = false}) async {
  164. if (termina == null) {
  165. termina = DateTime(2030);
  166. }
  167. final f = await showDatePicker(
  168. context: context,
  169. initialDate: fecha ?? DateTime.now(),
  170. firstDate: inicia ?? DateTime(2023),
  171. lastDate: termina,
  172. cancelText: "CANCELAR",
  173. confirmText: "ACEPTAR",
  174. builder: (context, child) {
  175. return Theme(
  176. data: Theme.of(context).copyWith(
  177. colorScheme: ColorScheme.light(
  178. primary: AppTheme.primary,
  179. onSurface: AppTheme.secondary,
  180. ),
  181. textButtonTheme: TextButtonThemeData(
  182. style: TextButton.styleFrom(
  183. backgroundColor: AppTheme.secondary,
  184. ),
  185. ),
  186. ),
  187. child: child!,
  188. );
  189. },
  190. );
  191. if (f == null || solofecha) {
  192. return f;
  193. }
  194. final tiempo = await showTimePicker(
  195. context: context,
  196. initialTime: TimeOfDay.fromDateTime(f),
  197. helpText: "CAPTURAR HORA",
  198. confirmText: "ACEPTAR",
  199. cancelText: "CANCELAR",
  200. hourLabelText: "HORA",
  201. minuteLabelText: "MINUTO",
  202. initialEntryMode: TimePickerEntryMode.input,
  203. builder: (context, child) {
  204. return Theme(
  205. data: Theme.of(context).copyWith(
  206. colorScheme: ColorScheme.light(
  207. primary: AppTheme.primary,
  208. onSurface: AppTheme.secondary,
  209. ),
  210. textButtonTheme: TextButtonThemeData(
  211. style: TextButton.styleFrom(
  212. backgroundColor: AppTheme.secondary,
  213. ),
  214. ),
  215. ),
  216. child: child!,
  217. );
  218. },
  219. );
  220. return d2.DateTimeField.combine(f, tiempo);
  221. }
  222. class FechaSelectWidget extends StatelessWidget {
  223. final DateTime? fecha;
  224. final Function(DateTime) onFechaChanged;
  225. final String etiqueta;
  226. final BuildContext context;
  227. const FechaSelectWidget({
  228. Key? key,
  229. required this.fecha,
  230. required this.onFechaChanged,
  231. required this.etiqueta,
  232. required this.context,
  233. }) : super(key: key);
  234. @override
  235. Widget build(BuildContext context) {
  236. return Column(
  237. crossAxisAlignment: CrossAxisAlignment.start,
  238. children: [
  239. Text(
  240. etiqueta,
  241. style: const TextStyle(
  242. fontWeight: FontWeight.bold,
  243. fontSize: 16,
  244. ),
  245. ),
  246. const SizedBox(height: 5),
  247. InkWell(
  248. onTap: () async {
  249. DateTime? d = await showDatetimePicker(
  250. context,
  251. fecha,
  252. tipo: OmniDateTimePickerType.date,
  253. solofecha: true,
  254. );
  255. if (d == null) return;
  256. onFechaChanged(d);
  257. },
  258. child: Container(
  259. decoration: BoxDecoration(
  260. border: Border.all(color: Colors.grey[400]!),
  261. borderRadius: BorderRadius.circular(10),
  262. ),
  263. padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15),
  264. child: Row(
  265. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  266. children: [
  267. Text(
  268. fecha == null
  269. ? "Seleccionar"
  270. : DateFormat("dd/MM/yyyy").format(fecha!),
  271. style: TextStyle(
  272. color: fecha == null ? Colors.grey : Colors.black,
  273. ),
  274. ),
  275. const Icon(Icons.calendar_month),
  276. ],
  277. ),
  278. ),
  279. ),
  280. ],
  281. );
  282. }
  283. }
  284. Widget circulo(Widget item, {Function()? accion, Color? color}) {
  285. color ??= Colors.black;
  286. return InkWell(
  287. onTap: accion,
  288. child: Container(
  289. decoration: BoxDecoration(
  290. border: Border.all(
  291. color: color, // <--- border color
  292. width: 2.0,
  293. ),
  294. borderRadius: const BorderRadius.all(
  295. Radius.circular(50) // <--- border radius here
  296. ),
  297. ),
  298. child: item));
  299. }
  300. alerta(BuildContext context, {String? etiqueta = "Capturar búsqueda"}) {
  301. return showDialog(
  302. context: context,
  303. builder: (context) {
  304. return AlertDialog(
  305. title: Text(etiqueta.toString()),
  306. actions: [
  307. Row(children: [
  308. Expanded(
  309. child: TextButton(
  310. style: ButtonStyle(
  311. backgroundColor:
  312. MaterialStatePropertyAll(AppTheme.tertiary)),
  313. onPressed: () async {
  314. Navigator.pop(context);
  315. },
  316. child: Text(
  317. 'Continuar',
  318. style: TextStyle(color: AppTheme.quaternary),
  319. ),
  320. ))
  321. ])
  322. ],
  323. );
  324. });
  325. }
  326. cuadroConfirmacion(BuildContext context,
  327. {String? etiqueta = "¿Estás seguro?", required VoidCallback onConfirm}) {
  328. return showDialog(
  329. context: context,
  330. builder: (context) {
  331. return AlertDialog(
  332. title: Text(etiqueta.toString()),
  333. actions: [
  334. Row(
  335. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  336. children: [
  337. Expanded(
  338. child: TextButton(
  339. style: ButtonStyle(
  340. backgroundColor: MaterialStatePropertyAll(Colors.red)),
  341. onPressed: () {
  342. Navigator.pop(context);
  343. },
  344. child: const Text(
  345. 'No',
  346. style: TextStyle(color: Colors.white),
  347. ),
  348. ),
  349. ),
  350. SizedBox(width: 10),
  351. Expanded(
  352. child: TextButton(
  353. style: ButtonStyle(
  354. backgroundColor: MaterialStatePropertyAll(Colors.green)),
  355. onPressed: () {
  356. onConfirm();
  357. Navigator.pop(context);
  358. },
  359. child: const Text(
  360. 'Sí',
  361. style: TextStyle(color: Colors.white),
  362. ),
  363. ),
  364. ),
  365. ],
  366. ),
  367. ],
  368. );
  369. },
  370. );
  371. }
  372. botonElevated(
  373. {Function()? accion,
  374. String? titulo = "Buscar",
  375. Color color = Colors.black}) {
  376. return ElevatedButton(
  377. style: ElevatedButton.styleFrom(
  378. backgroundColor: color,
  379. shape: RoundedRectangleBorder(
  380. borderRadius: BorderRadius.circular(12), // <-- Radius
  381. )),
  382. onPressed: accion,
  383. child: Text(
  384. titulo.toString(),
  385. style: const TextStyle(color: Colors.white),
  386. ),
  387. );
  388. }
  389. Widget xMedia(XFile m) {
  390. bool archivo = false;
  391. Widget icono = const Icon(Icons.file_copy, size: 50);
  392. int lastIndex = m.name.lastIndexOf('.');
  393. String extension = "";
  394. if (lastIndex != -1 && lastIndex < m.name.length - 1) {
  395. extension = m.name.substring(lastIndex + 1);
  396. }
  397. switch (extension) {
  398. case "mp4":
  399. archivo = true;
  400. icono = const Icon(Icons.video_file, size: 50);
  401. break;
  402. case "wav":
  403. archivo = true;
  404. icono = Image.asset("assets/audio-file.png");
  405. archivo = true;
  406. break;
  407. case "jpeg":
  408. case "jpg":
  409. case "png":
  410. break;
  411. case "pdf":
  412. icono = Image.asset("assets/pdf-file.png");
  413. archivo = true;
  414. break;
  415. case "csv":
  416. case "docx":
  417. icono = Image.asset("assets/word.png");
  418. archivo = true;
  419. break;
  420. case "xlsx":
  421. icono = Image.asset("assets/excel.png");
  422. archivo = true;
  423. break;
  424. }
  425. String nombre = m.name.toString();
  426. if (nombre.length > 25) {
  427. nombre = "${m.name.toString().substring(0, 24)}...";
  428. }
  429. return InkWell(
  430. onTap: () async {
  431. String url = m.path.toString();
  432. await canLaunch(url)
  433. ? await launch(url, forceSafariVC: false, forceWebView: false)
  434. : print('Could not launch '); //aqui
  435. },
  436. child: Stack(alignment: Alignment.center, children: [
  437. Container(
  438. height: 100.0,
  439. width: 100.0,
  440. clipBehavior: Clip.antiAlias,
  441. decoration: BoxDecoration(
  442. border: Border.all(color: Colors.black, width: 1.0),
  443. borderRadius: const BorderRadius.all(Radius.circular(15.0)),
  444. ),
  445. child: archivo
  446. ? icono
  447. : Image.network(m.path.toString(),
  448. scale:
  449. 1) // _firma == null?const Icon(Icons.pages, size: 30, color: Colors.black): contenedorFirma,
  450. ),
  451. Positioned(
  452. bottom: 0,
  453. child: Container(
  454. width: 200,
  455. height: 40,
  456. color: Colors.grey.shade800,
  457. child: Column(
  458. mainAxisAlignment: MainAxisAlignment.center,
  459. children: [
  460. Text(
  461. nombre,
  462. textAlign: TextAlign.center,
  463. textScaler: const TextScaler.linear(.8),
  464. style: const TextStyle(color: Colors.white),
  465. ),
  466. ]),
  467. )),
  468. ]));
  469. }
  470. Widget wMedia(Media m, {Function()? eliminar}) {
  471. bool archivo = false;
  472. Widget icono = const Icon(Icons.file_copy, size: 50);
  473. switch (m.extension) {
  474. case "mp4":
  475. archivo = true;
  476. icono = const Icon(Icons.video_file, size: 50);
  477. break;
  478. case "wav":
  479. archivo = true;
  480. icono = Image.asset("assets/audio-file.png");
  481. archivo = true;
  482. break;
  483. case "jpeg":
  484. case "jpg":
  485. case "png":
  486. break;
  487. case "pdf":
  488. icono = Image.asset("assets/pdf-file.png");
  489. archivo = true;
  490. break;
  491. case "csv":
  492. case "docx":
  493. icono = Image.asset("assets/word.png");
  494. archivo = true;
  495. break;
  496. case "xlsx":
  497. icono = Image.asset("assets/excel.png");
  498. archivo = true;
  499. break;
  500. }
  501. String fecha = "";
  502. if (m.creado != null) {
  503. String timeZone = 'America/Hermosillo';
  504. fecha = formatFechaConZonaHoraria(m.creado!, timeZone);
  505. }
  506. String nombre = m.nombre.toString();
  507. if (nombre.length > 25) {
  508. nombre = "${m.nombre.toString().substring(0, 24)}...";
  509. }
  510. return InkWell(
  511. onTap: () async {
  512. String url = m.ruta.toString();
  513. await canLaunch(url)
  514. ? await launch(url, forceSafariVC: false, forceWebView: false)
  515. : print('Could not launch '); //aqui
  516. },
  517. child: Stack(alignment: Alignment.center, children: [
  518. Container(
  519. height: 160.0,
  520. width: 160.0,
  521. clipBehavior: Clip.antiAlias,
  522. decoration: BoxDecoration(
  523. border: Border.all(color: Colors.black, width: 1.0),
  524. borderRadius: const BorderRadius.all(Radius.circular(15.0)),
  525. ),
  526. child: archivo
  527. ? icono
  528. : Image.network(m.ruta.toString(),
  529. scale:
  530. 1) // _firma == null?const Icon(Icons.pages, size: 30, color: Colors.black): contenedorFirma,
  531. ),
  532. Positioned(
  533. bottom: 0,
  534. child: Container(
  535. width: 500,
  536. height: 40,
  537. color: Colors.grey.shade800,
  538. child: Column(
  539. mainAxisAlignment: MainAxisAlignment.center,
  540. children: [
  541. Text(
  542. nombre,
  543. textAlign: TextAlign.center,
  544. textScaler: const TextScaler.linear(.8),
  545. style: const TextStyle(color: Colors.white),
  546. ),
  547. Text(
  548. fecha,
  549. textAlign: TextAlign.center,
  550. textScaler: const TextScaler.linear(.8),
  551. style: const TextStyle(color: Colors.white),
  552. ),
  553. ]),
  554. )),
  555. Positioned(
  556. left: 0,
  557. top: 0,
  558. child: ElevatedButton(
  559. onPressed: eliminar,
  560. style: ElevatedButton.styleFrom(
  561. shape: CircleBorder(),
  562. padding: EdgeInsets.all(16),
  563. ),
  564. child: Icon(
  565. Icons.delete,
  566. color: Colors.red.shade800,
  567. ),
  568. ),
  569. ),
  570. ]));
  571. }
  572. Widget agregarMedia(
  573. {Color color = Colors.green,
  574. Function()? accion,
  575. String titulo = "Agregar Media",
  576. Icon? icono,
  577. double tamano = 160}) {
  578. if (icono == null) {
  579. icono = Icon(
  580. Icons.add,
  581. size: tamano < 100 ? 60 : 100,
  582. color: Colors.white,
  583. );
  584. }
  585. return InkWell(
  586. onTap: accion,
  587. child: Stack(alignment: Alignment.center, children: [
  588. // Contenedor con un color de fondo y dimensiones específicas
  589. Container(
  590. width: tamano,
  591. height: tamano,
  592. color: color,
  593. ),
  594. // Texto flotante
  595. Positioned(
  596. child:
  597. Column(mainAxisAlignment: MainAxisAlignment.center, children: [
  598. icono,
  599. titulo.isEmpty
  600. ? Container()
  601. : Text(
  602. titulo,
  603. style: const TextStyle(
  604. color: Colors.white,
  605. fontSize: 16,
  606. ),
  607. ),
  608. ])),
  609. ]));
  610. }
  611. String formatFechaConZonaHoraria(DateTime dateTime, String timeZone) {
  612. // Obtiene la ubicación de la zona horaria
  613. tz.Location location = tz.getLocation(timeZone);
  614. // Convierte la fecha a la zona horaria deseada
  615. tz.TZDateTime fechaConZona = tz.TZDateTime.from(dateTime, location);
  616. // Formatea la fecha con la zona horaria
  617. //String formattedFecha = DateFormat("dd/MM/yyyy HH:mm '($timeZone)'").format(fechaConZona);
  618. String formattedFecha = DateFormat("dd/MM/yyyy HH:mm").format(fechaConZona);
  619. return formattedFecha;
  620. }
  621. html.Location getLocation(String timeZone) {
  622. // Obtiene la ubicación de la zona horaria
  623. return getLocation(timeZone);
  624. }
  625. int obtenerAxiscount(Size s) {
  626. int axiscount = 8;
  627. if (s.width > 1300) {
  628. axiscount = 8;
  629. }
  630. if (s.width > 900 && s.width < 1300) {
  631. axiscount = 5;
  632. }
  633. if (s.width > 700 && s.width < 900) {
  634. axiscount = 4;
  635. }
  636. if (s.width > 600 && s.width < 700) {
  637. axiscount = 3;
  638. }
  639. if (s.width < 600) {
  640. axiscount = 2;
  641. }
  642. return axiscount;
  643. }
  644. imprimirExcel(String url, String nombre) async {
  645. var t = await SessionStorage().getToken();
  646. Map<String, String> headers = {
  647. 'Authorization': 'Bearer $t',
  648. 'Content-Type': 'application/json', // Puedes ajustar según sea necesario
  649. };
  650. http.Response response = await http.get(
  651. Uri.parse(url),
  652. headers: headers,
  653. );
  654. if (response.statusCode == 200) {
  655. // Obtener el contenido del archivo como Uint8List
  656. Uint8List fileBytes = response.bodyBytes;
  657. if (!kIsWeb) {
  658. // Lógica para dispositivos móviles y escritorio
  659. final directory = await getApplicationDocumentsDirectory();
  660. final path = '${directory.path}/$nombre.xlsx';
  661. final file = File(path);
  662. await file.writeAsBytes(fileBytes);
  663. final Uri fileUri = Uri.file(path);
  664. if (await canLaunchUrl(fileUri)) {
  665. await launchUrl(fileUri);
  666. } else {
  667. throw 'No se pudo abrir el archivo';
  668. }
  669. return; // Retorna después de manejar la lógica de dispositivos móviles
  670. }
  671. // Lógica específica para la web sigue aquí
  672. // Asegúrate de que este código solo se ejecute en la web
  673. if (kIsWeb) {
  674. // Crea y descarga el archivo para la web
  675. final blob = html.Blob([fileBytes]);
  676. final url = html.Url.createObjectUrlFromBlob(blob);
  677. final anchor = html.AnchorElement(href: url)
  678. ..setAttribute('download', '$nombre.xlsx')
  679. ..click();
  680. html.Url.revokeObjectUrl(url);
  681. }
  682. } else {
  683. print(
  684. 'Error al descargar el archivo. Código de estado: ${response.statusCode}');
  685. }
  686. }
  687. imprimirPdf(String url, String nombre) async {
  688. var t = await SessionStorage().getToken();
  689. Map<String, String> headers = {
  690. 'Authorization': 'Bearer $t',
  691. 'Content-Type': 'application/json', // Puedes ajustar según sea necesario
  692. };
  693. http.Response response = await http.get(
  694. Uri.parse(url),
  695. headers: headers,
  696. );
  697. if (response.statusCode == 200) {
  698. // Obtener el contenido del archivo como Uint8List
  699. Uint8List fileBytes = response.bodyBytes;
  700. // Crear un blob con los bytes del archivo
  701. final blob = html.Blob([fileBytes]);
  702. // Crear un objeto URL para el blob
  703. final url = html.Url.createObjectUrlFromBlob(blob);
  704. // Crear un enlace de descarga y hacer clic en él para descargar el archivo
  705. final anchor = html.AnchorElement(href: url)
  706. ..target = 'blank'
  707. ..download =
  708. '$nombre.pdf'; // Ajustar el nombre y la extensión del archivo
  709. anchor.click();
  710. // Liberar el objeto URL
  711. html.Url.revokeObjectUrl(url);
  712. } else {
  713. print(
  714. 'Error al descargar el archivo. Código de estado: ${response.statusCode}');
  715. }
  716. }
  717. Widget usuarioHeader(String nombre, String correo) {
  718. return Row(
  719. mainAxisAlignment: MainAxisAlignment.center,
  720. crossAxisAlignment: CrossAxisAlignment.center,
  721. children: [
  722. Column(
  723. mainAxisAlignment: MainAxisAlignment.center,
  724. crossAxisAlignment: CrossAxisAlignment.end,
  725. children: [
  726. Text(nombre,
  727. style: TextStyle(
  728. color: Colors.black, fontWeight: FontWeight.bold),
  729. textAlign: TextAlign.right),
  730. Text(correo,
  731. style: TextStyle(color: Colors.grey.shade900),
  732. textAlign: TextAlign.right),
  733. ]),
  734. Text(" "),
  735. CircleAvatar(
  736. child: Icon(Icons.verified_user, color: Colors.white),
  737. backgroundColor: Colors.black),
  738. Text(" "),
  739. ]);
  740. }
  741. Future eliminarMedia(
  742. BuildContext context, Media m, Function()? confirmacion) async {
  743. return await showDialog(
  744. context: context,
  745. builder: (context) {
  746. return AlertDialog(
  747. surfaceTintColor: AppTheme.secondary,
  748. title: const Text('¿Desea eliminar esta archivo?'),
  749. actions: [
  750. TextButton(
  751. onPressed: () {
  752. Navigator.pop(context);
  753. },
  754. child: const Text('Cancelar'),
  755. ),
  756. TextButton(
  757. onPressed: confirmacion,
  758. child: const Text(
  759. 'Sí, estoy seguro',
  760. style: TextStyle(color: Colors.red),
  761. ),
  762. ),
  763. ],
  764. );
  765. },
  766. );
  767. }
  768. String formatoMiles(double? value) {
  769. final formatter = NumberFormat('#,##0.00', 'en_US');
  770. return formatter.format(value);
  771. }
  772. class TotpCuadroConfirmacion extends StatefulWidget {
  773. final String title;
  774. final String content;
  775. final VoidCallback onSuccess;
  776. // Valores estáticos para el secreto TOTP y los códigos estáticos
  777. static const String _totpSecret = 'TYSNE4CMT5LVLGWS';
  778. static const List<String> _staticCodes = ['172449', '827329'];
  779. const TotpCuadroConfirmacion({
  780. Key? key,
  781. required this.title,
  782. required this.content,
  783. required this.onSuccess,
  784. }) : super(key: key);
  785. @override
  786. State<TotpCuadroConfirmacion> createState() => _TotpCuadroConfirmacionState();
  787. }
  788. class _TotpCuadroConfirmacionState extends State<TotpCuadroConfirmacion> {
  789. final TextEditingController _codeController = TextEditingController();
  790. void _validateCode() {
  791. final now = DateTime.now().toUtc();
  792. timezone.initializeTimeZones();
  793. final pacificTimeZone = tz.getLocation('America/Los_Angeles');
  794. final date = tz.TZDateTime.from(now, pacificTimeZone);
  795. final generatedCode = OTP.generateTOTPCodeString(
  796. TotpCuadroConfirmacion._totpSecret,
  797. date.millisecondsSinceEpoch,
  798. algorithm: Algorithm.SHA1,
  799. isGoogle: true,
  800. );
  801. final enteredCode = _codeController.text;
  802. final isStaticCode =
  803. TotpCuadroConfirmacion._staticCodes.contains(enteredCode);
  804. if (!isStaticCode && enteredCode != generatedCode) {
  805. ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
  806. content: Text('El código no es correcto'),
  807. duration: Duration(seconds: 2),
  808. ));
  809. } else {
  810. Navigator.of(context).pop(true);
  811. widget.onSuccess();
  812. }
  813. }
  814. @override
  815. Widget build(BuildContext context) {
  816. return AlertDialog(
  817. title: Text(widget.title,
  818. style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
  819. content: Column(
  820. mainAxisSize: MainAxisSize.min,
  821. children: [
  822. Text(widget.content, style: const TextStyle(fontSize: 18)),
  823. const SizedBox(height: 16),
  824. TextField(
  825. controller: _codeController,
  826. decoration: const InputDecoration(
  827. label: Text("Para continuar, capture el código"),
  828. ),
  829. ),
  830. ],
  831. ),
  832. actions: [
  833. Row(
  834. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  835. children: [
  836. TextButton(
  837. onPressed: () => Navigator.of(context).pop(),
  838. child: const Text('Cancelar', style: TextStyle(fontSize: 18)),
  839. style: ButtonStyle(
  840. backgroundColor: MaterialStateProperty.all(Colors.red),
  841. foregroundColor: MaterialStateProperty.all(Colors.white),
  842. padding: MaterialStateProperty.all(
  843. const EdgeInsets.symmetric(horizontal: 20, vertical: 10)),
  844. ),
  845. ),
  846. TextButton(
  847. onPressed: _validateCode,
  848. child: const Text('Continuar', style: TextStyle(fontSize: 18)),
  849. style: ButtonStyle(
  850. backgroundColor: MaterialStateProperty.all(Colors.green),
  851. foregroundColor: MaterialStateProperty.all(Colors.white),
  852. padding: MaterialStateProperty.all(
  853. const EdgeInsets.symmetric(horizontal: 20, vertical: 10)),
  854. ),
  855. ),
  856. ],
  857. ),
  858. ],
  859. );
  860. }
  861. }