Implement local vault service with encryption key management and integrate it into the app. Add settings screen for data management and enhance home screen with new features. Update database connection for encryption support and modify repository to use the new database structure. Improve UI elements across the application for better user experience.

This commit is contained in:
2026-05-13 22:57:23 +02:00
parent 96f8f95924
commit 94fdfe51eb
16 changed files with 875 additions and 87 deletions
+204
View File
@@ -0,0 +1,204 @@
import 'package:flutter/material.dart';
import 'package:notas/widgets/app_title_bar.dart';
import 'package:notas/widgets/menu_drawer.dart';
import 'package:notas/widgets/search_app_bar.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({
super.key,
required this.onDeleteAllData,
});
final Future<void> Function() onDeleteAllData;
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool _isBusy = false;
bool _isMenuOpen = false;
final GlobalKey _headerKey = GlobalKey();
double _menuTopInset = 0;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateMenuTopInset();
});
}
void _updateMenuTopInset() {
final BuildContext? headerContext = _headerKey.currentContext;
if (headerContext == null) {
return;
}
final RenderObject? renderObject = headerContext.findRenderObject();
if (renderObject is! RenderBox) {
return;
}
final double newInset = renderObject.size.height;
if ((newInset - _menuTopInset).abs() < 0.5) {
return;
}
setState(() {
_menuTopInset = newInset;
});
}
Future<void> _confirmAndDeleteAll() async {
final bool? confirmed = await showDialog<bool>(
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. No se podrá recuperar.'),
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.')),
);
Navigator.of(context).pop();
} 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;
});
}
}
void _handleMenuItemTapped(String item) {
setState(() {
_isMenuOpen = false;
});
if (item == 'all_notes') {
Navigator.of(context).pop();
}
}
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateMenuTopInset();
});
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: Stack(
children: [
Column(
children: [
Column(
key: _headerKey,
mainAxisSize: MainAxisSize.min,
children: [
const AppTitleBar(),
SearchAppBar(
onMenuPressed: () {
setState(() {
_isMenuOpen = !_isMenuOpen;
});
},
showSearch: false,
titleText: 'Configuración',
),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
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 todos los datos'),
),
const SizedBox(height: 16),
const Text('Esto cerrará el vault actual y eliminará la base de datos local junto con la clave de cifrado.'),
],
),
),
),
],
),
Positioned.fill(
top: _menuTopInset,
child: IgnorePointer(
ignoring: !_isMenuOpen,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 300),
opacity: _isMenuOpen ? 0.5 : 0.0,
curve: Curves.easeOutCubic,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
_isMenuOpen = false;
});
},
child: Container(color: Colors.black),
),
),
),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
left: _isMenuOpen ? 0 : -280,
top: _menuTopInset,
bottom: 0,
width: 280,
child: Material(
color: const Color.fromRGBO(24, 25, 26, 1),
elevation: 8,
child: MenuDrawer(onMenuItemTapped: _handleMenuItemTapped),
),
),
],
),
),
),
);
}
}