// screens/home_screen.dart // Enthält die BottomNavigationBar-Logik und Navigation zwischen den Hauptscreens import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:intl/intl.dart'; import 'search_tab.dart'; import 'favorites_tab.dart'; import 'calendar_tab.dart'; import 'profile_tab.dart'; import 'training_detail_screen.dart'; class HomeScreen extends StatefulWidget { final VoidCallback? onLogoutSuccess; const HomeScreen({super.key, this.onLogoutSuccess}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { int _selectedIndex = 0; Map? _nextTraining; bool _isLoading = true; DateTime? _calendarInitialDate; String? _favoriteCategoryFilter; List> _suggestions = []; bool _isLoadingSuggestions = true; String? _suggestionsError; @override void initState() { super.initState(); _loadNextTraining(); _loadSuggestions(); } Future _loadSuggestions() async { setState(() { _isLoadingSuggestions = true; _suggestionsError = null; }); try { final user = FirebaseAuth.instance.currentUser; if (user == null) { setState(() => _isLoadingSuggestions = false); return; } // 1. Hole alle vom User genutzten Übungs-IDs final userDoc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get(); final Set usedExerciseIds = {}; if (userDoc.exists) { final userData = userDoc.data() as Map; final trainings = Map.from(userData['trainings'] ?? {}); trainings.forEach((date, trainingsList) { final list = List>.from(trainingsList); for (final training in list) { final exercises = List>.from(training['exercises'] ?? []); for (final exercise in exercises) { if (exercise.containsKey('id')) { usedExerciseIds.add(exercise['id']); } } } }); } // 2. Hole alle Übungen final exercisesSnapshot = await FirebaseFirestore.instance.collection('Training').get(); final allExercises = exercisesSnapshot.docs.map((doc) { return {'id': doc.id, ...doc.data() as Map}; }).toList(); // 3. Filtere nach "nicht genutzt" und "gut bewertet" final suggestions = allExercises.where((exercise) { final isUsed = usedExerciseIds.contains(exercise['id']); final rating = (exercise['rating overall'] as num?)?.toDouble() ?? 0.0; return !isUsed && rating >= 4.0; }).toList(); suggestions.shuffle(); setState(() { _suggestions = suggestions.take(5).toList(); _isLoadingSuggestions = false; }); } catch (e) { setState(() { _isLoadingSuggestions = false; _suggestionsError = 'Fehler beim Laden.'; }); } } Future _loadNextTraining() async { setState(() => _isLoading = true); try { final user = FirebaseAuth.instance.currentUser; if (user == null) return; final userDoc = await FirebaseFirestore.instance .collection('User') .doc(user.uid) .get(); if (!userDoc.exists) return; final userData = userDoc.data() as Map; final userRole = userData['role'] as String?; final userClub = userData['club'] as String?; QuerySnapshot trainersSnapshot; if (userRole == 'trainer') { // Trainer sieht nur seine eigenen Trainings trainersSnapshot = await FirebaseFirestore.instance .collection('User') .where('role', isEqualTo: 'trainer') .where(FieldPath.documentId, isEqualTo: user.uid) .get(); } else { // Spieler sieht nur Trainings von Trainern seines Vereins if (userClub == null || userClub.isEmpty) { setState(() { _nextTraining = null; _isLoading = false; }); return; } trainersSnapshot = await FirebaseFirestore.instance .collection('User') .where('role', isEqualTo: 'trainer') .where('club', isEqualTo: userClub) .get(); } final now = DateTime.now(); DateTime? nextTrainingDate; Map? nextTraining; for (var trainerDoc in trainersSnapshot.docs) { final trainerData = trainerDoc.data() as Map; final trainingTimes = trainerData['trainingTimes'] as Map? ?? {}; final trainingDurations = trainerData['trainingDurations'] as Map? ?? {}; final cancelledTrainings = trainerData['cancelledTrainings'] as List? ?? []; trainingTimes.forEach((day, timeStr) { if (timeStr == null) return; final timeParts = (timeStr as String).split(':'); if (timeParts.length != 2) return; final hour = int.tryParse(timeParts[0]) ?? 0; final minute = int.tryParse(timeParts[1]) ?? 0; final daysUntilNext = _getDaysUntilNext(day, now.weekday); final eventDate = DateTime(now.year, now.month, now.day + daysUntilNext, hour, minute); // Prüfe die nächsten 4 Wochen for (var i = 0; i < 4; i++) { final date = eventDate.add(Duration(days: i * 7)); final normalizedDate = DateTime(date.year, date.month, date.day); final dateString = normalizedDate.toIso8601String(); final isCancelled = cancelledTrainings.any((cancelled) { if (cancelled is Map) { final cancelledDate = DateTime.parse(cancelled['date'] as String); return isSameDay(cancelledDate, normalizedDate); } return false; }); if (!isCancelled && (nextTrainingDate == null || date.isBefore(nextTrainingDate!))) { nextTrainingDate = date; nextTraining = { 'date': dateString, 'time': timeStr, 'duration': trainingDurations[day] ?? 60, 'trainerName': trainerData['name'] ?? 'Unbekannter Trainer', 'day': day, }; } } }); } setState(() { _nextTraining = nextTraining; _isLoading = false; }); } catch (e) { print('Error loading next training: $e'); setState(() { _isLoading = false; }); } } int _getDaysUntilNext(String day, int currentWeekday) { final weekdays = { 'Montag': 1, 'Dienstag': 2, 'Mittwoch': 3, 'Donnerstag': 4, 'Freitag': 5, 'Samstag': 6, 'Sonntag': 7, }; final targetWeekday = weekdays[day] ?? 1; var daysUntilNext = targetWeekday - currentWeekday; if (daysUntilNext <= 0) { daysUntilNext += 7; } return daysUntilNext; } bool isSameDay(DateTime a, DateTime b) { return a.year == b.year && a.month == b.month && a.day == b.day; } void _onItemTapped(int index) { if (_selectedIndex != index) { setState(() { _selectedIndex = index; }); // Wenn zum Home-Tab gewechselt wird, lade die Vorschläge neu if (index == 0) { _loadSuggestions(); } } } List get _screens => [ _buildHomeTab(), const SearchTab(), FavoritesTab(categoryFilter: _favoriteCategoryFilter), CalendarTab(initialDate: _calendarInitialDate), ProfileTab(onLogoutSuccess: widget.onLogoutSuccess), ]; @override Widget build(BuildContext context) { return Scaffold( body: _screens[_selectedIndex], bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: _selectedIndex, onTap: _onItemTapped, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suche'), BottomNavigationBarItem( icon: Icon(Icons.favorite_border), label: 'Favoriten', ), BottomNavigationBarItem( icon: Icon(Icons.calendar_today), label: 'Kalender', ), BottomNavigationBarItem( icon: Icon(Icons.person_outline), label: 'Profil', ), ], ), ); } Widget _buildHomeTab() { return SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), const Center( child: Text( 'Hallo Trainer!', style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: Color(0xFFE57399), ), ), ), const SizedBox(height: 24), GestureDetector( onTap: () { if (_nextTraining != null) { setState(() { _calendarInitialDate = DateTime.parse(_nextTraining!['date']); _selectedIndex = 3; }); } else { setState(() => _selectedIndex = 3); } }, child: Stack( children: [ Container( height: 140, width: double.infinity, decoration: BoxDecoration( color: Colors.pink[100], borderRadius: BorderRadius.circular(20), ), child: const Center( child: Icon(Icons.sports_handball, size: 80, color: Colors.white70), ), ), Positioned( left: 20, bottom: 20, child: Text( 'Dein nächstes Training', style: TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, shadows: [ Shadow( blurRadius: 8, color: Colors.black.withOpacity(0.5), offset: Offset(2, 2), ), ], ), ), ), if (_isLoading) const Positioned.fill( child: Center(child: CircularProgressIndicator()), ) else if (_nextTraining != null) Positioned( right: 20, bottom: 20, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(12), ), child: Text( DateFormat('EEEE, dd.MM.yyyy', 'de_DE').format( DateTime.parse(_nextTraining!['date']), ), style: const TextStyle(color: Colors.white, fontSize: 14), ), ), ), ], ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Favoriten', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.chevron_right), onPressed: () {}, ), ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFavoriteCircle('Aufwärmen & Mobilisation', Icons.directions_run, categoryColors['Aufwärmen & Mobilisation']!), _buildFavoriteCircle('Wurf- & Torabschluss', Icons.sports_handball, categoryColors['Wurf- & Torabschluss']!), _buildFavoriteCircle('Torwarttraining', Icons.sports, categoryColors['Torwarttraining']!), _buildFavoriteCircle('Athletik', Icons.fitness_center, categoryColors['Athletik']!), _buildFavoriteCircle('Pass', Icons.compare_arrows, categoryColors['Pass']!), _buildFavoriteCircle('Koordination', Icons.directions_walk, categoryColors['Koordination']!), ], ), ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Vorschläge', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.chevron_right), onPressed: () { setState(() => _selectedIndex = 1); }, ), ], ), if (_isLoadingSuggestions) const SizedBox( height: 170, child: Center(child: CircularProgressIndicator()), ) else if (_suggestionsError != null) SizedBox( height: 170, child: Center(child: Text(_suggestionsError!)), ) else if (_suggestions.isEmpty) const SizedBox( height: 170, child: Center(child: Text('Keine neuen Vorschläge gefunden.')), ) else SizedBox( height: 170, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _suggestions.length, itemBuilder: (context, index) { return _buildSuggestionCard(_suggestions[index]); }, ), ), ], ), ), ); } Widget _buildFavoriteCircle(String label, IconData icon, Color color) { return Padding( padding: const EdgeInsets.only(right: 16.0), child: GestureDetector( onTap: () { setState(() { _favoriteCategoryFilter = label; _selectedIndex = 2; }); }, child: Column( children: [ CircleAvatar( radius: 24, backgroundColor: color.withOpacity(0.2), child: Icon(icon, color: color, size: 26), ), const SizedBox(height: 4), SizedBox( width: 60, child: Text( label, style: const TextStyle(fontSize: 12), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ), ); } Widget _buildSuggestionCard(Map exercise) { final category = exercise['category'] as String? ?? 'Sonstiges'; final title = exercise['title'] as String? ?? 'Unbekannte Übung'; final id = exercise['id'] as String; final color = categoryColors[category] ?? Colors.grey; final icon = categoryIcons[category] ?? Icons.help_outline; return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => TrainingDetailScreen(trainingId: id), ), ); }, child: Container( width: 130, margin: const EdgeInsets.only(right: 16), child: Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 3, clipBehavior: Clip.antiAlias, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 80, width: double.infinity, color: color.withOpacity(0.15), child: Center( child: Icon(icon, color: color, size: 40), ), ), Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( category, style: const TextStyle(fontSize: 12, color: Colors.grey), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), ), ], ), ), ), ); } } const Map categoryIcons = { 'Aufwärmen & Mobilisation': Icons.directions_run, 'Wurf- & Torabschluss': Icons.sports_handball, 'Torwarttraining': Icons.sports, 'Athletik': Icons.fitness_center, 'Pass': Icons.compare_arrows, 'Koordination': Icons.directions_walk, }; const Map categoryColors = { 'Aufwärmen & Mobilisation': Colors.deepOrange, 'Wurf- & Torabschluss': Colors.orange, 'Torwarttraining': Colors.green, 'Athletik': Colors.blue, 'Pass': Colors.purple, 'Koordination': Colors.teal, };