import 'package:flutter/material.dart'; import 'package:notas/data/local_vault_service.dart'; import 'package:notas/widgets/search_app_bar.dart'; import 'package:notas/data/api_client.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({ super.key, required this.onDeleteAllData, required this.onBackToHome, required this.onForceSync, }); final Future Function() onDeleteAllData; final VoidCallback onBackToHome; final Future Function() onForceSync; @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { bool _isBusy = false; bool _isSyncing = false; final TextEditingController _endpointController = TextEditingController(); final TextEditingController _encryptionKeyController = TextEditingController(); bool _endpointLoading = true; bool _encryptionKeyLoading = false; bool _encryptionKeyVisible = false; Future _confirmAndDeleteAll() async { final bool? confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Borrar todos los datos'), content: const Text('¿Estás seguro? Esta acción eliminará la base de datos local y la clave de cifrado. Asegúrate de tener una copia de seguridad si es necesario o cuenta sincronizada.'), actions: [ TextButton(onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancelar')), TextButton(onPressed: () => Navigator.of(context).pop(true), child: const Text('Borrar', style: TextStyle(color: Colors.red))), ], ), ); if (confirmed != true) return; setState(() { _isBusy = true; }); try { await widget.onDeleteAllData(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Todos los datos locales han sido eliminados.')), ); } catch (error) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error al borrar los datos: $error')), ); } finally { if (!mounted) return; setState(() { _isBusy = false; }); } } Future _forceSync() async { if (_isBusy || _isSyncing) { return; } setState(() { _isSyncing = true; }); try { await widget.onForceSync(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Sincronización forzada completada.')), ); } catch (error) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error al forzar la sincronización: $error')), ); } finally { if (!mounted) return; setState(() { _isSyncing = false; }); } } @override void initState() { super.initState(); _loadEndpoint(); } Future _loadEndpoint() async { final String endpoint = await ApiConfig.getEndpoint(); if (!mounted) return; _endpointController.text = endpoint; setState(() { _endpointLoading = false; }); } Future _loadEncryptionKey() async { setState(() { _encryptionKeyLoading = true; }); try { final String? encryptionKey = await LocalVaultService.instance.readEncryptionKey(); if (!mounted) return; if (encryptionKey == null || encryptionKey.isEmpty) { _encryptionKeyController.text = ''; _encryptionKeyVisible = false; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No se pudo leer la clave de cifrado.')), ); return; } _encryptionKeyController.text = encryptionKey; _encryptionKeyVisible = true; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Clave de cifrado mostrada.')), ); } catch (error) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error al leer la clave de cifrado: $error')), ); } finally { if (mounted) { setState(() { _encryptionKeyLoading = false; }); } } } void _hideEncryptionKey() { setState(() { _encryptionKeyVisible = false; _encryptionKeyController.clear(); }); } @override void dispose() { _endpointController.dispose(); _encryptionKeyController.dispose(); super.dispose(); } Future _saveEndpoint() async { final String value = _endpointController.text.trim(); try { await ApiConfig.setEndpoint(value); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Endpoint guardado'))); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error guardando endpoint: $e'))); } } Future _resetEndpoint() async { await ApiConfig.clearEndpoint(); final String endpoint = await ApiConfig.getEndpoint(); if (!mounted) return; _endpointController.text = endpoint; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Endpoint restaurado al valor por defecto'))); } Widget _buildResponsiveInputActionsRow({ required Widget input, required List actions, }) { return LayoutBuilder( builder: (context, constraints) { final bool isCompact = constraints.maxWidth < 600; if (isCompact) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox(width: double.infinity, child: input), const SizedBox(height: 8), Align( alignment: Alignment.centerRight, child: Wrap( alignment: WrapAlignment.end, spacing: 8, runSpacing: 8, children: actions, ), ), ], ); } return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded(child: input), const SizedBox(width: 8), Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ for (int index = 0; index < actions.length; index++) ...[ if (index > 0) const SizedBox(width: 8), actions[index], ], ], ), ), ], ); }, ); } Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [ Color(0xFF191A1D), Color(0xFF222326), Color(0xFF101114), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: SafeArea( child: Column( children: [ SearchAppBar( onLeadingPressed: widget.onBackToHome, leadingIcon: Icons.arrow_back, leadingTooltip: 'Atrás', showSearch: false, titleText: 'Configuración', ), Expanded( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Expanded( child: Text('Borrar datos locales:'), ), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.redAccent, foregroundColor: Colors.white, textStyle: const TextStyle(fontWeight: FontWeight.w600), ), onPressed: _isBusy ? null : _confirmAndDeleteAll, icon: _isBusy ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.delete_forever), label: const Text('Borrar'), ), ], ), const SizedBox(height: 16), Row( children: [ const Expanded( child: Text('Forzar sincronizacion total:'), ), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.amber, foregroundColor: Colors.black, textStyle: const TextStyle(fontWeight: FontWeight.w700), ), onPressed: (_isBusy || _isSyncing) ? null : _forceSync, icon: _isSyncing ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sync), label: const Text('Sincronizar'), ), ], ), const SizedBox(height: 24), const Text('API endpoint (ej: http://localhost:3000/api)'), const SizedBox(height: 8), _buildResponsiveInputActionsRow( input: _endpointLoading ? const SizedBox(height: 48, child: Center(child: CircularProgressIndicator())) : TextField( controller: _endpointController, style: const TextStyle(color: Colors.white), keyboardType: TextInputType.url, decoration: InputDecoration( labelText: 'API endpoint', labelStyle: TextStyle(color: Colors.white.withValues(alpha: 0.7)), filled: true, fillColor: Colors.white.withValues(alpha: 0.05), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.12)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.12)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Colors.amber, width: 1.2), ), ), ), actions: [ ElevatedButton( onPressed: _endpointLoading ? null : _saveEndpoint, child: const Text('Guardar'), ), OutlinedButton( onPressed: _endpointLoading ? null : _resetEndpoint, child: const Text('Restaurar'), ), ], ), const SizedBox(height: 24), const Text('Clave de cifrado local'), const SizedBox(height: 8), _buildResponsiveInputActionsRow( input: TextField( controller: _encryptionKeyController, readOnly: true, obscureText: !_encryptionKeyVisible, enableSuggestions: false, autocorrect: false, style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: _encryptionKeyVisible ? 'Clave de cifrado' : 'Oculta hasta pulsar mostrar', labelStyle: TextStyle(color: Colors.white.withValues(alpha: 0.7)), filled: true, fillColor: Colors.white.withValues(alpha: 0.05), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.12)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.12)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Colors.amber, width: 1.2), ), ), ), actions: [ ElevatedButton( onPressed: _encryptionKeyLoading ? null : _loadEncryptionKey, child: _encryptionKeyLoading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Mostrar'), ), OutlinedButton( onPressed: _encryptionKeyVisible ? _hideEncryptionKey : null, child: const Text('Ocultar'), ), ], ), ], ), ), ), ], ), ), ), ); } }