import 'package:enter_farma_plus_mobile/src/core/printing/bluetooth_print_service.dart'; import 'package:enter_farma_plus_mobile/src/core/storage/tenant_session_store.dart'; import 'package:enter_farma_plus_mobile/src/core/theme/app_tokens.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; /// Storage key for the saved printer MAC address. const _printerMacKey = 'printer_mac'; /// Storage key for the saved printer display name. const _printerNameKey = 'printer_name'; /// Shows the printer settings as a modal bottom sheet. /// /// Usage: /// ```dart /// showPrinterSettings(context, ref); /// ``` Future showPrinterSettings(BuildContext context, WidgetRef ref) { return showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => const _PrinterSettingsSheet(), ); } // --------------------------------------------------------------------------- // Sheet widget // --------------------------------------------------------------------------- class _PrinterSettingsSheet extends ConsumerStatefulWidget { const _PrinterSettingsSheet(); @override ConsumerState<_PrinterSettingsSheet> createState() => _PrinterSettingsSheetState(); } class _PrinterSettingsSheetState extends ConsumerState<_PrinterSettingsSheet> { List? _devices; bool _scanning = false; bool _connecting = false; bool _printing = false; String? _connectedMac; String? _connectedName; String? _error; @override void initState() { super.initState(); _loadSaved(); } // --------------------------------------------------------------------------- // Persistence helpers // --------------------------------------------------------------------------- Future _loadSaved() async { final storage = ref.read(secureStorageProvider); final mac = await storage.read(key: _printerMacKey); final name = await storage.read(key: _printerNameKey); if (mac != null && mac.isNotEmpty) { final printService = ref.read(bluetoothPrintServiceProvider); final live = await printService.refreshConnectionStatus(); if (mounted) { setState(() { _connectedMac = mac; _connectedName = name; if (!live) { // Stored but not currently connected -- clear status indicator. _connectedMac = mac; } }); } } } Future _savePrinter(String mac, String name) async { final storage = ref.read(secureStorageProvider); await storage.write(key: _printerMacKey, value: mac); await storage.write(key: _printerNameKey, value: name); } Future _clearPrinter() async { final storage = ref.read(secureStorageProvider); await storage.delete(key: _printerMacKey); await storage.delete(key: _printerNameKey); } // --------------------------------------------------------------------------- // Actions // --------------------------------------------------------------------------- Future _scan() async { setState(() { _scanning = true; _error = null; }); try { final printService = ref.read(bluetoothPrintServiceProvider); final devices = await printService.scanDevices(); if (mounted) { setState(() { _devices = devices; _scanning = false; if (devices.isEmpty) { _error = 'No se encontraron dispositivos vinculados.\n' 'Vincula la impresora desde los ajustes de Bluetooth del sistema.'; } }); } } catch (e) { if (mounted) { setState(() { _scanning = false; _error = 'Error al buscar dispositivos: $e'; }); } } } Future _connectTo(BluetoothInfo device) async { setState(() { _connecting = true; _error = null; }); try { final printService = ref.read(bluetoothPrintServiceProvider); final ok = await printService.connect(device.macAdress); if (!mounted) return; if (ok) { await _savePrinter(device.macAdress, device.name); setState(() { _connectedMac = device.macAdress; _connectedName = device.name; _connecting = false; }); } else { setState(() { _connecting = false; _error = 'No se pudo conectar a ${device.name}.'; }); } } catch (e) { if (mounted) { setState(() { _connecting = false; _error = 'Error al conectar: $e'; }); } } } Future _disconnect() async { final printService = ref.read(bluetoothPrintServiceProvider); await printService.disconnect(); await _clearPrinter(); if (mounted) { setState(() { _connectedMac = null; _connectedName = null; }); } } Future _testPrint() async { setState(() { _printing = true; _error = null; }); try { final printService = ref.read(bluetoothPrintServiceProvider); await printService.printTest(); if (mounted) { setState(() => _printing = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Impresión de prueba enviada.')), ); } } catch (e) { if (mounted) { setState(() { _printing = false; _error = 'Error al imprimir: $e'; }); } } } // --------------------------------------------------------------------------- // Build // --------------------------------------------------------------------------- @override Widget build(BuildContext context) { final theme = Theme.of(context); final hasConnection = _connectedMac != null && _connectedMac!.isNotEmpty; return SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Drag handle Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: AppTokens.border, borderRadius: BorderRadius.circular(2), ), ), ), // Title row Row( children: [ CircleAvatar( backgroundColor: AppTokens.cta.withValues(alpha:0.12), foregroundColor: AppTokens.cta, child: const Icon(Icons.print_rounded), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Impresora Bluetooth', style: theme.textTheme.headlineSmall, ), const SizedBox(height: 4), Text( hasConnection ? 'Conectada: ${_connectedName ?? _connectedMac}' : 'Sin impresora conectada', style: theme.textTheme.bodyMedium?.copyWith( color: hasConnection ? AppTokens.success : AppTokens.secondary, ), ), ], ), ), ], ), const SizedBox(height: 20), // Error banner if (_error != null) ...[ Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTokens.danger.withValues(alpha:0.08), borderRadius: BorderRadius.circular(AppTokens.radiusSmall), border: Border.all(color: AppTokens.danger.withValues(alpha:0.3)), ), child: Text( _error!, style: theme.textTheme.bodyMedium?.copyWith( color: AppTokens.danger, ), ), ), const SizedBox(height: 16), ], // Connected printer card if (hasConnection) ...[ Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon( Icons.bluetooth_connected_rounded, color: AppTokens.success, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _connectedName ?? 'Impresora', style: theme.textTheme.titleMedium, ), const SizedBox(height: 2), Text( _connectedMac!, style: theme.textTheme.bodySmall?.copyWith( color: AppTokens.secondary, ), ), ], ), ), IconButton( onPressed: _disconnect, icon: const Icon(Icons.link_off_rounded), tooltip: 'Desconectar', color: AppTokens.danger, ), ], ), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _printing ? null : _testPrint, icon: _printing ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.receipt_long_rounded), label: Text(_printing ? 'Imprimiendo...' : 'Imprimir prueba'), ), ), const SizedBox(height: 16), const Divider(), const SizedBox(height: 12), ], // Scan button SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _scanning ? null : _scan, icon: _scanning ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Icon(Icons.bluetooth_searching_rounded), label: Text( _scanning ? 'Buscando...' : 'Buscar dispositivos', ), ), ), const SizedBox(height: 16), // Device list if (_devices != null && _devices!.isNotEmpty) ...[ Text( 'Dispositivos vinculados', style: theme.textTheme.titleMedium, ), const SizedBox(height: 8), ...List.generate(_devices!.length, (i) { final device = _devices![i]; final isCurrentlyConnected = device.macAdress == _connectedMac; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: Icon( isCurrentlyConnected ? Icons.bluetooth_connected_rounded : Icons.bluetooth_rounded, color: isCurrentlyConnected ? AppTokens.success : AppTokens.secondary, ), title: Text(device.name), subtitle: Text( device.macAdress, style: theme.textTheme.bodySmall?.copyWith( color: AppTokens.secondary, ), ), trailing: isCurrentlyConnected ? Chip( label: const Text('Conectada'), backgroundColor: AppTokens.success.withValues(alpha:0.12), labelStyle: const TextStyle( color: AppTokens.success, fontSize: 12, ), ) : _connecting ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, ), ) : const Icon( Icons.arrow_forward_ios_rounded, size: 16, color: AppTokens.secondary, ), onTap: (isCurrentlyConnected || _connecting) ? null : () => _connectTo(device), ), ); }), ], ], ), ), ); } }