diff --git a/lib/data/api_client.dart b/lib/data/api_client.dart index 557d38a..6883897 100644 --- a/lib/data/api_client.dart +++ b/lib/data/api_client.dart @@ -217,6 +217,35 @@ class AuthApi { } } + Future> deleteAllServerData({String? endpoint}) async { + final String? token = await accessToken; + if (token == null) { + return {'error': true, 'message': 'No access token available'}; + } + + final String base = endpoint ?? await ApiConfig.getEndpoint(); + final Uri url = Uri.parse('$base/auth/delete-all-data'); + + final http.Response res = await http.post( + url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (res.statusCode >= 200 && res.statusCode < 300) { + return {'error': false, 'status': res.statusCode, 'body': res.body}; + } + + try { + final dynamic decoded = jsonDecode(res.body); + return {'error': true, 'status': res.statusCode, 'body': decoded}; + } catch (_) { + return {'error': true, 'status': res.statusCode, 'body': res.body}; + } + } + List _randomBytes(int length) { final Random random = Random.secure(); return List.generate(length, (_) => random.nextInt(256)); diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 93af161..4ff47cc 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -26,6 +26,7 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { bool _isBusy = false; bool _isSyncing = false; + bool _isServerDeleting = false; bool _isThemeSaving = false; final TextEditingController _endpointController = TextEditingController(); final TextEditingController _encryptionKeyController = TextEditingController(); @@ -83,6 +84,64 @@ class _SettingsScreenState extends State { } } + 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; @@ -219,6 +278,30 @@ class _SettingsScreenState extends State { }); } + 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 = @@ -382,21 +465,29 @@ class _SettingsScreenState extends State { 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'), + _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, ), ], ), @@ -407,7 +498,9 @@ class _SettingsScreenState extends State { child: Text('Forzar sincronizacion total:'), ), ElevatedButton.icon( - onPressed: (_isBusy || _isSyncing) ? null : _forceSync, + onPressed: (_isBusy || _isSyncing || _isServerDeleting) + ? null + : _forceSync, icon: _isSyncing ? const SizedBox( width: 16,