Enhance navigation and structure: integrate SettingsScreen into main app flow, update HomeScreen to open settings, and improve UI elements for better user experience.

This commit is contained in:
2026-05-14 16:52:39 +02:00
parent ca8399dbc9
commit 91a26f6c7b
4 changed files with 274 additions and 207 deletions
+92 -6
View File
@@ -7,6 +7,7 @@ import 'package:notas/data/note_repository.dart';
import 'package:notas/platform/app_platform.dart'; import 'package:notas/platform/app_platform.dart';
import 'package:notas/platform/window_state.dart'; import 'package:notas/platform/window_state.dart';
import 'package:notas/screens/home_screen.dart'; import 'package:notas/screens/home_screen.dart';
import 'package:notas/screens/settings_screen.dart';
import 'package:notas/screens/vault_access_screen.dart'; import 'package:notas/screens/vault_access_screen.dart';
import 'package:notas/theme/app_theme.dart'; import 'package:notas/theme/app_theme.dart';
import 'package:notas/widgets/app_title_bar.dart'; import 'package:notas/widgets/app_title_bar.dart';
@@ -14,6 +15,11 @@ import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
enum _AppSection {
home,
settings,
}
class NotesApp extends StatefulWidget { class NotesApp extends StatefulWidget {
const NotesApp({super.key}); const NotesApp({super.key});
@@ -22,6 +28,8 @@ class NotesApp extends StatefulWidget {
} }
class _NotesAppState extends State<NotesApp> with WindowListener { class _NotesAppState extends State<NotesApp> with WindowListener {
static const Duration _screenTransitionDuration = Duration(milliseconds: 280);
final LocalVaultService _vaultService = LocalVaultService.instance; final LocalVaultService _vaultService = LocalVaultService.instance;
final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>(); GlobalKey<ScaffoldMessengerState>();
@@ -30,6 +38,7 @@ class _NotesAppState extends State<NotesApp> with WindowListener {
NoteRepository? _repository; NoteRepository? _repository;
bool _isBootstrapping = true; bool _isBootstrapping = true;
bool _isUnlocking = false; bool _isUnlocking = false;
_AppSection _currentSection = _AppSection.home;
@override @override
void initState() { void initState() {
@@ -80,6 +89,26 @@ class _NotesAppState extends State<NotesApp> with WindowListener {
}); });
} }
void _openSettings() {
if (!mounted) {
return;
}
setState(() {
_currentSection = _AppSection.settings;
});
}
void _openHome() {
if (!mounted) {
return;
}
setState(() {
_currentSection = _AppSection.home;
});
}
Future<void> _resetLocalVaultData() async { Future<void> _resetLocalVaultData() async {
final AppDatabase? database = _database; final AppDatabase? database = _database;
@@ -207,6 +236,68 @@ class _NotesAppState extends State<NotesApp> with WindowListener {
); );
} }
Widget _buildMainShell(NoteRepository repository) {
final Widget activeScreen = _currentSection == _AppSection.home
? HomeScreen(
key: const ValueKey<String>('home-screen'),
repository: repository,
onOpenSettings: _openSettings,
)
: SettingsScreen(
key: const ValueKey<String>('settings-screen'),
onDeleteAllData: _resetLocalVaultData,
onBackToHome: _openHome,
);
return MaterialApp(
title: 'Mis Notas',
debugShowCheckedModeBanner: false,
scaffoldMessengerKey: _scaffoldMessengerKey,
theme: AppTheme.theme,
home: 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: [
const AppTitleBar(),
Expanded(
child: AnimatedSwitcher(
duration: _screenTransitionDuration,
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
transitionBuilder: (Widget child, Animation<double> animation) {
final Animation<Offset> offsetAnimation = Tween<Offset>(
begin: const Offset(0.08, 0.0),
end: Offset.zero,
).animate(animation);
return FadeTransition(
opacity: animation,
child: SlideTransition(position: offsetAnimation, child: child),
);
},
child: activeScreen,
),
),
],
),
),
),
),
);
}
@override @override
void onWindowResize() { void onWindowResize() {
_saveWindowSize(); _saveWindowSize();
@@ -226,12 +317,7 @@ class _NotesAppState extends State<NotesApp> with WindowListener {
final NoteRepository? repository = _repository; final NoteRepository? repository = _repository;
if (repository != null) { if (repository != null) {
return _buildAppShell( return _buildMainShell(repository);
home: HomeScreen(
repository: repository,
onDeleteAllData: _resetLocalVaultData,
),
);
} }
return _buildAppShell( return _buildAppShell(
+176 -194
View File
@@ -6,27 +6,19 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:notas/data/note_repository.dart'; import 'package:notas/data/note_repository.dart';
import 'package:notas/models/note.dart'; import 'package:notas/models/note.dart';
import 'package:notas/screens/note_editor_screen.dart'; import 'package:notas/screens/note_editor_screen.dart';
import 'package:notas/widgets/app_title_bar.dart';
import 'package:notas/widgets/menu_drawer.dart'; import 'package:notas/widgets/menu_drawer.dart';
import 'package:notas/screens/settings_screen.dart';
import 'package:notas/widgets/note_card.dart'; import 'package:notas/widgets/note_card.dart';
import 'package:notas/widgets/search_app_bar.dart'; import 'package:notas/widgets/search_app_bar.dart';
// HomeScreen: main entry showing notes in a responsive masonry grid.
// Key behaviors implemented here:
// - Load/save notes via `NoteRepository` (SQLite through Drift)
// - Open `NoteEditorScreen` for create/edit
// - Drag & drop reordering (updates `index` in the database)
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
const HomeScreen({ const HomeScreen({
super.key, super.key,
required this.repository, required this.repository,
required this.onDeleteAllData, required this.onOpenSettings,
}); });
final NoteRepository repository; final NoteRepository repository;
final Future<void> Function() onDeleteAllData; final VoidCallback onOpenSettings;
@override @override
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
@@ -174,130 +166,130 @@ class _HomeScreenState extends State<HomeScreen> {
: _notes.isEmpty : _notes.isEmpty
? const _EmptyState() ? const _EmptyState()
: MouseRegion( : MouseRegion(
cursor: _isDragging ? SystemMouseCursors.grabbing : SystemMouseCursors.basic, cursor: _isDragging ? SystemMouseCursors.grabbing : SystemMouseCursors.basic,
child: MasonryGridView.count( child: MasonryGridView.count(
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
mainAxisSpacing: 10, mainAxisSpacing: 10,
crossAxisSpacing: 10, crossAxisSpacing: 10,
itemCount: _getFilteredNotes().length, itemCount: _getFilteredNotes().length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final List<Note> filteredNotes = _getFilteredNotes(); final List<Note> filteredNotes = _getFilteredNotes();
return DragTarget<int>( return DragTarget<int>(
onAcceptWithDetails: (DragTargetDetails<int> details) { onAcceptWithDetails: (DragTargetDetails<int> details) {
final Note targetNote = filteredNotes[index]; final Note targetNote = filteredNotes[index];
final int originalTargetIndex = _notes.indexOf(targetNote); final int originalTargetIndex = _notes.indexOf(targetNote);
_reorderNote(details.data, originalTargetIndex); _reorderNote(details.data, originalTargetIndex);
}, },
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final double cellWidth = constraints.maxWidth; final double cellWidth = constraints.maxWidth;
return Draggable<int>( return Draggable<int>(
data: _notes.indexOf(filteredNotes[index]), data: _notes.indexOf(filteredNotes[index]),
onDragStarted: () { onDragStarted: () {
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_isDragging = true; _isDragging = true;
}); });
}, },
onDragEnd: (_) { onDragEnd: (_) {
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_isDragging = false; _isDragging = false;
}); });
}, },
onDraggableCanceled: (_, _) { onDraggableCanceled: (_, _) {
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_isDragging = false; _isDragging = false;
}); });
}, },
feedback: MouseRegion( feedback: MouseRegion(
cursor: SystemMouseCursors.grabbing, cursor: SystemMouseCursors.grabbing,
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
elevation: 8, elevation: 8,
child: SizedBox( child: SizedBox(
width: cellWidth, width: cellWidth,
child: TweenAnimationBuilder<double>( child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0.97, end: 1.0), tween: Tween(begin: 0.97, end: 1.0),
duration: const Duration(milliseconds: 180), duration: const Duration(milliseconds: 180),
curve: Curves.easeOutCubic, curve: Curves.easeOutCubic,
builder: (context, scale, child) { builder: (context, scale, child) {
return Transform.scale( return Transform.scale(
scale: scale, scale: scale,
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: child, child: child,
); );
}, },
child: Opacity( child: Opacity(
opacity: 0.95, opacity: 0.95,
child: NoteCard(note: filteredNotes[index], onTap: () {}, isDragging: true), child: NoteCard(note: filteredNotes[index], onTap: () {}, isDragging: true),
),
), ),
), ),
), ),
), ),
), childWhenDragging: MouseRegion(
childWhenDragging: MouseRegion( cursor: SystemMouseCursors.grabbing,
cursor: SystemMouseCursors.grabbing, child: Opacity(
child: Opacity( opacity: 0.3,
opacity: 0.3, child: Container(
child: Container( padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16), decoration: BoxDecoration(
decoration: BoxDecoration( color: const Color.fromRGBO(24, 25, 26, 1),
color: const Color.fromRGBO(24, 25, 26, 1), borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white24, width: 1),
border: Border.all(color: Colors.white24, width: 1), ),
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Text(
Text( filteredNotes[index].title,
filteredNotes[index].title, style: const TextStyle(
style: const TextStyle( color: Colors.white,
color: Colors.white, fontSize: 16,
fontSize: 16, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
maxLines: 2, const SizedBox(height: 8),
overflow: TextOverflow.ellipsis, Text(
), filteredNotes[index].body,
const SizedBox(height: 8), style: const TextStyle(color: Colors.white70, fontSize: 14),
Text( maxLines: 20,
filteredNotes[index].body, overflow: TextOverflow.clip,
style: const TextStyle(color: Colors.white70, fontSize: 14), ),
maxLines: 20, ],
overflow: TextOverflow.clip, ),
),
],
), ),
), ),
), ),
), child: Container(
child: Container( decoration: BoxDecoration(
decoration: BoxDecoration( border: candidateData.isNotEmpty
border: candidateData.isNotEmpty ? Border.all(color: Colors.blue.shade400, width: 2)
? Border.all(color: Colors.blue.shade400, width: 2) : null,
: null, borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12), ),
child: NoteCard(
key: ValueKey<int>(filteredNotes[index].id ?? filteredNotes[index].index),
note: filteredNotes[index],
onTap: () => _openNoteEditor(filteredNotes[index]),
isDragging: _isDragging,
),
), ),
child: NoteCard( );
key: ValueKey<int>(filteredNotes[index].id ?? filteredNotes[index].index), },
note: filteredNotes[index], );
onTap: () => _openNoteEditor(filteredNotes[index]), },
isDragging: _isDragging, );
), },
), ),
); );
},
);
},
);
},
),
);
return Scaffold( return Scaffold(
body: Container( body: Container(
@@ -314,84 +306,74 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
const AppTitleBar(), SearchAppBar(
SearchAppBar( onMenuPressed: () {
onMenuPressed: () { setState(() {
setState(() { _isMenuOpen = !_isMenuOpen;
_isMenuOpen = !_isMenuOpen; });
}); },
}, onSearchChanged: (String query) {
onSearchChanged: (String query) { setState(() {
setState(() { _searchQuery = query;
_searchQuery = query; });
}); },
}, ),
), Expanded(
Expanded( child: Stack(
child: Stack( children: [
children: [ Padding(
Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), child: body,
child: body, ),
), Positioned.fill(
// Dark overlay when menu is open; clicking it closes the menu. child: IgnorePointer(
Positioned.fill( ignoring: !_isMenuOpen,
child: IgnorePointer( child: AnimatedOpacity(
ignoring: !_isMenuOpen, duration: const Duration(milliseconds: 300),
child: AnimatedOpacity( opacity: _isMenuOpen ? 0.5 : 0.0,
duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic,
opacity: _isMenuOpen ? 0.5 : 0.0, child: GestureDetector(
curve: Curves.easeOutCubic, behavior: HitTestBehavior.opaque,
child: GestureDetector( onTap: () {
behavior: HitTestBehavior.opaque, setState(() {
onTap: () { _isMenuOpen = false;
setState(() { });
_isMenuOpen = false; },
}); child: Container(color: Colors.black),
},
child: Container(
color: Colors.black,
), ),
), ),
), ),
), ),
), AnimatedPositioned(
AnimatedPositioned( duration: const Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic,
curve: Curves.easeOutCubic, left: _isMenuOpen ? 0 : -280,
left: _isMenuOpen ? 0 : -280, top: 0,
top: 0, bottom: 0,
bottom: 0, width: 280,
width: 280, child: Material(
child: Material( color: const Color.fromRGBO(24, 25, 26, 1),
color: const Color.fromRGBO(24, 25, 26, 1), elevation: 8,
elevation: 8, child: MenuDrawer(
child: MenuDrawer( onMenuItemTapped: (String item) {
onMenuItemTapped: (String item) { setState(() {
setState(() { _isMenuOpen = false;
_isMenuOpen = false; });
});
if (item == 'settings') { if (item == 'settings') {
Navigator.of(context).push( widget.onOpenSettings();
MaterialPageRoute( }
builder: (_) => SettingsScreen( },
onDeleteAllData: widget.onDeleteAllData, ),
),
),
);
}
},
), ),
), ),
), ],
], ),
), ),
), ],
],
),
), ),
),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _openNoteComposer, onPressed: _openNoteComposer,
@@ -429,4 +411,4 @@ class _EmptyState extends StatelessWidget {
), ),
); );
} }
} }
+3 -2
View File
@@ -21,7 +21,7 @@ class NoteEditorScreen extends StatefulWidget {
return showGeneralDialog<dynamic>( return showGeneralDialog<dynamic>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
barrierColor: Colors.black.withValues(alpha: 0.5), barrierColor: const Color.fromARGB(127, 0, 0, 0).withValues(alpha: 0.5),
transitionDuration: const Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animation, secondaryAnimation) { pageBuilder: (context, animation, secondaryAnimation) {
return NoteEditorScreen(note: note); return NoteEditorScreen(note: note);
@@ -142,8 +142,9 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 600), constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF202124), color: const Color.fromRGBO(24, 25, 26, 1),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white24, width: 1),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.5), color: Colors.black.withValues(alpha: 0.5),
+3 -5
View File
@@ -1,14 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/widgets/app_title_bar.dart';
import 'package:notas/widgets/search_app_bar.dart'; import 'package:notas/widgets/search_app_bar.dart';
class SettingsScreen extends StatefulWidget { class SettingsScreen extends StatefulWidget {
const SettingsScreen({ const SettingsScreen({
super.key, super.key,
required this.onDeleteAllData, required this.onDeleteAllData,
required this.onBackToHome,
}); });
final Future<void> Function() onDeleteAllData; final Future<void> Function() onDeleteAllData;
final VoidCallback onBackToHome;
@override @override
State<SettingsScreen> createState() => _SettingsScreenState(); State<SettingsScreen> createState() => _SettingsScreenState();
@@ -44,8 +45,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Todos los datos locales han sido eliminados.')), const SnackBar(content: Text('Todos los datos locales han sido eliminados.')),
); );
Navigator.of(context).pop();
} catch (error) { } catch (error) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -77,9 +76,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
const AppTitleBar(),
SearchAppBar( SearchAppBar(
onLeadingPressed: () => Navigator.of(context).pop(), onLeadingPressed: widget.onBackToHome,
leadingIcon: Icons.arrow_back, leadingIcon: Icons.arrow_back,
leadingTooltip: 'Atrás', leadingTooltip: 'Atrás',
showSearch: false, showSearch: false,