// ignore_for_file: use_build_context_synchronously, prefer_if_null_operators, prefer_conditional_assignment, must_be_immutable, deprecated_member_use import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:omni_datetime_picker/omni_datetime_picker.dart'; import 'package:flutter/material.dart' as d; import 'package:datetime_picker_formfield/datetime_picker_formfield.dart' as d2; import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../data/session/session_storage.dart'; import '../models/media_model.dart'; import '../services/base_service.dart'; import '../themes/themes.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:http/http.dart' as http; import "package:universal_html/html.dart" as html; //import 'package:syncfusion_flutter_xlsio/xlsio.dart' as sio; import 'package:flutter/foundation.dart' show kIsWeb; encabezado( {double elevacion = 1, TextStyle? estilo, List? acciones, PreferredSize? bottom, Color? backgroundColor, String? nombre, Widget? leading = null, String? titulo, bool centerTitle = true, bool withDrawer = false}) { if (acciones == null) { acciones = []; } if (BaseService().baseUrl.contains("test") || BaseService().base_url.contains("test")) { // backgroundColor = Colors.grey.shade500; } return AppBar( leading: leading, elevation: elevacion, backgroundColor: backgroundColor, centerTitle: centerTitle, titleTextStyle: estilo, title: titulo == null ? Image.asset("assets/logo.png", width: 100) : Text(titulo.toString(), style: estilo), actions: [ ...acciones, // Image.asset("assets/logo.png", width: 100), ], bottom: bottom); } tarjeta(Widget item, {Color color = Colors.white, double padding = 0}) { if (padding > 0) { return Padding( padding: const EdgeInsets.all(0), child: Card( color: color, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), margin: const EdgeInsets.only(bottom: 16), elevation: 0, borderOnForeground: true, child: Padding(padding: EdgeInsets.all(padding), child: item))); } return Card( color: color, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), margin: const EdgeInsets.only(bottom: 8), elevation: 0, borderOnForeground: true, child: Padding( padding: const EdgeInsets.all(8), child: item, )); } class Cargando extends StatelessWidget { const Cargando({super.key}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFffe000), body: Center( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 10), child: Image.asset("assets/edesarrollos_logo.png", height: 200)), const CircularProgressIndicator(backgroundColor: Colors.grey), Container(margin: const EdgeInsets.only(bottom: 8.0)), const Text("Cargando contenido...", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Colors.black)), const Text("Por favor espere.", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: Colors.black), textAlign: TextAlign.center) ]), )); } } class CargandoBarra extends StatelessWidget { const CargandoBarra({super.key}); @override Widget build(BuildContext context) { return const Center( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 8), LinearProgressIndicator( //value: controller.value, semanticsLabel: 'Cargando...', ), const SizedBox(height: 8), ]), ); } } class SinDatos extends StatelessWidget { final String message; final IconData icon; const SinDatos({ Key? key, this.message = 'No hay datos', this.icon = Icons.inbox, }) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 80, color: Colors.grey), Text(message, style: TextStyle(fontSize: 24, color: Colors.grey)), ], ), ); } } boton(String etiqueta, Function()? accion, {double height = 75, double width = 380}) { return Align( alignment: Alignment.topLeft, child: SizedBox( height: height, width: 380, child: ElevatedButton( style: ButtonStyle( shape: MaterialStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), backgroundColor: MaterialStatePropertyAll(AppTheme.primary), ), onPressed: accion, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(etiqueta, style: TextStyle(fontSize: 18, color: AppTheme.secondary)), ], ), ), ), ); } Future showHora(BuildContext context, DateTime? fecha) async { if (fecha == null) { return fecha; } final tiempo = await showTimePicker( useRootNavigator: false, helpText: "CAPTURAR HORA", confirmText: "ACEPTAR", cancelText: "CANCELAR", hourLabelText: "HORA", minuteLabelText: "MINUTO", initialEntryMode: TimePickerEntryMode.input, context: context, initialTime: TimeOfDay.fromDateTime(fecha), builder: (context, childs) { return MediaQuery( data: MediaQuery.of(context).copyWith( alwaysUse24HourFormat: true, padding: const EdgeInsets.all(1), size: const Size.square(1), textScaler: const TextScaler.linear(.8)), child: childs!); }, ); return d2.DateTimeField.combine(fecha!, tiempo); } Future showDatetimePicker(BuildContext context, DateTime? fecha, {DateTime? inicia, DateTime? termina, OmniDateTimePickerType tipo = OmniDateTimePickerType.dateAndTime, bool solofecha = false}) async { if (termina == null) { termina = DateTime(2030); } final f = await d.showDatePicker( context: context, initialDate: fecha != null ? DateTime.now() : fecha, firstDate: inicia == null ? DateTime(2000) : inicia, // DateTime(2023), lastDate: termina, cancelText: "CANCELAR", confirmText: "ACEPTAR", ); if (f == null) { return fecha; } if (solofecha) { return f; } final tiempo = await showTimePicker( useRootNavigator: false, helpText: "CAPTURAR HORA", confirmText: "ACEPTAR", cancelText: "CANCELAR", hourLabelText: "HORA", minuteLabelText: "MINUTO", initialEntryMode: TimePickerEntryMode.input, context: context, initialTime: TimeOfDay.fromDateTime(f), builder: (context, childs) { return MediaQuery( data: MediaQuery.of(context).copyWith( alwaysUse24HourFormat: true, padding: const EdgeInsets.all(1), size: const Size.square(1), textScaler: const TextScaler.linear(.8)), child: childs!); }, ); return d2.DateTimeField.combine(f, tiempo); } class FechaSelectWidget extends StatelessWidget { final DateTime? fecha; final Function(DateTime) onFechaChanged; final String etiqueta; final BuildContext context; const FechaSelectWidget({ Key? key, required this.fecha, required this.onFechaChanged, required this.etiqueta, required this.context, }) : super(key: key); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( etiqueta, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 5), InkWell( onTap: () async { DateTime? d = await showDatetimePicker( context, fecha, tipo: OmniDateTimePickerType.date, solofecha: true, ); if (d == null) return; onFechaChanged(d); }, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey[400]!), borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( fecha == null ? "Seleccionar" : DateFormat("dd/MM/yyyy").format(fecha!), style: TextStyle( color: fecha == null ? Colors.grey : Colors.black, ), ), const Icon(Icons.calendar_month), ], ), ), ), ], ); } } Widget circulo(Widget item, {Function()? accion, Color? color}) { color ??= Colors.black; return InkWell( onTap: accion, child: Container( decoration: BoxDecoration( border: Border.all( color: color, // <--- border color width: 2.0, ), borderRadius: const BorderRadius.all( Radius.circular(50) // <--- border radius here ), ), child: item)); } alerta(BuildContext context, {String? etiqueta = "Capturar búsqueda"}) { return showDialog( context: context, builder: (context) { return AlertDialog( title: Text(etiqueta.toString()), actions: [ Row(children: [ Expanded( child: TextButton( style: ButtonStyle( backgroundColor: MaterialStatePropertyAll(Colors.black)), onPressed: () async { Navigator.pop(context); }, child: const Text( 'Regresar', style: TextStyle(color: Colors.white, fontSize: 16), ), )) ]) ], ); }); } botonElevated( {Function()? accion, String? titulo = "Buscar", Color color = Colors.black}) { return ElevatedButton( style: ElevatedButton.styleFrom( minimumSize: Size(0, 65), backgroundColor: color, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), )), onPressed: accion, child: Text( titulo.toString(), style: const TextStyle(color: Colors.white), ), ); } Widget xMedia(XFile m, {String? nombrePersonalizado}) { bool archivo = false; Widget icono = const Icon(Icons.file_copy, size: 50); int lastIndex = m.name.lastIndexOf('.'); String extension = ""; if (lastIndex != -1 && lastIndex < m.name.length - 1) { extension = m.name.substring(lastIndex + 1); } switch (extension) { case "mp4": archivo = true; icono = const Icon(Icons.video_file, size: 50); break; case "wav": archivo = true; icono = Image.asset("assets/audio-file.png"); archivo = true; break; case "jpeg": case "jpg": case "png": break; case "pdf": icono = Image.asset("assets/pdf-file.png"); archivo = true; break; case "csv": case "docx": icono = Image.asset("assets/word.png"); archivo = true; break; case "xlsx": icono = Image.asset("assets/excel.png"); archivo = true; break; } // Usar el nombre personalizado si está disponible, de lo contrario usar el nombre del archivo String nombre = nombrePersonalizado ?? m.name.toString(); if (nombre.length > 25) { nombre = "${nombre.substring(0, 24)}..."; } return InkWell( onTap: () async { String url = m.path.toString(); await canLaunch(url) ? await launch(url, forceSafariVC: false, forceWebView: false) : print('Could not launch $url'); }, child: Stack(alignment: Alignment.center, children: [ Container( height: 100.0, width: 100.0, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 1.0), borderRadius: const BorderRadius.all(Radius.circular(15.0)), ), child: archivo ? icono : Image.network(m.path.toString(), scale: 1), ), Positioned( bottom: 0, child: Container( width: 200, height: 40, color: Colors.grey.shade800, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( nombre, textAlign: TextAlign.center, textScaler: const TextScaler.linear(.8), style: const TextStyle(color: Colors.white), ), ], ), ), ), ]), ); } Widget wMedia(Media m, {Function()? eliminar, bool mostrarEliminar = true, bool preview = true, Function()? onTapFunction}) { bool archivo = false; Widget icono = const Icon(Icons.file_copy, size: 50); Uint8List? imageBytes; print( "Mostrando media: ${m.nombre}, Ruta: ${m.ruta}, Extensión: ${m.extension}"); switch (m.extension) { case "mp4": archivo = true; icono = const Icon(Icons.video_file, size: 50); break; case "wav": archivo = true; icono = Image.asset("assets/audio-file.png"); break; case "txt": archivo = true; break; case "jpeg": case "jpg": case "png": break; case "pdf": icono = Image.asset("assets/pdf-file.png"); archivo = true; break; case "csv": case "docx": icono = Image.asset("assets/word.png"); archivo = true; break; case "xlsx": icono = Image.asset("assets/excel.png"); archivo = true; break; } String fecha = ""; if (m.creado != null) { String timeZone = 'America/Hermosillo'; fecha = formatFechaConZonaHoraria(m.creado!, timeZone); } String nombre = m.nombre.toString(); if (nombre.length > 25) { nombre = "${m.nombre.toString().substring(0, 24)}..."; } return InkWell( onTap: () async { if (preview) { if (m.ruta != null && m.ruta!.startsWith("blob")) { print("Intentando mostrar un blob..."); // Lógica para previsualizar blobs } else if (m.ruta != null) { String url = m.ruta.toString(); print("Intentando abrir la URL: $url"); await canLaunch(url) ? await launch(url, forceSafariVC: false, forceWebView: false) : print('No se pudo lanzar la URL'); } else { print("Ruta nula, no se puede abrir."); } } else { if (onTapFunction != null) { onTapFunction(); } } }, child: Stack(alignment: Alignment.center, children: [ Container( height: 160.0, width: 160.0, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 1.0), borderRadius: const BorderRadius.all(Radius.circular(15.0)), ), child: archivo ? icono : (m.ruta != null && m.ruta!.startsWith("blob") && imageBytes != null ? Image.memory(imageBytes!) : m.ruta != null ? Image.network(m.ruta.toString(), scale: 1) : const Text("No hay imagen disponible"))), Positioned( bottom: 0, child: Container( width: 500, height: 40, color: Colors.grey.shade800, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( nombre, textAlign: TextAlign.center, textScaler: const TextScaler.linear(.8), style: const TextStyle(color: Colors.white), ), Text( fecha, textAlign: TextAlign.center, textScaler: const TextScaler.linear(.8), style: const TextStyle(color: Colors.white), ), ]), )), if (mostrarEliminar) Positioned( left: 0, top: 0, child: ElevatedButton( onPressed: eliminar, style: ElevatedButton.styleFrom( shape: const CircleBorder(), padding: const EdgeInsets.all(16), ), child: Icon( Icons.delete, color: Colors.red.shade800, ), ), ), ])); } Widget agregarMedia( {Color color = Colors.green, Function()? accion, String titulo = "Agregar Media", Icon? icono, double tamano = 160, bool tamanoPequeno = false}) { double iconSize = tamanoPequeno ? 30 : 100; double containerSize = tamanoPequeno ? 100 : tamano; if (icono == null) { icono = Icon( Icons.add, size: iconSize, color: Colors.white, ); } return InkWell( onTap: accion, child: Stack(alignment: Alignment.center, children: [ Container( width: containerSize, height: containerSize, color: color, ), Positioned( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ icono, titulo.isEmpty ? Container() : Text( titulo, style: TextStyle( color: Colors.white, fontSize: tamanoPequeno ? 12 : 16, ), ), ])), ])); } String formatFechaConZonaHoraria(DateTime dateTime, String timeZone) { // Obtiene la ubicación de la zona horaria tz.Location location = tz.getLocation(timeZone); // Convierte la fecha a la zona horaria deseada tz.TZDateTime fechaConZona = tz.TZDateTime.from(dateTime, location); // Formatea la fecha con la zona horaria //String formattedFecha = DateFormat("dd/MM/yyyy HH:mm '($timeZone)'").format(fechaConZona); String formattedFecha = DateFormat("dd/MM/yyyy HH:mm").format(fechaConZona); return formattedFecha; } html.Location getLocation(String timeZone) { // Obtiene la ubicación de la zona horaria return getLocation(timeZone); } int obtenerAxiscount(Size s) { int axiscount = 8; if (s.width > 1300) { axiscount = 8; } if (s.width > 900 && s.width < 1300) { axiscount = 5; } if (s.width > 700 && s.width < 900) { axiscount = 4; } if (s.width > 600 && s.width < 700) { axiscount = 3; } if (s.width < 600) { axiscount = 2; } return axiscount; } imprimirExcel(String url, String nombre) async { var t = await SessionStorage().getToken(); Map headers = { 'Authorization': 'Bearer $t', 'Content-Type': 'application/json', // Puedes ajustar según sea necesario }; http.Response response = await http.get( Uri.parse(url), headers: headers, ); if (response.statusCode == 200) { // Obtener el contenido del archivo como Uint8List Uint8List fileBytes = response.bodyBytes; if (!kIsWeb) { // Lógica para dispositivos móviles y escritorio final directory = await getApplicationDocumentsDirectory(); final path = '${directory.path}/$nombre.xlsx'; final file = File(path); await file.writeAsBytes(fileBytes); final Uri fileUri = Uri.file(path); if (await canLaunchUrl(fileUri)) { await launchUrl(fileUri); } else { throw 'No se pudo abrir el archivo'; } return; // Retorna después de manejar la lógica de dispositivos móviles } // Lógica específica para la web sigue aquí // Asegúrate de que este código solo se ejecute en la web if (kIsWeb) { // Crea y descarga el archivo para la web final blob = html.Blob([fileBytes]); final url = html.Url.createObjectUrlFromBlob(blob); final anchor = html.AnchorElement(href: url) ..setAttribute('download', '$nombre.xlsx') ..click(); html.Url.revokeObjectUrl(url); } } else { print( 'Error al descargar el archivo. Código de estado: ${response.statusCode}'); } } Widget usuarioHeader(String nombre, String correo) { return Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(nombre, style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold), textAlign: TextAlign.right), Text(correo, style: TextStyle(color: Colors.grey.shade700), textAlign: TextAlign.right), ]), Text(" "), CircleAvatar( child: Icon(Icons.verified_user, color: Colors.white), backgroundColor: Colors.black), Text(" "), ]); } Future eliminarMedia( BuildContext context, Media m, Function()? confirmacion) async { return await showDialog( context: context, builder: (context) { return AlertDialog( surfaceTintColor: AppTheme.secondary, title: const Text('¿Desea eliminar este archivo?'), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Cancelar'), ), TextButton( onPressed: confirmacion, child: const Text( 'Sí, estoy seguro', style: TextStyle(color: Colors.red), ), ), ], ); }, ); }