From 28f4ede4aa99d7e8e275fcdecd7f7d3401176b9d Mon Sep 17 00:00:00 2001 From: Marcos Date: Thu, 21 May 2026 16:26:31 +0200 Subject: [PATCH] feat: Refactor note editor dialog and delete confirmation for improved readability and reusability --- lib/screens/home_screen.dart | 680 ++++++++++++++++------------ lib/screens/note_editor_screen.dart | 241 ++++------ lib/widgets/menu_drawer.dart | 55 ++- 3 files changed, 518 insertions(+), 458 deletions(-) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index a12637c..28bde18 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -316,12 +316,6 @@ class _HomeScreenState extends State { final Widget body = _isLoading ? const Center(child: CircularProgressIndicator()) - : visibleNotes.isEmpty - ? _EmptyState( - showDeletedNotes: _showDeletedNotes, - categoryName: currentCategory?.name, - searchQuery: _searchQuery, - ) : RefreshIndicator( onRefresh: () async { await widget.onRequestSync(); @@ -330,295 +324,405 @@ class _HomeScreenState extends State { cursor: _isDragging ? SystemMouseCursors.grabbing : SystemMouseCursors.basic, - child: MasonryGridView.count( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - itemCount: visibleNotes.length, - itemBuilder: (BuildContext context, int index) { - final List filteredNotes = visibleNotes; - 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; - final bool requiresLongPressToDrag = - _requiresLongPressToDrag(_lastPointerKind); + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: visibleNotes.isEmpty + ? [ + SliverFillRemaining( + hasScrollBody: false, + child: _EmptyState( + showDeletedNotes: _showDeletedNotes, + categoryName: currentCategory?.name, + searchQuery: _searchQuery, + ), + ), + ] + : [ + SliverPadding( + padding: const EdgeInsets.only(bottom: 8), + sliver: SliverMasonryGrid.count( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childCount: visibleNotes.length, + itemBuilder: (BuildContext context, int index) { + final List filteredNotes = visibleNotes; + 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; + final bool requiresLongPressToDrag = + _requiresLongPressToDrag( + _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, + final Widget draggableNote = + requiresLongPressToDrag + ? LongPressDraggable( + data: _notes.indexOf( + filteredNotes[index], ), - maxLines: 2, - ), - const SizedBox(height: 8), - Text( - filteredNotes[index].body, - style: const TextStyle( - color: Colors.white70, - fontSize: 14, + 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, + ), ), - 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, + : 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, + ), + ), ); - }, - 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, - ), - ), - ); - return Listener( - onPointerDown: (PointerDownEvent event) { - if (_lastPointerKind == event.kind) { - return; - } + return Listener( + onPointerDown: + (PointerDownEvent event) { + if (_lastPointerKind == + event.kind) { + return; + } - setState(() { - _lastPointerKind = event.kind; - }); + setState(() { + _lastPointerKind = event.kind; + }); + }, + child: draggableNote, + ); + }, + ); + }, + ); }, - child: draggableNote, - ); - }, - ); - }, - ); - }, + ), + ), + ], ), ), ); @@ -904,7 +1008,7 @@ class _CategoryDialogState extends State<_CategoryDialog> { await widget.onRequestSync(); } catch (_) {} if (mounted) { - await widget.onCategoryDeleted(); + await widget.onCategoryDeleted(); Navigator.pop(context); } } catch (e) { diff --git a/lib/screens/note_editor_screen.dart b/lib/screens/note_editor_screen.dart index 87be3b5..ceb834b 100644 --- a/lib/screens/note_editor_screen.dart +++ b/lib/screens/note_editor_screen.dart @@ -28,39 +28,44 @@ class NoteEditorScreen extends StatefulWidget { @override State createState() => _NoteEditorScreenState(); + static Future _showGeneralEditorDialog( + BuildContext context, { + Note? note, + String? categoryId, + }) { + return showGeneralDialog( + context: context, + barrierDismissible: false, + barrierColor: Colors.transparent, + transitionDuration: const Duration(milliseconds: 200), + pageBuilder: (context, animation, secondaryAnimation) { + return NoteEditorScreen(note: note, categoryId: categoryId); + }, + transitionBuilder: (context, animation, secondaryAnimation, child) { + return ScaleTransition(scale: animation, child: child); + }, + ); + } + static Future showDialog( BuildContext context, { Note? note, String? categoryId, }) { if (isAndroid || isIOS) { - return showGeneralDialog( - context: context, - barrierDismissible: false, - barrierColor: Colors.transparent, - transitionDuration: const Duration(milliseconds: 200), - pageBuilder: (context, animation, secondaryAnimation) { - return NoteEditorScreen(note: note, categoryId: categoryId); - }, - transitionBuilder: (context, animation, secondaryAnimation, child) { - return ScaleTransition(scale: animation, child: child); - }, + return _showGeneralEditorDialog( + context, + note: note, + categoryId: categoryId, ); } final OverlayState? overlayState = Overlay.of(context, rootOverlay: true); if (overlayState == null) { - return showGeneralDialog( - context: context, - barrierDismissible: false, - barrierColor: Colors.transparent, - transitionDuration: const Duration(milliseconds: 200), - pageBuilder: (context, animation, secondaryAnimation) { - return NoteEditorScreen(note: note, categoryId: categoryId); - }, - transitionBuilder: (context, animation, secondaryAnimation, child) { - return ScaleTransition(scale: animation, child: child); - }, + return _showGeneralEditorDialog( + context, + note: note, + categoryId: categoryId, ); } @@ -161,43 +166,52 @@ class _NoteEditorScreenState extends State { _complete(updatedNote); } - Future _showDeleteConfirmation() async { + Widget _buildDeleteConfirmationDialog({ + required ValueChanged onConfirmed, + }) { final bool isDeletedNote = _currentNote.isDeleted; + return AlertDialog( + backgroundColor: const Color(0xFF303134), + title: Text( + isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota', + style: const TextStyle(color: Colors.white), + ), + content: Text( + isDeletedNote + ? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.' + : '¿Estás seguro de que deseas eliminar esta nota?', + style: const TextStyle(color: Colors.white70), + ), + actions: [ + TextButton( + onPressed: () => onConfirmed(false), + child: const Text( + 'Cancelar', + style: TextStyle(color: Colors.white70), + ), + ), + TextButton( + onPressed: () => onConfirmed(true), + child: Text( + isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar', + style: const TextStyle(color: Colors.red), + ), + ), + ], + ); + } + + Future _showDeleteConfirmation() async { if (_isMobileLayout) { final bool? confirmed = await showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext dialogContext) { - return AlertDialog( - backgroundColor: const Color(0xFF303134), - title: Text( - isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota', - style: const TextStyle(color: Colors.white), - ), - content: Text( - isDeletedNote - ? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.' - : '¿Estás seguro de que deseas eliminar esta nota?', - style: const TextStyle(color: Colors.white70), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(false), - child: const Text( - 'Cancelar', - style: TextStyle(color: Colors.white70), - ), - ), - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(true), - child: Text( - isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar', - style: const TextStyle(color: Colors.red), - ), - ), - ], + return _buildDeleteConfirmationDialog( + onConfirmed: (bool confirmed) => + Navigator.of(dialogContext).pop(confirmed), ); }, ); @@ -212,34 +226,9 @@ class _NoteEditorScreenState extends State { barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext dialogContext) { - return AlertDialog( - backgroundColor: const Color(0xFF303134), - title: Text( - isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota', - style: const TextStyle(color: Colors.white), - ), - content: Text( - isDeletedNote - ? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.' - : '¿Estás seguro de que deseas eliminar esta nota?', - style: const TextStyle(color: Colors.white70), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(false), - child: const Text( - 'Cancelar', - style: TextStyle(color: Colors.white70), - ), - ), - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(true), - child: Text( - isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar', - style: const TextStyle(color: Colors.red), - ), - ), - ], + return _buildDeleteConfirmationDialog( + onConfirmed: (bool confirmed) => + Navigator.of(dialogContext).pop(confirmed), ); }, ); @@ -251,18 +240,18 @@ class _NoteEditorScreenState extends State { late final OverlayEntry entry; bool didRemove = false; - void close(bool confirmed) { - if (!completer.isCompleted) { - completer.complete(confirmed); - } - if (!didRemove && entry.mounted) { - didRemove = true; - entry.remove(); - } - } - entry = OverlayEntry( builder: (BuildContext overlayContext) { + final ValueChanged close = (bool confirmed) { + if (!completer.isCompleted) { + completer.complete(confirmed); + } + if (!didRemove && entry.mounted) { + didRemove = true; + entry.remove(); + } + }; + return Material( color: Colors.transparent, child: Stack( @@ -276,39 +265,7 @@ class _NoteEditorScreenState extends State { Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), - child: AlertDialog( - backgroundColor: const Color(0xFF303134), - title: Text( - isDeletedNote - ? 'Eliminar permanentemente' - : 'Eliminar nota', - style: const TextStyle(color: Colors.white), - ), - content: Text( - isDeletedNote - ? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.' - : '¿Estás seguro de que deseas eliminar esta nota?', - style: const TextStyle(color: Colors.white70), - ), - actions: [ - TextButton( - onPressed: () => close(false), - child: const Text( - 'Cancelar', - style: TextStyle(color: Colors.white70), - ), - ), - TextButton( - onPressed: () => close(true), - child: Text( - isDeletedNote - ? 'Eliminar permanentemente' - : 'Eliminar', - style: const TextStyle(color: Colors.red), - ), - ), - ], - ), + child: _buildDeleteConfirmationDialog(onConfirmed: close), ), ), ], @@ -351,26 +308,15 @@ class _NoteEditorScreenState extends State { return Column( children: [ Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.white12, - width: 1, - ), - ), + border: Border(bottom: BorderSide(color: Colors.white12, width: 1)), ), child: Row( children: [ IconButton( onPressed: _closeWithoutSaving, - icon: const Icon( - Icons.close, - color: Colors.white70, - ), + icon: const Icon(Icons.close, color: Colors.white70), tooltip: 'Cerrar sin guardar', ), const SizedBox(width: 8), @@ -434,9 +380,7 @@ class _NoteEditorScreenState extends State { ), decoration: const InputDecoration( hintText: 'Escribe tu nota...', - hintStyle: TextStyle( - color: Colors.white30, - ), + hintStyle: TextStyle(color: Colors.white30), border: InputBorder.none, contentPadding: EdgeInsets.zero, ), @@ -447,14 +391,9 @@ class _NoteEditorScreenState extends State { ), ), Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - border: Border( - top: BorderSide(color: Colors.white12, width: 1), - ), + border: Border(top: BorderSide(color: Colors.white12, width: 1)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -462,18 +401,12 @@ class _NoteEditorScreenState extends State { if (!_isNewNote) IconButton( onPressed: _deleteNote, - icon: const Icon( - Icons.delete_outline, - color: Colors.red, - ), + icon: const Icon(Icons.delete_outline, color: Colors.red), tooltip: 'Eliminar nota', ) else const SizedBox(width: 48), - FilledButton( - onPressed: _saveNote, - child: const Text('Guardar'), - ), + FilledButton(onPressed: _saveNote, child: const Text('Guardar')), ], ), ), diff --git a/lib/widgets/menu_drawer.dart b/lib/widgets/menu_drawer.dart index 059e752..8cf2602 100644 --- a/lib/widgets/menu_drawer.dart +++ b/lib/widgets/menu_drawer.dart @@ -45,12 +45,9 @@ class MenuDrawer extends StatelessWidget { child: Column( children: categories.map((category) { final categoryId = 'category_${category.id}'; - final IconData categoryIcon = category.iconCodePoint == null - ? Icons.folder_outlined - : IconData( - category.iconCodePoint!, - fontFamily: 'MaterialIcons', - ); + final IconData categoryIcon = _iconForCodePoint( + category.iconCodePoint, + ); return _MenuItemTile( icon: categoryIcon, @@ -102,6 +99,31 @@ class MenuDrawer extends StatelessWidget { } } +IconData _iconForCodePoint(int? codePoint) { + if (codePoint == null) { + return Icons.folder_outlined; + } + + const List icons = [ + Icons.folder, + Icons.work, + Icons.star, + Icons.home, + Icons.school, + Icons.book, + Icons.music_note, + Icons.lightbulb, + ]; + + for (final IconData icon in icons) { + if (icon.codePoint == codePoint) { + return icon; + } + } + + return Icons.folder_outlined; +} + class _MenuItemTile extends StatelessWidget { const _MenuItemTile({ required this.icon, @@ -136,7 +158,7 @@ class _MenuItemTile extends StatelessWidget { top: 2, bottom: 2, ), - decoration: BoxDecoration( + child: Material( color: backgroundColor, borderRadius: selected ? const BorderRadius.only( @@ -144,16 +166,17 @@ class _MenuItemTile extends StatelessWidget { bottomRight: Radius.circular(999), ) : BorderRadius.circular(12), - ), - child: ListTile( - leading: Icon(icon, color: iconColor ?? foregroundColor), - trailing: trailing, - title: Text( - label, - style: TextStyle(color: textColor ?? foregroundColor, fontSize: 14), + clipBehavior: Clip.antiAlias, + child: ListTile( + leading: Icon(icon, color: iconColor ?? foregroundColor), + trailing: trailing, + title: Text( + label, + style: TextStyle(color: textColor ?? foregroundColor, fontSize: 14), + ), + onTap: onTap, + hoverColor: Colors.white.withValues(alpha: 0.1), ), - onTap: onTap, - hoverColor: Colors.white.withValues(alpha: 0.1), ), ); }