feat: Refactor draggable note implementation for improved readability and maintainability

This commit is contained in:
2026-05-21 16:50:11 +02:00
parent 28f4ede4aa
commit 063b300428
+175 -332
View File
@@ -369,337 +369,39 @@ class _HomeScreenState extends State<HomeScreen> {
_lastPointerKind, _lastPointerKind,
); );
final Widget draggableNote = final Widget draggableNote = _DraggableNote(
requiresLongPressToDrag note: filteredNotes[index],
? LongPressDraggable<int>( dataIndex: _notes.indexOf(
data: _notes.indexOf( filteredNotes[index],
filteredNotes[index], ),
), cellWidth: cellWidth,
delay: const Duration( requiresLongPressToDrag:
milliseconds: 280, requiresLongPressToDrag,
), isDragging: _isDragging,
onDragStarted: () { isDragTargetActive:
if (!mounted) return; candidateData.isNotEmpty,
setState(() { onTap: () => _openNoteEditor(
_isDragging = true; filteredNotes[index],
}); ),
}, onDragStarted: () {
onDragEnd: (_) { if (!mounted) return;
if (!mounted) return; setState(() {
setState(() { _isDragging = true;
_isDragging = false; });
}); },
}, onDragEnd: (_) {
onDraggableCanceled: (_, _) { if (!mounted) return;
if (!mounted) return; setState(() {
setState(() { _isDragging = false;
_isDragging = false; });
}); },
}, onDraggableCanceled: () {
feedback: MouseRegion( if (!mounted) return;
cursor: setState(() {
SystemMouseCursors.grabbing, _isDragging = false;
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,
),
maxLines: 2,
),
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: NoteCard(
key: ValueKey<String>(
filteredNotes[index].id,
),
note: filteredNotes[index],
onTap: () => _openNoteEditor(
filteredNotes[index],
),
isDragging: _isDragging,
),
),
)
: 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,
),
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,
),
],
),
),
),
),
child: Container(
decoration: BoxDecoration(
border:
candidateData.isNotEmpty
? Border.all(
color: Colors
.blue
.shade400,
width: 2,
)
: null,
borderRadius:
BorderRadius.circular(12),
),
child: NoteCard(
key: ValueKey<String>(
filteredNotes[index].id,
),
note: filteredNotes[index],
onTap: () => _openNoteEditor(
filteredNotes[index],
),
isDragging: _isDragging,
),
),
);
return Listener( return Listener(
onPointerDown: onPointerDown:
@@ -827,7 +529,7 @@ class _HomeScreenState extends State<HomeScreen> {
child: MenuDrawer( child: MenuDrawer(
onMenuItemTapped: _handleMenuItemTapped, onMenuItemTapped: _handleMenuItemTapped,
selectedItem: _selectedCategoryId != null selectedItem: _selectedCategoryId != null
? 'category_${_selectedCategoryId}' ? 'category_$_selectedCategoryId'
: (_showDeletedNotes : (_showDeletedNotes
? 'deleted_notes' ? 'deleted_notes'
: 'all_notes'), : 'all_notes'),
@@ -859,6 +561,146 @@ class _HomeScreenState extends State<HomeScreen> {
} }
} }
class _DraggableNote extends StatelessWidget {
const _DraggableNote({
required this.note,
required this.dataIndex,
required this.cellWidth,
required this.requiresLongPressToDrag,
required this.isDragging,
required this.isDragTargetActive,
required this.onTap,
required this.onDragStarted,
required this.onDragEnd,
required this.onDraggableCanceled,
});
final Note note;
final int dataIndex;
final double cellWidth;
final bool requiresLongPressToDrag;
final bool isDragging;
final bool isDragTargetActive;
final VoidCallback onTap;
final void Function(DraggableDetails) onDragEnd;
final VoidCallback onDraggableCanceled;
final VoidCallback onDragStarted;
Widget _buildFeedback() {
return 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: note,
onTap: () {},
isDragging: true,
),
),
),
),
),
);
}
Widget _buildChildWhenDragging() {
return 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(
note.title,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Text(
note.body,
style: const TextStyle(color: Colors.white70, fontSize: 14),
maxLines: 20,
overflow: TextOverflow.clip,
),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
final Widget content = Container(
decoration: BoxDecoration(
border: isDragTargetActive
? Border.all(color: Colors.blue.shade400, width: 2)
: null,
borderRadius: BorderRadius.circular(12),
),
child: NoteCard(
key: ValueKey<String>(note.id),
note: note,
onTap: onTap,
isDragging: isDragging,
),
);
if (requiresLongPressToDrag) {
return LongPressDraggable<int>(
data: dataIndex,
delay: const Duration(milliseconds: 280),
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(),
feedback: _buildFeedback(),
childWhenDragging: _buildChildWhenDragging(),
child: content,
);
}
return Draggable<int>(
data: dataIndex,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(),
feedback: _buildFeedback(),
childWhenDragging: _buildChildWhenDragging(),
child: content,
);
}
}
class _EmptyState extends StatelessWidget { class _EmptyState extends StatelessWidget {
const _EmptyState({ const _EmptyState({
required this.showDeletedNotes, required this.showDeletedNotes,
@@ -1009,6 +851,7 @@ class _CategoryDialogState extends State<_CategoryDialog> {
} catch (_) {} } catch (_) {}
if (mounted) { if (mounted) {
await widget.onCategoryDeleted(); await widget.onCategoryDeleted();
if (!mounted) return;
Navigator.pop(context); Navigator.pop(context);
} }
} catch (e) { } catch (e) {