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, required this.currentSeedColor, required this.onThemeColorSelected, }); final Future Function() onDeleteAllData; final VoidCallback onBackToHome; final Future Function() onForceSync; final Color currentSeedColor; final Future Function(Color color) onThemeColorSelected; @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { bool _isBusy = false; bool _isSyncing = false; bool _isServerDeleting = false; bool _isThemeSaving = false; final TextEditingController _endpointController = TextEditingController(); final TextEditingController _encryptionKeyController = TextEditingController(); bool _endpointLoading = true; bool _encryptionKeyLoading = false; bool _encryptionKeyVisible = false; late Color _selectedSeedColor; static const List _themeColorOptions = [ Colors.amber, Colors.blue, Colors.teal, Colors.green, Colors.pink, Colors.purple, ]; 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 _confirmAndDeleteServerData() async { final bool? confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Borrar toda la info del servidor'), content: const Text( '¿Estás seguro? Esta acción eliminará toda la información almacenada en el servidor y no se puede deshacer.', ), 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(() { _isServerDeleting = true; }); try { final Map response = await AuthApi.instance.deleteAllServerData(); if (response['error'] == true) { throw Exception(response['body'] ?? response['message'] ?? 'Error desconocido'); } await AuthApi.instance.clearTokens(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Toda la información del servidor ha sido eliminada.')), ); } catch (error) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error al borrar la info del servidor: $error')), ); } finally { if (!mounted) return; setState(() { _isServerDeleting = 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; }); } } Future _selectThemeColor(Color color) async { if (_isThemeSaving || _selectedSeedColor == color) { return; } final Color previousColor = _selectedSeedColor; setState(() { _selectedSeedColor = color; _isThemeSaving = true; }); try { await widget.onThemeColorSelected(color); } catch (error) { if (!mounted) { return; } setState(() { _selectedSeedColor = previousColor; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('No se pudo guardar el color: $error')), ); } finally { if (mounted) { setState(() { _isThemeSaving = false; }); } } } @override void initState() { super.initState(); _selectedSeedColor = widget.currentSeedColor; _loadEndpoint(); } @override void didUpdateWidget(covariant SettingsScreen oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.currentSeedColor != widget.currentSeedColor && widget.currentSeedColor != _selectedSeedColor) { _selectedSeedColor = widget.currentSeedColor; } } 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(); }); } Widget _buildDestructiveButton({ required String label, required VoidCallback? onPressed, required bool isLoading, required IconData icon, }) { return ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.redAccent, foregroundColor: Colors.white, textStyle: const TextStyle(fontWeight: FontWeight.w600), ), onPressed: onPressed, icon: isLoading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : Icon(icon), label: Text(label), ); } Widget _buildThemeColorButton(Color color) { final bool isSelected = _selectedSeedColor.value == color.value; final Color foregroundColor = ThemeData.estimateBrightnessForColor(color) == Brightness.dark ? Colors.white : Colors.black; return Semantics( button: true, selected: isSelected, label: 'Color ${color.value.toRadixString(16)}', child: Tooltip( message: isSelected ? 'Color actual' : 'Usar este color', child: InkWell( onTap: _isThemeSaving ? null : () => _selectThemeColor(color), borderRadius: BorderRadius.circular(12), child: AnimatedContainer( duration: const Duration(milliseconds: 180), width: 44, height: 44, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(12), border: Border.all( color: isSelected ? Colors.white : Colors.white24, width: isSelected ? 2.5 : 1.2, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.25), blurRadius: 8, offset: const Offset(0, 3), ), ], ), child: Stack( alignment: Alignment.center, children: [ if (isSelected) Icon( Icons.check, size: 22, color: foregroundColor, ), ], ), ), ), ), ); } @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:'), ), _buildDestructiveButton( label: 'Borrar', onPressed: (_isBusy || _isServerDeleting) ? null : _confirmAndDeleteAll, isLoading: _isBusy, icon: Icons.delete_forever, ), ], ), const SizedBox(height: 16), Row( children: [ const Expanded( child: Text('Borrar info del servidor:'), ), _buildDestructiveButton( label: 'Borrar', onPressed: (_isBusy || _isSyncing || _isServerDeleting) ? null : _confirmAndDeleteServerData, isLoading: _isServerDeleting, icon: Icons.cloud_off, ), ], ), const SizedBox(height: 16), Row( children: [ const Expanded( child: Text('Forzar sincronizacion total:'), ), ElevatedButton.icon( onPressed: (_isBusy || _isSyncing || _isServerDeleting) ? 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('Color del esquema'), const SizedBox(height: 8), Wrap( spacing: 12, runSpacing: 12, children: [ for (final Color color in _themeColorOptions) _buildThemeColorButton(color), ], ), 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'), ), ], ), ], ), ), ), ], ), ), ), ); } }