import 'dart:async'; import 'package:flutter/material.dart'; import 'package:notas/models/category.dart'; import 'package:notas/theme/app_palette.dart'; import 'package:notas/widgets/category_style.dart'; const String kCreateCategoryMenuValue = '__create_category__'; class CategoryMenuButton extends StatelessWidget { const CategoryMenuButton({ super.key, required this.categories, required this.selectedCategoryId, required this.onCategorySelected, this.onCreateCategory, this.onEditCategory, this.icon = Icons.filter_alt_outlined, this.tooltip = 'Filtrar por categorías', this.isActive = false, this.allowCreateCategory = false, this.emptyLabel = 'Todas las categorías', this.createLabel = 'Crear categoría', this.backgroundColor, }); final List categories; final String? selectedCategoryId; final ValueChanged onCategorySelected; final FutureOr Function()? onCreateCategory; final FutureOr Function(Category category)? onEditCategory; final IconData icon; final String tooltip; final bool isActive; final bool allowCreateCategory; final String emptyLabel; final String createLabel; final Color? backgroundColor; @override Widget build(BuildContext context) { final AppPalette palette = Theme.of(context).extension() ?? AppPalette.fromBrightness(Theme.of(context).brightness); return IconButton( onPressed: () async { final String? selectedValue = await showCategorySelectionMenu( anchorContext: context, categories: categories, selectedCategoryId: selectedCategoryId, allowCreateCategory: allowCreateCategory, emptyLabel: emptyLabel, createLabel: createLabel, onEditCategory: onEditCategory, ); if (selectedValue == null) { return; } if (selectedValue == kCreateCategoryMenuValue) { await onCreateCategory?.call(); return; } onCategorySelected(selectedValue.isEmpty ? null : selectedValue); }, tooltip: tooltip, iconSize: 24, style: IconButton.styleFrom( backgroundColor: backgroundColor ?? (isActive ? palette.accent.withValues(alpha: 0.08) : Colors.transparent), shape: const CircleBorder(), ), icon: Stack( clipBehavior: Clip.none, children: [ Icon(icon, color: palette.textSecondary), if (isActive) Positioned( right: -1, top: -1, child: Container( width: 7, height: 7, decoration: BoxDecoration( color: palette.accent, shape: BoxShape.circle, ), ), ), ], ), ); } } Future showCategorySelectionMenu({ required BuildContext anchorContext, required List categories, required String? selectedCategoryId, required bool allowCreateCategory, required String emptyLabel, required String createLabel, FutureOr Function(Category category)? onEditCategory, }) { return showMenu( context: anchorContext, position: _menuRectFromContext(anchorContext), elevation: 10, color: _paletteOf(anchorContext).surfaceElevated, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), items: >[ PopupMenuItem( value: '', child: _buildCategoryMenuItem( context: anchorContext, label: emptyLabel, icon: allowCreateCategory ? Icons.folder_outlined : Icons.filter_alt_outlined, color: _paletteOf(anchorContext).textSecondary, selected: selectedCategoryId == null, ), ), const PopupMenuDivider(), for (final Category category in categories) PopupMenuItem( value: category.id, child: Builder( builder: (BuildContext menuContext) { final Color categoryColor = Color( category.colorValue ?? _paletteOf(menuContext).accent.toARGB32(), ); return _buildCategoryMenuItem( context: menuContext, label: category.name, icon: CategoryStyle.iconForCodePoint(category.iconCodePoint), color: categoryColor, selected: selectedCategoryId == category.id, onEditPressed: onEditCategory == null ? null : () { Navigator.of(menuContext).pop(); final FutureOr result = onEditCategory(category); if (result is Future) { unawaited(result); } }, ); }, ), ), if (allowCreateCategory) ...[ const PopupMenuDivider(), PopupMenuItem( value: kCreateCategoryMenuValue, child: _buildCategoryMenuItem( context: anchorContext, label: createLabel, icon: Icons.add_circle_outline, color: _paletteOf(anchorContext).textSecondary, selected: false, ), ), ], ], ); } RelativeRect _menuRectFromContext(BuildContext anchorContext) { final RenderBox button = anchorContext.findRenderObject()! as RenderBox; final RenderBox overlay = Overlay.of(anchorContext).context.findRenderObject()! as RenderBox; final Offset topLeft = button.localToGlobal(Offset.zero, ancestor: overlay); final Offset bottomRight = button.localToGlobal( button.size.bottomRight(Offset.zero), ancestor: overlay, ); return RelativeRect.fromRect( Rect.fromLTRB(topLeft.dx, topLeft.dy, bottomRight.dx, bottomRight.dy), Offset.zero & overlay.size, ); } Widget _buildCategoryMenuItem({ required BuildContext context, required String label, required IconData icon, required Color color, required bool selected, VoidCallback? onEditPressed, }) { final AppPalette palette = _paletteOf(context); return AnimatedContainer( duration: const Duration(milliseconds: 160), padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: selected ? color.withValues(alpha: 0.05) : Colors.transparent, borderRadius: BorderRadius.circular(12), border: Border.all( color: selected ? color.withValues(alpha: 0.42) : Colors.transparent, width: 1, ), ), child: Row( children: [ Icon(icon, color: color, size: 20), const SizedBox(width: 10), Expanded( child: Text( label, overflow: TextOverflow.ellipsis, style: TextStyle( color: selected ? palette.textPrimary : palette.textSecondary, fontWeight: selected ? FontWeight.w600 : FontWeight.w500, ), ), ), const SizedBox(width: 8), if (onEditPressed != null) IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), icon: Icon( Icons.more_vert, size: 18, color: palette.textSecondary, ), onPressed: onEditPressed, ), ], ), ); } AppPalette _paletteOf(BuildContext context) { return Theme.of(context).extension() ?? AppPalette.fromBrightness(Theme.of(context).brightness); }