diff --git a/trainerbox/lib/main.dart b/trainerbox/lib/main.dart index bbe3cf7..9be86d5 100644 --- a/trainerbox/lib/main.dart +++ b/trainerbox/lib/main.dart @@ -6,12 +6,14 @@ import 'package:trainerbox/firebase_options.dart'; import 'screens/home_screen.dart'; import 'screens/login_screen.dart'; import 'screens/search_tab.dart'; +import 'package:intl/date_symbol_data_local.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + await initializeDateFormatting('de_DE', null); runApp(const MyApp()); } diff --git a/trainerbox/lib/screens/calendar_tab.dart b/trainerbox/lib/screens/calendar_tab.dart index 968b0bc..b307e69 100644 --- a/trainerbox/lib/screens/calendar_tab.dart +++ b/trainerbox/lib/screens/calendar_tab.dart @@ -4,8 +4,18 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'training_detail_screen.dart'; +const Map categoryColors = { + 'Aufwärmen & Mobilisation': Colors.deepOrange, + 'Wurf- & Torabschluss': Colors.orange, + 'Torwarttraining': Colors.green, + 'Athletik': Colors.blue, + 'Pass': Colors.purple, + 'Koordination': Colors.teal, +}; + class CalendarTab extends StatefulWidget { - const CalendarTab({super.key}); + final DateTime? initialDate; + const CalendarTab({super.key, this.initialDate}); @override State createState() => _CalendarTabState(); @@ -32,12 +42,23 @@ class _CalendarTabState extends State { @override void initState() { super.initState(); - _focusedDay = DateTime.now(); + _focusedDay = widget.initialDate ?? DateTime.now(); _selectedDay = _focusedDay; _currentUserId = FirebaseAuth.instance.currentUser?.uid; _initializeData(); } + @override + void didUpdateWidget(covariant CalendarTab oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.initialDate != null && widget.initialDate != oldWidget.initialDate) { + setState(() { + _focusedDay = widget.initialDate!; + _selectedDay = widget.initialDate!; + }); + } + } + Future _initializeData() async { if (_currentUserId != null) { final userDoc = await FirebaseFirestore.instance @@ -451,7 +472,7 @@ class _CalendarTabState extends State { ListTile( leading: Icon( Icons.sports, - color: isCurrentUser ? Colors.blue : null, + color: categoryColors[event['day']] ?? (isCurrentUser ? Colors.blue : Colors.grey), ), title: Text( isCurrentUser ? 'Training' : event['trainerName'], diff --git a/trainerbox/lib/screens/home_screen.dart b/trainerbox/lib/screens/home_screen.dart index 6ab8aa5..a696ad0 100644 --- a/trainerbox/lib/screens/home_screen.dart +++ b/trainerbox/lib/screens/home_screen.dart @@ -1,7 +1,9 @@ // screens/home_screen.dart // Enthält die BottomNavigationBar-Logik und Navigation zwischen den Hauptscreens import 'package:flutter/material.dart'; -import 'home_tab.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'; @@ -17,14 +19,142 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _selectedIndex = 0; + Map? _nextTraining; + bool _isLoading = true; + DateTime? _calendarInitialDate; - List get _screens => [ - const HomeTab(), - const SearchTab(), - const FavoritesTab(), - const CalendarTab(), - ProfileTab(onLogoutSuccess: widget.onLogoutSuccess), - ]; + @override + void initState() { + super.initState(); + _loadNextTraining(); + } + + 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) { setState(() { @@ -32,6 +162,14 @@ class _HomeScreenState extends State { }); } + List get _screens => [ + _buildHomeTab(), + const SearchTab(), + const FavoritesTab(), + CalendarTab(initialDate: _calendarInitialDate), + ProfileTab(onLogoutSuccess: widget.onLogoutSuccess), + ]; + @override Widget build(BuildContext context) { return Scaffold( @@ -59,4 +197,217 @@ class _HomeScreenState extends State { ), ); } -} \ No newline at end of file + + 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: () {}, + ), + ], + ), + SizedBox( + height: 70, + child: ListView( + scrollDirection: Axis.horizontal, + 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: () {}, + ), + ], + ), + SizedBox( + height: 170, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + _buildSuggestionCard('Wurf- & Torabschluss', 'Sprungwurf', Icons.sports_handball, categoryColors['Wurf- & Torabschluss']!), + _buildSuggestionCard('Pass', 'Doppelpass', Icons.compare_arrows, categoryColors['Pass']!), + _buildSuggestionCard('Koordination', 'Leiterlauf', Icons.directions_walk, categoryColors['Koordination']!), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildFavoriteCircle(String label, IconData icon, Color color) { + return Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Column( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: color.withOpacity(0.2), + child: Icon(icon, color: color, size: 28), + ), + const SizedBox(height: 4), + Text(label, style: const TextStyle(fontSize: 12)), + ], + ), + ); + } + + Widget _buildSuggestionCard(String category, String title, IconData icon, Color color) { + return Container( + width: 130, + margin: const EdgeInsets.only(right: 16), + child: Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + elevation: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 80, + width: double.infinity, + decoration: BoxDecoration( + color: color.withOpacity(0.15), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Center( + child: Icon(icon, color: color, size: 40), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(category, style: const TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 4), + Text(title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + ], + ), + ), + ], + ), + ), + ); + } +} + +const Map categoryColors = { + 'Aufwärmen & Mobilisation': Colors.deepOrange, + 'Wurf- & Torabschluss': Colors.orange, + 'Torwarttraining': Colors.green, + 'Athletik': Colors.blue, + 'Pass': Colors.purple, + 'Koordination': Colors.teal, +}; \ No newline at end of file