diff --git a/lib/app.dart b/lib/app.dart index e7507c9..98983e9 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -7,6 +7,7 @@ import 'package:notas/data/note_repository.dart'; import 'package:notas/platform/app_platform.dart'; import 'package:notas/platform/window_state.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/theme/app_theme.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:window_manager/window_manager.dart'; +enum _AppSection { + home, + settings, +} + class NotesApp extends StatefulWidget { const NotesApp({super.key}); @@ -22,6 +28,8 @@ class NotesApp extends StatefulWidget { } class _NotesAppState extends State with WindowListener { + static const Duration _screenTransitionDuration = Duration(milliseconds: 280); + final LocalVaultService _vaultService = LocalVaultService.instance; final GlobalKey _scaffoldMessengerKey = GlobalKey(); @@ -30,6 +38,7 @@ class _NotesAppState extends State with WindowListener { NoteRepository? _repository; bool _isBootstrapping = true; bool _isUnlocking = false; + _AppSection _currentSection = _AppSection.home; @override void initState() { @@ -80,6 +89,26 @@ class _NotesAppState extends State with WindowListener { }); } + void _openSettings() { + if (!mounted) { + return; + } + + setState(() { + _currentSection = _AppSection.settings; + }); + } + + void _openHome() { + if (!mounted) { + return; + } + + setState(() { + _currentSection = _AppSection.home; + }); + } + Future _resetLocalVaultData() async { final AppDatabase? database = _database; @@ -207,6 +236,68 @@ class _NotesAppState extends State with WindowListener { ); } + Widget _buildMainShell(NoteRepository repository) { + final Widget activeScreen = _currentSection == _AppSection.home + ? HomeScreen( + key: const ValueKey('home-screen'), + repository: repository, + onOpenSettings: _openSettings, + ) + : SettingsScreen( + key: const ValueKey('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 animation) { + final Animation offsetAnimation = Tween( + 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 void onWindowResize() { _saveWindowSize(); @@ -226,12 +317,7 @@ class _NotesAppState extends State with WindowListener { final NoteRepository? repository = _repository; if (repository != null) { - return _buildAppShell( - home: HomeScreen( - repository: repository, - onDeleteAllData: _resetLocalVaultData, - ), - ); + return _buildMainShell(repository); } return _buildAppShell( diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 3e5c276..83654bd 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -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/models/note.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/screens/settings_screen.dart'; import 'package:notas/widgets/note_card.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 { const HomeScreen({ super.key, required this.repository, - required this.onDeleteAllData, + required this.onOpenSettings, }); final NoteRepository repository; - final Future Function() onDeleteAllData; + final VoidCallback onOpenSettings; @override State createState() => _HomeScreenState(); @@ -174,130 +166,130 @@ class _HomeScreenState extends State { : _notes.isEmpty ? const _EmptyState() : MouseRegion( - cursor: _isDragging ? SystemMouseCursors.grabbing : SystemMouseCursors.basic, - child: MasonryGridView.count( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - itemCount: _getFilteredNotes().length, - itemBuilder: (BuildContext context, int index) { - final List filteredNotes = _getFilteredNotes(); - return DragTarget( - onAcceptWithDetails: (DragTargetDetails details) { - final Note targetNote = filteredNotes[index]; - final int originalTargetIndex = _notes.indexOf(targetNote); - _reorderNote(details.data, originalTargetIndex); - }, - builder: (context, candidateData, rejectedData) { - return LayoutBuilder( - builder: (context, constraints) { - final double cellWidth = constraints.maxWidth; + cursor: _isDragging ? SystemMouseCursors.grabbing : SystemMouseCursors.basic, + child: MasonryGridView.count( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + itemCount: _getFilteredNotes().length, + itemBuilder: (BuildContext context, int index) { + final List filteredNotes = _getFilteredNotes(); + return DragTarget( + onAcceptWithDetails: (DragTargetDetails details) { + final Note targetNote = filteredNotes[index]; + final int originalTargetIndex = _notes.indexOf(targetNote); + _reorderNote(details.data, originalTargetIndex); + }, + builder: (context, candidateData, rejectedData) { + return LayoutBuilder( + builder: (context, constraints) { + final double cellWidth = constraints.maxWidth; - return Draggable( - data: _notes.indexOf(filteredNotes[index]), - onDragStarted: () { - if (!mounted) return; - setState(() { - _isDragging = true; - }); - }, - onDragEnd: (_) { - if (!mounted) return; - setState(() { - _isDragging = false; - }); - }, - onDraggableCanceled: (_, _) { - if (!mounted) return; - setState(() { - _isDragging = false; - }); - }, - feedback: MouseRegion( - cursor: SystemMouseCursors.grabbing, - child: Material( - color: Colors.transparent, - elevation: 8, - child: SizedBox( - width: cellWidth, - child: TweenAnimationBuilder( - tween: Tween(begin: 0.97, end: 1.0), - duration: const Duration(milliseconds: 180), - curve: Curves.easeOutCubic, - builder: (context, scale, child) { - return Transform.scale( - scale: scale, - alignment: Alignment.topLeft, - child: child, - ); - }, - child: Opacity( - opacity: 0.95, - child: NoteCard(note: filteredNotes[index], onTap: () {}, isDragging: true), + return Draggable( + data: _notes.indexOf(filteredNotes[index]), + onDragStarted: () { + if (!mounted) return; + setState(() { + _isDragging = true; + }); + }, + onDragEnd: (_) { + if (!mounted) return; + setState(() { + _isDragging = false; + }); + }, + onDraggableCanceled: (_, _) { + if (!mounted) return; + setState(() { + _isDragging = false; + }); + }, + feedback: MouseRegion( + cursor: SystemMouseCursors.grabbing, + child: Material( + color: Colors.transparent, + elevation: 8, + child: SizedBox( + width: cellWidth, + child: TweenAnimationBuilder( + tween: Tween(begin: 0.97, end: 1.0), + duration: const Duration(milliseconds: 180), + curve: Curves.easeOutCubic, + builder: (context, scale, child) { + return Transform.scale( + scale: scale, + alignment: Alignment.topLeft, + child: child, + ); + }, + child: Opacity( + opacity: 0.95, + child: NoteCard(note: filteredNotes[index], onTap: () {}, isDragging: true), + ), ), ), ), ), - ), - childWhenDragging: MouseRegion( - cursor: SystemMouseCursors.grabbing, - child: Opacity( - opacity: 0.3, - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color.fromRGBO(24, 25, 26, 1), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white24, width: 1), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - filteredNotes[index].title, - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold, + childWhenDragging: MouseRegion( + cursor: SystemMouseCursors.grabbing, + child: Opacity( + opacity: 0.3, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color.fromRGBO(24, 25, 26, 1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.white24, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + filteredNotes[index].title, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 8), - Text( - filteredNotes[index].body, - style: const TextStyle(color: Colors.white70, fontSize: 14), - maxLines: 20, - overflow: TextOverflow.clip, - ), - ], + const SizedBox(height: 8), + Text( + filteredNotes[index].body, + style: const TextStyle(color: Colors.white70, fontSize: 14), + maxLines: 20, + overflow: TextOverflow.clip, + ), + ], + ), ), ), ), - ), - child: Container( - decoration: BoxDecoration( - border: candidateData.isNotEmpty - ? Border.all(color: Colors.blue.shade400, width: 2) - : null, - borderRadius: BorderRadius.circular(12), + child: Container( + decoration: BoxDecoration( + border: candidateData.isNotEmpty + ? Border.all(color: Colors.blue.shade400, width: 2) + : null, + borderRadius: BorderRadius.circular(12), + ), + child: NoteCard( + key: ValueKey(filteredNotes[index].id ?? filteredNotes[index].index), + note: filteredNotes[index], + onTap: () => _openNoteEditor(filteredNotes[index]), + isDragging: _isDragging, + ), ), - child: NoteCard( - key: ValueKey(filteredNotes[index].id ?? filteredNotes[index].index), - note: filteredNotes[index], - onTap: () => _openNoteEditor(filteredNotes[index]), - isDragging: _isDragging, - ), - ), - ); - }, - ); - }, - ); - }, - ), - ); + ); + }, + ); + }, + ); + }, + ), + ); return Scaffold( body: Container( @@ -314,84 +306,74 @@ class _HomeScreenState extends State { ), child: SafeArea( child: Column( - children: [ - const AppTitleBar(), - SearchAppBar( - onMenuPressed: () { - setState(() { - _isMenuOpen = !_isMenuOpen; - }); - }, - onSearchChanged: (String query) { - setState(() { - _searchQuery = query; - }); - }, - ), - Expanded( - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), - child: body, - ), - // Dark overlay when menu is open; clicking it closes the menu. - Positioned.fill( - 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, + children: [ + SearchAppBar( + onMenuPressed: () { + setState(() { + _isMenuOpen = !_isMenuOpen; + }); + }, + onSearchChanged: (String query) { + setState(() { + _searchQuery = query; + }); + }, + ), + Expanded( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + child: body, + ), + Positioned.fill( + 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: 0, - bottom: 0, - width: 280, - child: Material( - color: const Color.fromRGBO(24, 25, 26, 1), - elevation: 8, - child: MenuDrawer( - onMenuItemTapped: (String item) { - setState(() { - _isMenuOpen = false; - }); + AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + left: _isMenuOpen ? 0 : -280, + top: 0, + bottom: 0, + width: 280, + child: Material( + color: const Color.fromRGBO(24, 25, 26, 1), + elevation: 8, + child: MenuDrawer( + onMenuItemTapped: (String item) { + setState(() { + _isMenuOpen = false; + }); - if (item == 'settings') { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => SettingsScreen( - onDeleteAllData: widget.onDeleteAllData, - ), - ), - ); - } - }, + if (item == 'settings') { + widget.onOpenSettings(); + } + }, + ), ), ), - ), - ], + ], + ), ), - ), - ], - ), + ], ), + ), ), floatingActionButton: FloatingActionButton( onPressed: _openNoteComposer, @@ -429,4 +411,4 @@ class _EmptyState extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/screens/note_editor_screen.dart b/lib/screens/note_editor_screen.dart index bab6d42..dc2955a 100644 --- a/lib/screens/note_editor_screen.dart +++ b/lib/screens/note_editor_screen.dart @@ -21,7 +21,7 @@ class NoteEditorScreen extends StatefulWidget { return showGeneralDialog( context: context, 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), pageBuilder: (context, animation, secondaryAnimation) { return NoteEditorScreen(note: note); @@ -142,8 +142,9 @@ class _NoteEditorScreenState extends State { child: Container( constraints: const BoxConstraints(maxWidth: 600), decoration: BoxDecoration( - color: const Color(0xFF202124), + color: const Color.fromRGBO(24, 25, 26, 1), borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.white24, width: 1), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.5), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 90167f9..987d16a 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:notas/widgets/app_title_bar.dart'; import 'package:notas/widgets/search_app_bar.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({ super.key, required this.onDeleteAllData, + required this.onBackToHome, }); final Future Function() onDeleteAllData; + final VoidCallback onBackToHome; @override State createState() => _SettingsScreenState(); @@ -44,8 +45,6 @@ class _SettingsScreenState extends State { 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( @@ -77,9 +76,8 @@ class _SettingsScreenState extends State { child: SafeArea( child: Column( children: [ - const AppTitleBar(), SearchAppBar( - onLeadingPressed: () => Navigator.of(context).pop(), + onLeadingPressed: widget.onBackToHome, leadingIcon: Icons.arrow_back, leadingTooltip: 'AtrĂ¡s', showSearch: false,