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
+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/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<void> Function() onDeleteAllData;
final VoidCallback onOpenSettings;
@override
State<HomeScreen> createState() => _HomeScreenState();
@@ -174,130 +166,130 @@ class _HomeScreenState extends State<HomeScreen> {
: _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<Note> filteredNotes = _getFilteredNotes();
return DragTarget<int>(
onAcceptWithDetails: (DragTargetDetails<int> 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<Note> filteredNotes = _getFilteredNotes();
return DragTarget<int>(
onAcceptWithDetails: (DragTargetDetails<int> 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<int>(
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<double>(
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<int>(
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<double>(
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<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(
body: Container(
@@ -314,84 +306,74 @@ class _HomeScreenState extends State<HomeScreen> {
),
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 {
),
);
}
}
}