import 'package:flutter/material.dart'; import 'package:sizer/sizer.dart'; import '../../core/app_export.dart'; import '../../models/route_model.dart'; import '../../services/app_state_service.dart'; import '../../services/transportation_service.dart'; import '../../widgets/custom_app_bar.dart'; import '../../widgets/custom_bottom_bar.dart'; import '../../widgets/route_selection_bottom_sheet.dart'; import './widgets/empty_state_widget.dart'; import './widgets/notification_badge_widget.dart'; import './widgets/route_selection_card.dart'; import './widgets/schedule_card.dart'; import './widgets/search_bar_widget.dart'; class SchedulesScreen extends StatefulWidget { const SchedulesScreen({super.key}); @override State createState() => _SchedulesScreenState(); } class _SchedulesScreenState extends State with TickerProviderStateMixin { int _currentBottomIndex = 1; // Schedules tab String _searchQuery = ''; final ScrollController _scrollController = ScrollController(); final GlobalKey _refreshIndicatorKey = GlobalKey(); // Animation controllers late AnimationController _fadeAnimationController; late Animation _fadeAnimation; // Service and data final TransportationService _transportationService = TransportationService(); final AppStateService _appStateService = AppStateService(); List _routes = []; List> _schedules = []; bool _isLoadingSchedules = false; String _currentScheduleType = 'weekday'; // Notification state final Map _notificationStates = {}; int _activeNotifications = 0; @override void initState() { super.initState(); _initializeAnimations(); _appStateService.addListener(_onGlobalStateChanged); _loadInitialData(); _determineCurrentScheduleType(); } @override void dispose() { _appStateService.removeListener(_onGlobalStateChanged); _fadeAnimationController.dispose(); _scrollController.dispose(); super.dispose(); } void _initializeAnimations() { _fadeAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _fadeAnimationController, curve: Curves.easeInOut, )); } void _onGlobalStateChanged() { // React to global route selection changes if (mounted) { _loadSchedulesForSelectedRoute(); } } void _determineCurrentScheduleType() { // Use Panama timezone as specified in requirements final panamaNow = DateTime.now().toUtc().add(Duration(hours: -5)); // Panama is UTC-5 final dayOfWeek = panamaNow.weekday; // Monday = 1, Sunday = 7 if (dayOfWeek >= 1 && dayOfWeek <= 5) { _currentScheduleType = 'weekday'; } else if (dayOfWeek == 6) { _currentScheduleType = 'saturday'; } else { _currentScheduleType = 'sunday'; } } Future _loadInitialData() async { // Check if we have routes loaded globally if (_appStateService.allRoutes.isEmpty && !_appStateService.isLoadingRoutes) { await _appStateService.loadRoutes(); } // Show toast if no routes found if (_appStateService.allRoutes.isEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { _appStateService.showNoRoutesToast(context); }); } // Update local routes reference setState(() { _routes = _appStateService.allRoutes; }); // Load schedules for currently selected route await _loadSchedulesForSelectedRoute(); } Future _loadSchedulesForSelectedRoute() async { final selectedRouteId = _appStateService.selectedRouteId; if (selectedRouteId == null) { setState(() { _schedules = []; _isLoadingSchedules = false; }); return; } setState(() { _isLoadingSchedules = true; }); try { final schedules = await _transportationService.getRouteTimetablesByScheduleType( selectedRouteId, _currentScheduleType, ); setState(() { _schedules = schedules; _isLoadingSchedules = false; }); _fadeAnimationController.forward(); } catch (e) { setState(() { _isLoadingSchedules = false; _schedules = []; }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error loading schedules: ${e.toString()}'), backgroundColor: Colors.red, duration: const Duration(seconds: 3), ), ); } } } List get _filteredRoutes { if (_searchQuery.isEmpty) return _routes; return _routes.where((route) { final name = route.name.toLowerCase(); final query = _searchQuery.toLowerCase(); return name.contains(query); }).toList(); } List> get _filteredSchedules { if (_searchQuery.isEmpty) return _schedules; return _schedules.where((schedule) { final departureTime = (schedule['departure_time'] as String).toLowerCase(); final query = _searchQuery.toLowerCase(); return departureTime.contains(query); }).toList(); } void _onBottomNavTap(int index) { setState(() { _currentBottomIndex = index; }); } Future _onRouteSelected(String routeId) async { await _appStateService.selectRoute(routeId); setState(() { _searchQuery = ''; }); } void _onSearchChanged(String query) { setState(() { _searchQuery = query; }); } void _onSearchClear() { setState(() { _searchQuery = ''; }); } void _toggleNotification(String scheduleId) { setState(() { final currentState = _notificationStates[scheduleId] ?? false; _notificationStates[scheduleId] = !currentState; if (!currentState) { _activeNotifications++; } else { _activeNotifications--; } }); } Future _onRefresh() async { await _appStateService.refreshRoutes(); setState(() { _routes = _appStateService.allRoutes; }); await _loadSchedulesForSelectedRoute(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Horarios actualizados'), backgroundColor: AppTheme.lightTheme.colorScheme.tertiary, duration: const Duration(seconds: 2), ), ); } } void _showRouteSelector() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) => RouteSelectionBottomSheet( title: 'Seleccionar Ruta', onRouteChanged: () { // The global state listener will handle the reload }, ), ); } void _showScheduleContextMenu(String scheduleId, String departureTime) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) => Container( decoration: BoxDecoration( color: AppTheme.lightTheme.colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), ), child: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 12.w, height: 0.5.h, margin: EdgeInsets.symmetric(vertical: 2.h), decoration: BoxDecoration( color: AppTheme.lightTheme.colorScheme.outline, borderRadius: BorderRadius.circular(2), ), ), Padding( padding: EdgeInsets.symmetric(horizontal: 4.w), child: Text( 'Opciones para $departureTime', style: AppTheme.lightTheme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), ), SizedBox(height: 2.h), _buildContextMenuItem( icon: 'alarm_add', title: 'Configurar Recordatorio', onTap: () { Navigator.pop(context); _showReminderDialog(scheduleId, departureTime); }, ), _buildContextMenuItem( icon: 'share', title: 'Compartir Horario', onTap: () { Navigator.pop(context); _shareSchedule(departureTime); }, ), _buildContextMenuItem( icon: 'report_problem', title: 'Reportar Problema', onTap: () { Navigator.pop(context); _reportIssue(scheduleId); }, ), SizedBox(height: 2.h), ], ), ), ), ); } Widget _buildContextMenuItem({ required String icon, required String title, required VoidCallback onTap, }) { return ListTile( leading: CustomIconWidget( iconName: icon, color: AppTheme.lightTheme.colorScheme.onSurface, size: 24, ), title: Text( title, style: AppTheme.lightTheme.textTheme.bodyLarge, ), onTap: onTap, ); } void _showReminderDialog(String scheduleId, String departureTime) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Configurar Recordatorio'), content: Text( '¿Deseas recibir una notificación antes de la salida de las $departureTime?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancelar'), ), ElevatedButton( onPressed: () { Navigator.pop(context); _toggleNotification(scheduleId); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Recordatorio configurado'), duration: Duration(seconds: 2), ), ); }, child: const Text('Confirmar'), ), ], ), ); } void _shareSchedule(String departureTime) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Compartiendo horario de $departureTime'), duration: const Duration(seconds: 2), ), ); } void _reportIssue(String scheduleId) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Reporte enviado. Gracias por tu feedback.'), duration: Duration(seconds: 2), ), ); } String _getScheduleTypeDisplayName() { switch (_currentScheduleType) { case 'weekday': return 'Lunes a Viernes'; case 'saturday': return 'Sábado'; case 'sunday': return 'Domingo'; default: return 'Horario'; } } Widget _buildRouteSelectionView() { return Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ CustomIconWidget( iconName: 'schedule', color: AppTheme.lightTheme.colorScheme.secondary, size: 20, ), SizedBox(width: 2.w), Text( 'Horarios para ${_getScheduleTypeDisplayName()}', style: AppTheme.lightTheme.textTheme.bodyMedium?.copyWith( color: AppTheme.lightTheme.colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), ], ), ), SearchBarWidget( hintText: 'Buscar rutas...', onChanged: _onSearchChanged, onClear: _onSearchClear, ), Expanded( child: _appStateService.isLoadingRoutes ? const Center(child: CircularProgressIndicator()) : _filteredRoutes.isEmpty ? const EmptyStateWidget( title: 'No se encontraron rutas', subtitle: 'Intenta con otros términos de búsqueda', ) : ListView.builder( controller: _scrollController, itemCount: _filteredRoutes.length, itemBuilder: (context, index) { final route = _filteredRoutes[index]; return RouteSelectionCard( routeName: route.displayName, duration: route.direction, onTap: () => _onRouteSelected(route.id), ); }, ), ), ], ); } Widget _buildScheduleView() { final selectedRoute = _appStateService.getSelectedRoute(); final selectedRouteName = _appStateService.selectedRouteName; if (selectedRoute == null || selectedRouteName == null) { return const Center( child: Text('No route selected'), ); } return Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h), decoration: BoxDecoration( color: AppTheme.lightTheme.colorScheme.surface, border: Border( bottom: BorderSide( color: AppTheme.lightTheme.colorScheme.outline, width: 1, ), ), ), child: Row( children: [ GestureDetector( onTap: () { _appStateService.clearSelectedRoute(); setState(() { _searchQuery = ''; _schedules.clear(); }); }, child: Container( padding: EdgeInsets.all(2.w), decoration: BoxDecoration( color: AppTheme.lightTheme.colorScheme.secondary .withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: CustomIconWidget( iconName: 'arrow_back', color: AppTheme.lightTheme.colorScheme.primary, size: 20, ), ), ), SizedBox(width: 3.w), Expanded( child: GestureDetector( onTap: _showRouteSelector, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( selectedRouteName, style: AppTheme.lightTheme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), Text( '${_getScheduleTypeDisplayName()} • ${selectedRoute.direction}', style: AppTheme.lightTheme.textTheme.bodySmall?.copyWith( color: AppTheme.lightTheme.colorScheme.onSurfaceVariant, ), ), ], ), ), ), ], ), ), SearchBarWidget( hintText: 'Buscar horarios...', onChanged: _onSearchChanged, onClear: _onSearchClear, ), Expanded( child: RefreshIndicator( key: _refreshIndicatorKey, onRefresh: _onRefresh, color: AppTheme.lightTheme.colorScheme.secondary, child: _isLoadingSchedules ? const Center(child: CircularProgressIndicator()) : _filteredSchedules.isEmpty ? const EmptyStateWidget( title: 'No hay horarios disponibles', subtitle: 'Intenta actualizar o selecciona otra ruta', ) : FadeTransition( opacity: _fadeAnimation, child: ListView.builder( controller: _scrollController, itemCount: _filteredSchedules.length, itemBuilder: (context, index) { final schedule = _filteredSchedules[index]; final scheduleId = schedule['id'].toString(); final departureTime = schedule['departure_time'] as String; final timeParts = departureTime.split(':'); final hour = timeParts[0]; final minute = timeParts[1]; final formattedTime = '$hour:$minute'; return ScheduleCard( departureTime: formattedTime, duration: selectedRoute.direction, arrivalTime: '', isNotificationEnabled: _notificationStates[scheduleId] ?? false, onNotificationToggle: () => _toggleNotification(scheduleId), onLongPress: () => _showScheduleContextMenu( scheduleId, formattedTime, ), ); }, ), ), ), ), ], ); } @override Widget build(BuildContext context) { final hasSelectedRoute = _appStateService.hasSelectedRoute; return Scaffold( backgroundColor: AppTheme.lightTheme.scaffoldBackgroundColor, appBar: CustomAppBar( title: 'Horarios', actions: [ NotificationBadgeWidget( count: _activeNotifications, child: IconButton( icon: CustomIconWidget( iconName: 'notifications', color: AppTheme.lightTheme.colorScheme.onSurface, size: 24, ), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Tienes $_activeNotifications notificaciones activas'), duration: const Duration(seconds: 2), ), ); }, ), ), ], ), body: SafeArea( child: !hasSelectedRoute ? _buildRouteSelectionView() : _buildScheduleView(), ), bottomNavigationBar: CustomBottomBar( currentIndex: _currentBottomIndex, onTap: _onBottomNavTap, ), ); } }