From 6f39bbf9e28fd118ca966b5b505946233719664c Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:41:27 -0500 Subject: [PATCH] fix: mover toggle dia/noche al navbar como icono - Icono sol/luna con animacion de rotacion en navbar - Navbar cambia color segun el tema (animado) - Quitar toggle del sidebar Co-Authored-By: Claude Sonnet 4.6 --- lib/ui/shared/navbar.dart | 203 ++++++++++++++++++++++--------------- lib/ui/shared/sidebar.dart | 75 -------------- 2 files changed, 119 insertions(+), 159 deletions(-) diff --git a/lib/ui/shared/navbar.dart b/lib/ui/shared/navbar.dart index e0d155f..3f6dc6c 100644 --- a/lib/ui/shared/navbar.dart +++ b/lib/ui/shared/navbar.dart @@ -26,14 +26,15 @@ class Navbar extends StatelessWidget { return Consumer(builder: (context, scoreProvider, _) { final reputation = scoreProvider.reputation; - return Container( + return AnimatedContainer( + duration: const Duration(milliseconds: 250), width: double.infinity, height: 56, decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, boxShadow: [ BoxShadow( - color: isDark ? Colors.black38 : Colors.black12, + color: isDark ? Colors.black45 : Colors.black12, blurRadius: 6, ), ], @@ -43,7 +44,7 @@ class Navbar extends StatelessWidget { if (size.width <= 700) IconButton( icon: Icon(Icons.menu_outlined, - color: isDark ? Colors.white70 : Colors.black87), + color: isDark ? Colors.white70 : Colors.black54), onPressed: () => SideMenuProvider.openMenu(), ), const SizedBox(width: 8), @@ -67,13 +68,32 @@ class Navbar extends StatelessWidget { ], ), - const SizedBox(width: 8), - - // Campana de notificaciones - _NotificationBell(isDark: isDark), - const SizedBox(width: 4), + // Toggle día / noche + Tooltip( + message: isDark ? 'Modo día' : 'Modo noche', + child: IconButton( + onPressed: themeProvider.toggle, + icon: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, anim) => RotationTransition( + turns: anim, + child: FadeTransition(opacity: anim, child: child), + ), + child: Icon( + isDark ? Icons.light_mode_rounded : Icons.dark_mode_rounded, + key: ValueKey(isDark), + color: isDark ? Colors.amber : const Color(0xFF64748B), + size: 22, + ), + ), + ), + ), + + // Campana + _NotificationBell(isDark: isDark), + // Avatar const NavbarAvatar(), const SizedBox(width: 12), @@ -92,10 +112,9 @@ class _NotificationBell extends StatelessWidget { Widget build(BuildContext context) { final professionalProvider = Provider.of(context); - return Consumer(builder: (context, servicesProvider, _) { - // Cuenta servicios pendientes según el modo - final pending = servicesProvider.services - .where((s) => s.service.status.index == 0) // ServiceStatus.pending + return Consumer(builder: (context, sp, _) { + final pending = sp.services + .where((s) => s.service.status.index == 0) .length; return Stack( @@ -106,15 +125,17 @@ class _NotificationBell extends StatelessWidget { icon: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: Icon( - pending > 0 ? Icons.notifications_active : Icons.notifications_outlined, + pending > 0 + ? Icons.notifications_active_rounded + : Icons.notifications_outlined, key: ValueKey(pending > 0), color: pending > 0 ? const Color(0xFF42A4EF) - : (isDark ? Colors.white54 : Colors.black54), + : (isDark ? Colors.white54 : const Color(0xFF64748B)), size: 22, ), ), - onPressed: () => _showNotificationsSheet(context, professionalProvider.isProModeActive, servicesProvider), + onPressed: () => _showSheet(context, professionalProvider.isProModeActive, sp), ), if (pending > 0) Positioned( @@ -144,7 +165,7 @@ class _NotificationBell extends StatelessWidget { }); } - void _showNotificationsSheet(BuildContext context, bool isPro, ServicesProvider sp) { + void _showSheet(BuildContext context, bool isPro, ServicesProvider sp) { final isDark = Theme.of(context).brightness == Brightness.dark; final pending = sp.services.where((s) => s.service.status.index == 0).toList(); @@ -154,81 +175,95 @@ class _NotificationBell extends StatelessWidget { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), - builder: (_) { - return Padding( - padding: const EdgeInsets.fromLTRB(20, 12, 20, 24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Container( - width: 36, height: 4, - decoration: BoxDecoration(color: Colors.grey[400], borderRadius: BorderRadius.circular(2)), + builder: (_) => Padding( + padding: const EdgeInsets.fromLTRB(20, 12, 20, 32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 36, height: 4, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), ), ), - const SizedBox(height: 16), - Row( - children: [ - const Icon(Icons.notifications_outlined, size: 20), - const SizedBox(width: 8), - const Text('Notificaciones', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), - const Spacer(), - if (pending.isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: const Color(0xFF42A4EF), - borderRadius: BorderRadius.circular(10), - ), - child: Text('${pending.length} nuevas', - style: const TextStyle(color: Colors.white, fontSize: 11)), - ), - ], - ), - const SizedBox(height: 12), - if (pending.isEmpty) - Padding( - padding: const EdgeInsets.symmetric(vertical: 24), - child: Center( - child: Column( - children: [ - Icon(Icons.notifications_none, size: 48, color: Colors.grey[400]), - const SizedBox(height: 8), - Text('Sin notificaciones pendientes', - style: TextStyle(color: Colors.grey[500])), - ], - ), - ), - ) - else - ...pending.take(5).map((s) => ListTile( - contentPadding: EdgeInsets.zero, - leading: Container( - width: 40, height: 40, + ), + const SizedBox(height: 16), + Row( + children: [ + Icon(Icons.notifications_outlined, + size: 20, color: isDark ? Colors.white70 : Colors.black87), + const SizedBox(width: 8), + Text('Notificaciones', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + )), + const Spacer(), + if (pending.isNotEmpty) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: const Color(0xFF42A4EF).withOpacity(0.12), - shape: BoxShape.circle, + color: const Color(0xFF42A4EF), + borderRadius: BorderRadius.circular(10), ), - child: const Icon(Icons.room_service_outlined, - color: Color(0xFF42A4EF), size: 20), + child: Text('${pending.length} nuevas', + style: const TextStyle(color: Colors.white, fontSize: 11)), ), - title: Text( - isPro ? 'Nueva solicitud de servicio' : 'Servicio pendiente', - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600), + ], + ), + const SizedBox(height: 12), + if (pending.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 28), + child: Center( + child: Column(children: [ + Icon(Icons.notifications_none, + size: 48, color: Colors.grey[400]), + const SizedBox(height: 8), + Text('Sin notificaciones pendientes', + style: TextStyle(color: Colors.grey[500])), + ]), + ), + ) + else + ...pending.take(5).map((s) => ListTile( + contentPadding: EdgeInsets.zero, + leading: Container( + width: 40, height: 40, + decoration: BoxDecoration( + color: const Color(0xFF42A4EF).withOpacity(0.12), + shape: BoxShape.circle, ), - subtitle: Text( - s.service.address.isNotEmpty ? s.service.address : 'Sin dirección', - style: const TextStyle(fontSize: 12), - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: const Icon(Icons.room_service_outlined, + color: Color(0xFF42A4EF), size: 20), + ), + title: Text( + isPro ? 'Nueva solicitud de servicio' : 'Servicio pendiente', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, ), - trailing: const Icon(Icons.circle, color: Color(0xFF42A4EF), size: 8), - )), - ], - ), - ); - }, + ), + subtitle: Text( + s.service.address.isNotEmpty ? s.service.address : 'Sin dirección', + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.white54 : Colors.black54, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + trailing: const Icon(Icons.circle, + color: Color(0xFF42A4EF), size: 8), + )), + ], + ), + ), ); } } diff --git a/lib/ui/shared/sidebar.dart b/lib/ui/shared/sidebar.dart index b0044b8..a4a9a88 100644 --- a/lib/ui/shared/sidebar.dart +++ b/lib/ui/shared/sidebar.dart @@ -3,7 +3,6 @@ import 'package:prosapp_web_app/providers/auth_provider.dart'; import 'package:prosapp_web_app/providers/professional_provider.dart'; import 'package:prosapp_web_app/providers/services_provider.dart'; import 'package:prosapp_web_app/providers/sidemenu_provider.dart'; -import 'package:prosapp_web_app/providers/theme_provider.dart'; import 'package:prosapp_web_app/router/router.dart'; import 'package:prosapp_web_app/services/navigation_service.dart'; import 'package:prosapp_web_app/services/notifications_service.dart'; @@ -26,7 +25,6 @@ class Sidebar extends StatelessWidget { final sideMenuProvider = Provider.of(context); final authProvider = Provider.of(context, listen: false); final professionalProvider = Provider.of(context); - final themeProvider = Provider.of(context); return Container( width: 220, @@ -190,79 +188,6 @@ class Sidebar extends StatelessWidget { onPressed: () => authProvider.logout(), ), const SizedBox(height: 20), - - // ── Toggle día / noche ── - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), - child: GestureDetector( - onTap: themeProvider.toggle, - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: themeProvider.isDark - ? const Color(0xFF1E293B) - : Colors.white.withOpacity(0.08), - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: Colors.white.withOpacity(0.12), - ), - ), - child: Row( - children: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: Icon( - themeProvider.isDark - ? Icons.light_mode_outlined - : Icons.dark_mode_outlined, - key: ValueKey(themeProvider.isDark), - color: themeProvider.isDark - ? Colors.amber - : const Color(0xFF8BAFD4), - size: 18, - ), - ), - const SizedBox(width: 10), - Text( - themeProvider.isDark ? 'Modo día' : 'Modo noche', - style: const TextStyle( - color: Color(0xFF8BAFD4), - fontSize: 13, - ), - ), - const Spacer(), - AnimatedContainer( - duration: const Duration(milliseconds: 250), - width: 36, - height: 20, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: themeProvider.isDark - ? const Color(0xFF42A4EF) - : Colors.white.withOpacity(0.2), - ), - child: AnimatedAlign( - duration: const Duration(milliseconds: 250), - alignment: themeProvider.isDark - ? Alignment.centerRight - : Alignment.centerLeft, - child: Container( - margin: const EdgeInsets.all(3), - width: 14, - height: 14, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - ), - ), - ), - ], - ), - ), - ), - ), ], ), );