From 063b30042825818b4cfb3f65c978df1d51a3297f Mon Sep 17 00:00:00 2001 From: Marcos Date: Thu, 21 May 2026 16:50:11 +0200 Subject: [PATCH] feat: Refactor draggable note implementation for improved readability and maintainability --- lib/screens/home_screen.dart | 507 ++++++++++++----------------------- 1 file changed, 175 insertions(+), 332 deletions(-) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 28bde18..2d7c553 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -369,337 +369,39 @@ class _HomeScreenState extends State { _lastPointerKind, ); - final Widget draggableNote = - requiresLongPressToDrag - ? LongPressDraggable( - data: _notes.indexOf( - filteredNotes[index], - ), - delay: const Duration( - milliseconds: 280, - ), - 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, - ), - 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( - filteredNotes[index].id, - ), - note: filteredNotes[index], - onTap: () => _openNoteEditor( - filteredNotes[index], - ), - isDragging: _isDragging, - ), - ), - ) - : 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, - ), - 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( - filteredNotes[index].id, - ), - note: filteredNotes[index], - onTap: () => _openNoteEditor( - filteredNotes[index], - ), - isDragging: _isDragging, - ), - ), - ); + final Widget draggableNote = _DraggableNote( + note: filteredNotes[index], + dataIndex: _notes.indexOf( + filteredNotes[index], + ), + cellWidth: cellWidth, + requiresLongPressToDrag: + requiresLongPressToDrag, + isDragging: _isDragging, + isDragTargetActive: + candidateData.isNotEmpty, + onTap: () => _openNoteEditor( + filteredNotes[index], + ), + onDragStarted: () { + if (!mounted) return; + setState(() { + _isDragging = true; + }); + }, + onDragEnd: (_) { + if (!mounted) return; + setState(() { + _isDragging = false; + }); + }, + onDraggableCanceled: () { + if (!mounted) return; + setState(() { + _isDragging = false; + }); + }, + ); return Listener( onPointerDown: @@ -827,7 +529,7 @@ class _HomeScreenState extends State { child: MenuDrawer( onMenuItemTapped: _handleMenuItemTapped, selectedItem: _selectedCategoryId != null - ? 'category_${_selectedCategoryId}' + ? 'category_$_selectedCategoryId' : (_showDeletedNotes ? 'deleted_notes' : 'all_notes'), @@ -859,6 +561,146 @@ class _HomeScreenState extends State { } } +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( + 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(note.id), + note: note, + onTap: onTap, + isDragging: isDragging, + ), + ); + + if (requiresLongPressToDrag) { + return LongPressDraggable( + 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( + data: dataIndex, + onDragStarted: onDragStarted, + onDragEnd: onDragEnd, + onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(), + feedback: _buildFeedback(), + childWhenDragging: _buildChildWhenDragging(), + child: content, + ); + } +} + class _EmptyState extends StatelessWidget { const _EmptyState({ required this.showDeletedNotes, @@ -1009,6 +851,7 @@ class _CategoryDialogState extends State<_CategoryDialog> { } catch (_) {} if (mounted) { await widget.onCategoryDeleted(); + if (!mounted) return; Navigator.pop(context); } } catch (e) {