From 9a8561e45e9d06b8cf6b79bf2fb06854ec136a9b Mon Sep 17 00:00:00 2001 From: joschy2002 Date: Wed, 25 Jun 2025 18:20:06 +0200 Subject: [PATCH] Commented Code --- trainerbox/lib/main.dart | 15 +++++- trainerbox/lib/models/exercise.dart | 17 ++++-- trainerbox/lib/screens/calendar_tab.dart | 25 ++++++--- trainerbox/lib/screens/favorites_tab.dart | 11 ++-- trainerbox/lib/screens/home_screen.dart | 34 ++++++++---- trainerbox/lib/screens/home_tab.dart | 13 +++-- trainerbox/lib/screens/login_screen.dart | 43 +++++++++------ trainerbox/lib/screens/profile_tab.dart | 53 ++++++++++++++++--- trainerbox/lib/screens/search_tab.dart | 39 +++++++++++++- .../lib/screens/training_detail_screen.dart | 30 ++++++++--- trainerbox/lib/utils/responsive.dart | 14 +++++ trainerbox/lib/widgets/category_circle.dart | 8 +++ trainerbox/lib/widgets/exercise_card.dart | 10 ++++ 13 files changed, 254 insertions(+), 58 deletions(-) diff --git a/trainerbox/lib/main.dart b/trainerbox/lib/main.dart index 9be86d5..97a7c76 100644 --- a/trainerbox/lib/main.dart +++ b/trainerbox/lib/main.dart @@ -1,5 +1,7 @@ // main.dart -// Einstiegspunkt der App und globale Konfigurationen +// Entry point of the app and global configuration. +// This file initializes Firebase, sets up localization, and launches the main app widget. + import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:trainerbox/firebase_options.dart'; @@ -8,15 +10,21 @@ import 'screens/login_screen.dart'; import 'screens/search_tab.dart'; import 'package:intl/date_symbol_data_local.dart'; +/// Main entry point for the Flutter application. void main() async { + // Ensures that widget binding is initialized before using platform channels. WidgetsFlutterBinding.ensureInitialized(); + // Initialize Firebase with platform-specific options. await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + // Initialize date formatting for German locale. await initializeDateFormatting('de_DE', null); + // Start the app. runApp(const MyApp()); } +/// The root widget of the application. class MyApp extends StatefulWidget { const MyApp({super.key}); @@ -24,15 +32,18 @@ class MyApp extends StatefulWidget { State createState() => _MyAppState(); } +/// State for the main app widget, manages login state and routing. class _MyAppState extends State { bool _loggedIn = false; + /// Called when login is successful. void _handleLoginSuccess() { setState(() { _loggedIn = true; }); } + /// Called when logout is successful. void _handleLogoutSuccess() { setState(() { _loggedIn = false; @@ -48,9 +59,11 @@ class _MyAppState extends State { colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), + // Show HomeScreen if logged in, otherwise show LoginScreen. home: _loggedIn ? HomeScreen(onLogoutSuccess: _handleLogoutSuccess) : LoginScreen(onLoginSuccess: _handleLoginSuccess), + // Define named routes for navigation. routes: { '/search': (context) => SearchTab( selectMode: (ModalRoute.of(context)?.settings.arguments as Map?)?['selectMode'] ?? false, diff --git a/trainerbox/lib/models/exercise.dart b/trainerbox/lib/models/exercise.dart index c2556a7..f0cc6c6 100644 --- a/trainerbox/lib/models/exercise.dart +++ b/trainerbox/lib/models/exercise.dart @@ -1,13 +1,24 @@ // models/exercise.dart +// Data model for a training exercise // Datenmodell für eine Trainingsübung import 'package:flutter/material.dart'; +/// Represents a training exercise with a title, category, and icon. +/// Stellt eine Trainingsübung mit Titel, Kategorie und Icon dar. class Exercise { - final String title; // Name der Übung - final String category; // Kategorie der Übung - final IconData icon; // Icon zur Darstellung + /// The name/title of the exercise. + // Name der Übung + final String title; + /// The category of the exercise. + // Kategorie der Übung + final String category; + /// The icon used to visually represent the exercise. + // Icon zur Darstellung + final IconData icon; + /// Creates an Exercise instance. + /// Erstellt eine neue Instanz einer Übung. Exercise({ required this.title, required this.category, diff --git a/trainerbox/lib/screens/calendar_tab.dart b/trainerbox/lib/screens/calendar_tab.dart index c04c911..659d45a 100644 --- a/trainerbox/lib/screens/calendar_tab.dart +++ b/trainerbox/lib/screens/calendar_tab.dart @@ -1,3 +1,6 @@ +// calendar_tab.dart +// This file contains the CalendarTab widget, which displays a calendar view of trainings and allows users to view, add, and manage training events. + import 'package:flutter/material.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; @@ -5,6 +8,7 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'training_detail_screen.dart'; import 'package:uuid/uuid.dart'; +/// Mapping of training categories to colors for calendar display. const Map categoryColors = { 'Aufwärmen & Mobilisation': Colors.deepOrange, 'Wurf- & Torabschluss': Colors.orange, @@ -14,6 +18,7 @@ const Map categoryColors = { 'Koordination': Colors.teal, }; +/// The CalendarTab displays a calendar with all training events for the user. class CalendarTab extends StatefulWidget { final DateTime? initialDate; const CalendarTab({super.key, this.initialDate}); @@ -22,6 +27,7 @@ class CalendarTab extends StatefulWidget { State createState() => _CalendarTabState(); } +/// State for the CalendarTab, manages event loading, calendar state, and user interactions. class _CalendarTabState extends State { CalendarFormat _calendarFormat = CalendarFormat.week; late DateTime _focusedDay; @@ -60,6 +66,7 @@ class _CalendarTabState extends State { } } + /// Initializes user data and loads events for the calendar. Future _initializeData() async { if (_currentUserId != null) { final userDoc = await FirebaseFirestore.instance @@ -76,6 +83,7 @@ class _CalendarTabState extends State { } } + /// Loads all training events for the current user (trainer or player). Future _loadEvents() async { if (_userRole == null) return; @@ -84,12 +92,14 @@ class _CalendarTabState extends State { QuerySnapshot trainersSnapshot; if (_userRole == 'trainer') { + // Trainer: only their own trainings trainersSnapshot = await FirebaseFirestore.instance .collection('User') .where('role', isEqualTo: 'trainer') .where(FieldPath.documentId, isEqualTo: _currentUserId) .get(); } else { + // Player: trainings from trainers in their club final userDoc = await FirebaseFirestore.instance .collection('User') .doc(_currentUserId) @@ -127,7 +137,7 @@ class _CalendarTabState extends State { final trainingTimes = trainerData['trainingTimes'] as Map? ?? {}; final trainingDurations = trainerData['trainingDurations'] as Map? ?? {}; - // Lade alle Trainings für das Datum + // Load all specific trainings for each date trainings.forEach((dateString, trainingsList) { final date = DateTime.tryParse(dateString); if (date == null) return; @@ -144,6 +154,7 @@ class _CalendarTabState extends State { return sum; }); + // Build the event map for this training final event = { 'trainerName': trainerData['name'] ?? 'Unbekannter Trainer', 'time': timeStr, @@ -165,7 +176,7 @@ class _CalendarTabState extends State { } }); - // Füge regelmäßige Trainings hinzu + // Add recurring weekly trainings for the year final now = DateTime.now(); final yearStart = DateTime(now.year, 1, 1); final yearEnd = DateTime(now.year + 1, 1, 1); @@ -179,7 +190,7 @@ class _CalendarTabState extends State { 'Sonntag': 7, }; - // Erstelle eine Set von gecancelten Daten für schnelleren Zugriff + // Create a set of cancelled dates for fast lookup final cancelledDates = cancelledTrainings .where((cancelled) => cancelled is Map) .map((cancelled) => DateTime.parse((cancelled as Map)['date'] as String)) @@ -195,23 +206,23 @@ class _CalendarTabState extends State { final duration = trainingDurations[day] ?? 60; final targetWeekday = weekdays[day] ?? 1; - // Finde das erste gewünschte Wochentags-Datum ab Jahresanfang + // Find the first desired weekday date from the start of the year DateTime firstDate = yearStart; while (firstDate.weekday != targetWeekday) { firstDate = firstDate.add(const Duration(days: 1)); } - // Generiere Trainings für das gesamte Jahr + // Generate weekly trainings for the whole year while (firstDate.isBefore(yearEnd)) { final normalizedDate = DateTime(firstDate.year, firstDate.month, firstDate.day); final dateString = normalizedDate.toIso8601String(); - // Prüfe, ob das Training für dieses Datum gecancelt ist + // Check if the training for this date is cancelled final isCancelled = cancelledDates.any((cancelledDate) => isSameDay(cancelledDate, normalizedDate) ); - // Prüfe, ob bereits ein spezifisches Training für dieses Datum existiert + // Check if a specific training already exists for this date final hasSpecificTraining = trainings.containsKey(dateString); if (!isCancelled && !hasSpecificTraining) { diff --git a/trainerbox/lib/screens/favorites_tab.dart b/trainerbox/lib/screens/favorites_tab.dart index 8e91bf8..d764d2c 100644 --- a/trainerbox/lib/screens/favorites_tab.dart +++ b/trainerbox/lib/screens/favorites_tab.dart @@ -1,8 +1,12 @@ +// favorites_tab.dart +// This file contains the FavoritesTab widget, which displays the user's favorite exercises and allows filtering by category. + import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'training_detail_screen.dart'; +/// The FavoritesTab displays the user's favorite exercises and allows filtering by category. class FavoritesTab extends StatefulWidget { final String? categoryFilter; const FavoritesTab({super.key, this.categoryFilter}); @@ -11,6 +15,7 @@ class FavoritesTab extends StatefulWidget { State createState() => _FavoritesTabState(); } +/// State for the FavoritesTab, manages category selection and favorite loading. class _FavoritesTabState extends State { static const List _categories = [ 'Aufwärmen & Mobilisation', @@ -42,7 +47,7 @@ class _FavoritesTabState extends State { body: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Filter-Chip-Leiste + // Category filter chips Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Wrap( @@ -84,7 +89,7 @@ class _FavoritesTabState extends State { return const Center(child: Text('Keine Favoriten gefunden')); } - // Lade alle Favoriten-Dokumente auf einmal + // Load all favorite exercise documents at once return FutureBuilder>( future: Future.wait(allFavorites.map((id) => FirebaseFirestore.instance.collection('Training').doc(id).get() @@ -97,7 +102,7 @@ class _FavoritesTabState extends State { return const Center(child: Text('Favoriten konnten nicht geladen werden')); } - // Filtere die Favoriten basierend auf der Kategorie + // Filter favorites by selected category final filteredFavorites = multiSnapshot.data!.where((doc) { if (!doc.exists) return false; if (_selectedCategory == null) return true; diff --git a/trainerbox/lib/screens/home_screen.dart b/trainerbox/lib/screens/home_screen.dart index d8251b4..ca98618 100644 --- a/trainerbox/lib/screens/home_screen.dart +++ b/trainerbox/lib/screens/home_screen.dart @@ -1,5 +1,6 @@ // screens/home_screen.dart -// Enthält die BottomNavigationBar-Logik und Navigation zwischen den Hauptscreens +// This file contains the main HomeScreen widget, which manages the bottom navigation bar and navigation between the main screens of the app. + import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; @@ -10,6 +11,7 @@ import 'calendar_tab.dart'; import 'profile_tab.dart'; import 'training_detail_screen.dart'; +/// The main home screen of the app, containing the bottom navigation bar and all main tabs. class HomeScreen extends StatefulWidget { final VoidCallback? onLogoutSuccess; const HomeScreen({super.key, this.onLogoutSuccess}); @@ -18,6 +20,7 @@ class HomeScreen extends StatefulWidget { State createState() => _HomeScreenState(); } +/// State for the HomeScreen, manages navigation and data loading for the home tab. class _HomeScreenState extends State { int _selectedIndex = 0; Map? _nextTraining; @@ -35,6 +38,7 @@ class _HomeScreenState extends State { _loadSuggestions(); } + /// Loads exercise suggestions for the user based on unused and highly rated exercises. Future _loadSuggestions() async { setState(() { _isLoadingSuggestions = true; @@ -48,7 +52,7 @@ class _HomeScreenState extends State { return; } - // 1. Hole alle vom User genutzten Übungs-IDs + // 1. Get all exercise IDs used by the user final userDoc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get(); final Set usedExerciseIds = {}; if (userDoc.exists) { @@ -67,14 +71,14 @@ class _HomeScreenState extends State { }); } - // 2. Hole alle Übungen + // 2. Get all exercises 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" + // 3. Filter for unused and highly rated exercises final suggestions = allExercises.where((exercise) { final isUsed = usedExerciseIds.contains(exercise['id']); final rating = (exercise['rating overall'] as num?)?.toDouble() ?? 0.0; @@ -91,11 +95,12 @@ class _HomeScreenState extends State { } catch (e) { setState(() { _isLoadingSuggestions = false; - _suggestionsError = 'Fehler beim Laden.'; + _suggestionsError = 'Error loading suggestions.'; }); } } + /// Loads the next training event for the user, depending on their role and club. Future _loadNextTraining() async { setState(() => _isLoading = true); try { @@ -115,14 +120,14 @@ class _HomeScreenState extends State { QuerySnapshot trainersSnapshot; if (userRole == 'trainer') { - // Trainer sieht nur seine eigenen Trainings + // Trainer sees only their own 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 + // Player sees only trainings from trainers in their club if (userClub == null || userClub.isEmpty) { setState(() { _nextTraining = null; @@ -160,7 +165,7 @@ class _HomeScreenState extends State { 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 + // Check the next 4 weeks for (var i = 0; i < 4; i++) { final date = eventDate.add(Duration(days: i * 7)); final normalizedDate = DateTime(date.year, date.month, date.day); @@ -180,7 +185,7 @@ class _HomeScreenState extends State { 'date': dateString, 'time': timeStr, 'duration': trainingDurations[day] ?? 60, - 'trainerName': trainerData['name'] ?? 'Unbekannter Trainer', + 'trainerName': trainerData['name'] ?? 'Unknown Trainer', 'day': day, }; } @@ -200,6 +205,7 @@ class _HomeScreenState extends State { } } + /// Returns the number of days until the next occurrence of a given weekday. int _getDaysUntilNext(String day, int currentWeekday) { final weekdays = { 'Montag': 1, @@ -219,22 +225,25 @@ class _HomeScreenState extends State { return daysUntilNext; } + /// Checks if two DateTime objects represent the same day. bool isSameDay(DateTime a, DateTime b) { return a.year == b.year && a.month == b.month && a.day == b.day; } + /// Handles navigation bar item taps and reloads suggestions if needed. void _onItemTapped(int index) { if (_selectedIndex != index) { setState(() { _selectedIndex = index; }); - // Wenn zum Home-Tab gewechselt wird, lade die Vorschläge neu + // Reload suggestions when switching to the Home tab if (index == 0) { _loadSuggestions(); } } } + /// List of main screens for the bottom navigation bar. List get _screens => [ _buildHomeTab(), const SearchTab(), @@ -271,6 +280,7 @@ class _HomeScreenState extends State { ); } + /// Builds the Home tab content, including next training, favorites, and suggestions. Widget _buildHomeTab() { return SafeArea( child: SingleChildScrollView( @@ -440,6 +450,7 @@ class _HomeScreenState extends State { ); } + /// Builds a favorite category circle with icon and label. Widget _buildFavoriteCircle(String label, IconData icon, Color color) { return Padding( padding: const EdgeInsets.only(right: 16.0), @@ -474,6 +485,7 @@ class _HomeScreenState extends State { ); } + /// Builds a suggestion card for an exercise. Widget _buildSuggestionCard(Map exercise) { final category = exercise['category'] as String? ?? 'Sonstiges'; final title = exercise['title'] as String? ?? 'Unbekannte Übung'; @@ -553,6 +565,7 @@ class _HomeScreenState extends State { } } +/// Mapping of category names to icons. const Map categoryIcons = { 'Aufwärmen & Mobilisation': Icons.directions_run, 'Wurf- & Torabschluss': Icons.sports_handball, @@ -562,6 +575,7 @@ const Map categoryIcons = { 'Koordination': Icons.directions_walk, }; +/// Mapping of category names to colors. const Map categoryColors = { 'Aufwärmen & Mobilisation': Colors.deepOrange, 'Wurf- & Torabschluss': Colors.orange, diff --git a/trainerbox/lib/screens/home_tab.dart b/trainerbox/lib/screens/home_tab.dart index 098700a..30403cb 100644 --- a/trainerbox/lib/screens/home_tab.dart +++ b/trainerbox/lib/screens/home_tab.dart @@ -1,7 +1,11 @@ +// home_tab.dart +// This file contains the HomeTab widget, which displays a welcome message, a featured image, favorite categories, and exercise suggestions. + import 'package:flutter/material.dart'; import '../widgets/category_circle.dart'; import '../widgets/exercise_card.dart'; +/// The HomeTab displays a welcome message, featured image, favorite categories, and exercise suggestions. class HomeTab extends StatelessWidget { const HomeTab({super.key}); @@ -13,7 +17,7 @@ class HomeTab extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Begrüßung + // Welcome message const Center( child: Text( 'Hallo Trainer!', @@ -25,7 +29,7 @@ class HomeTab extends StatelessWidget { ), ), const SizedBox(height: 20), - // Bild mit Titel + // Featured image with title overlay Container( height: 200, width: double.infinity, @@ -52,7 +56,7 @@ class HomeTab extends StatelessWidget { ), ), const SizedBox(height: 20), - // Favoriten Kategorien + // Favorite categories section const Text( 'Favoriten', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -77,7 +81,7 @@ class HomeTab extends StatelessWidget { ), ), const SizedBox(height: 20), - // Vorschläge + // Suggestions section const Text( 'Vorschläge', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -88,6 +92,7 @@ class HomeTab extends StatelessWidget { child: Stack( alignment: Alignment.center, children: [ + // PageView with exercise suggestion cards PageView( children: const [ ExerciseCard( diff --git a/trainerbox/lib/screens/login_screen.dart b/trainerbox/lib/screens/login_screen.dart index 228b137..c737e09 100644 --- a/trainerbox/lib/screens/login_screen.dart +++ b/trainerbox/lib/screens/login_screen.dart @@ -1,7 +1,11 @@ +// login_screen.dart +// This file contains the LoginScreen widget, which handles user authentication (login and registration) and role selection. + import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +/// The LoginScreen allows users to log in or register, and select their role (trainer or player). class LoginScreen extends StatefulWidget { final void Function() onLoginSuccess; const LoginScreen({super.key, required this.onLoginSuccess}); @@ -10,6 +14,7 @@ class LoginScreen extends StatefulWidget { State createState() => _LoginScreenState(); } +/// State for the LoginScreen, manages form state, authentication, and error handling. class _LoginScreenState extends State { final _formKey = GlobalKey(); final _emailController = TextEditingController(); @@ -20,6 +25,7 @@ class _LoginScreenState extends State { bool _isLogin = true; bool _isTrainer = false; + /// Handles login or registration when the form is submitted. Future _submit() async { setState(() { _loading = true; _error = null; }); try { @@ -31,31 +37,29 @@ class _LoginScreenState extends State { password: _passwordController.text.trim(), ); - // Firestore-Check + // Check if user profile exists in Firestore final uid = cred.user!.uid; - final userDoc = await FirebaseFirestore.instance.collection('User').doc(uid).get(); if (userDoc.exists) { widget.onLoginSuccess(); } else { - setState(() { _error = 'Kein Benutzerprofil in der Datenbank gefunden!'; }); + setState(() { _error = 'No user profile found in the database!'; }); await FirebaseAuth.instance.signOut(); } } catch (e) { rethrow; } } else { - // Registrierung + // Registration try { UserCredential cred = await FirebaseAuth.instance.createUserWithEmailAndPassword( email: _emailController.text.trim(), password: _passwordController.text.trim(), ); - // User-Datensatz in Firestore anlegen + // Create user document in Firestore final uid = cred.user!.uid; - await FirebaseFirestore.instance.collection('User').doc(uid).set({ 'email': _emailController.text.trim(), 'name': _nameController.text.trim(), @@ -69,6 +73,7 @@ class _LoginScreenState extends State { } } } on FirebaseAuthException catch (e) { + // Handle Firebase authentication errors String errorMessage; switch (e.code) { case 'user-not-found': @@ -81,7 +86,7 @@ class _LoginScreenState extends State { errorMessage = 'Ungültige E-Mail-Adresse.'; break; case 'user-disabled': - errorMessage = 'Dieser Account wurde deaktiviert.'; + errorMessage = 'Dieses Konto wurde deaktiviert.'; break; case 'email-already-in-use': errorMessage = 'Diese E-Mail-Adresse wird bereits verwendet.'; @@ -90,14 +95,14 @@ class _LoginScreenState extends State { errorMessage = 'Das Passwort ist zu schwach.'; break; case 'operation-not-allowed': - errorMessage = 'Diese Operation ist nicht erlaubt.'; + errorMessage = 'Diese Aktion ist nicht erlaubt.'; break; default: errorMessage = 'Ein Fehler ist aufgetreten: ${e.message}'; } setState(() { _error = errorMessage; }); } catch (e) { - setState(() { _error = 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.'; }); + setState(() { _error = 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es später erneut.'; }); } finally { setState(() { _loading = false; }); } @@ -114,15 +119,18 @@ class _LoginScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(_isLogin ? 'Login' : 'Registrieren', style: Theme.of(context).textTheme.headlineMedium), + // Title + Text(_isLogin ? 'Anmeldung' : 'Registrieren', style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: 32), if (!_isLogin) ...[ + // Name field for registration TextFormField( controller: _nameController, decoration: const InputDecoration(labelText: 'Name'), - validator: (v) => v != null && v.trim().isNotEmpty ? null : 'Name angeben', + validator: (v) => v != null && v.trim().isNotEmpty ? null : 'Bitte gib deinen Namen ein', ), const SizedBox(height: 16), + // Role selection Row( children: [ Checkbox( @@ -144,24 +152,28 @@ class _LoginScreenState extends State { ), const SizedBox(height: 16), ], + // Email field TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'E-Mail'), keyboardType: TextInputType.emailAddress, - validator: (v) => v != null && v.contains('@') ? null : 'Gib eine gültige E-Mail ein', + validator: (v) => v != null && v.contains('@') ? null : 'Bitte gib eine gültige E-Mail ein', ), const SizedBox(height: 16), + // Password field TextFormField( controller: _passwordController, decoration: const InputDecoration(labelText: 'Passwort'), obscureText: true, - validator: (v) => v != null && v.length >= 6 ? null : 'Mind. 6 Zeichen', + validator: (v) => v != null && v.length >= 6 ? null : 'Mindestens 6 Zeichen', ), const SizedBox(height: 24), if (_error != null) ...[ + // Error message Text(_error!, style: const TextStyle(color: Colors.red)), const SizedBox(height: 12), ], + // Submit button SizedBox( width: double.infinity, child: ElevatedButton( @@ -174,10 +186,11 @@ class _LoginScreenState extends State { }, child: _loading ? const CircularProgressIndicator() - : Text(_isLogin ? 'Login' : 'Registrieren'), + : Text(_isLogin ? 'Anmelden' : 'Registrieren'), ), ), const SizedBox(height: 16), + // Switch between login and registration TextButton( onPressed: _loading ? null @@ -187,7 +200,7 @@ class _LoginScreenState extends State { _error = null; }); }, - child: Text(_isLogin ? 'Noch keinen Account? Jetzt registrieren!' : 'Schon registriert? Jetzt einloggen!'), + child: Text(_isLogin ? 'Noch kein Konto? Jetzt registrieren!' : 'Schon registriert? Jetzt anmelden!'), ), ], ), diff --git a/trainerbox/lib/screens/profile_tab.dart b/trainerbox/lib/screens/profile_tab.dart index dbce400..40ac2ce 100644 --- a/trainerbox/lib/screens/profile_tab.dart +++ b/trainerbox/lib/screens/profile_tab.dart @@ -3,7 +3,12 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:intl/intl.dart'; +// profile_tab.dart +// This file contains the ProfileTab widget, which allows users to view and edit their profile information, including training times and durations. Trainers have additional options for managing training schedules. + +/// The ProfileTab displays and allows editing of user profile information. class ProfileTab extends StatefulWidget { + /// Callback for when the user logs out successfully. final VoidCallback? onLogoutSuccess; const ProfileTab({super.key, this.onLogoutSuccess}); @@ -12,23 +17,35 @@ class ProfileTab extends StatefulWidget { } class _ProfileTabState extends State { + // Form key for validating the profile form. final _formKey = GlobalKey(); + // Indicates if the user is a trainer. bool _isTrainer = false; + // Indicates if data is currently loading. bool _isLoading = false; + // Stores training times for each day. Map _trainingTimes = {}; + // Stores training durations for each day. Map _trainingDurations = {}; + // User's name. String _name = ''; + // User's email. String _email = ''; + // User's club. String _club = ''; + // User's role (trainer or player). String? _userRole; + // Date the user joined. DateTime? _joinDate; @override void initState() { super.initState(); + // Load user data when the widget is initialized. _loadUserData(); } + /// Loads user data from Firebase and updates the state. Future _loadUserData() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return; @@ -41,6 +58,7 @@ class _ProfileTabState extends State { final trainingTimes = data['trainingTimes'] as Map? ?? {}; final trainingDurations = data['trainingDurations'] as Map? ?? {}; + // Convert training times from string to TimeOfDay. final convertedTrainingTimes = {}; trainingTimes.forEach((key, value) { if (value != null) { @@ -79,6 +97,7 @@ class _ProfileTabState extends State { } } + /// Saves the training time and duration for a specific day to Firebase. Future _saveTrainingTime(String day, TimeOfDay? time, int? duration) async { if (time == null || duration == null) return; @@ -89,13 +108,13 @@ class _ProfileTabState extends State { final timeString = '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; - // Update training times and durations + // Update training times and durations in Firestore. await FirebaseFirestore.instance.collection('User').doc(user.uid).update({ 'trainingTimes.$day': timeString, 'trainingDurations.$day': duration, }); - // Trainings ab 1. Januar des aktuellen Jahres bis 52 Wochen in die Zukunft anlegen + // Create training entries from the start of the year for 52 weeks ahead. final now = DateTime.now(); final yearStart = DateTime(now.year, 1, 1); final weekdays = { @@ -109,7 +128,7 @@ class _ProfileTabState extends State { }; final targetWeekday = weekdays[day] ?? 1; - // Finde das erste gewünschte Wochentags-Datum ab Jahresanfang + // Find the first occurrence of the target weekday from the start of the year. DateTime firstDate = yearStart; while (firstDate.weekday != targetWeekday) { firstDate = firstDate.add(const Duration(days: 1)); @@ -123,20 +142,20 @@ class _ProfileTabState extends State { newTrainingDates.add(dateString); } - // Load existing trainingExercises and cancelledTrainings + // Load existing trainingExercises and cancelledTrainings from Firestore. final userDoc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get(); final data = userDoc.data() ?? {}; final trainingExercises = Map.from(data['trainingExercises'] ?? {}); final cancelledTrainings = List>.from(data['cancelledTrainings'] ?? []); - // Add empty training only if not already present + // Add empty training only if not already present. for (final dateString in newTrainingDates) { if (!trainingExercises.containsKey(dateString)) { trainingExercises[dateString] = []; } } - // Remove cancelledTrainings for these dates + // Remove cancelledTrainings for these dates. cancelledTrainings.removeWhere((cancelled) => cancelled is Map && cancelled.containsKey('date') && @@ -171,6 +190,7 @@ class _ProfileTabState extends State { } } + /// Removes the training time and duration for a specific day from Firebase. Future _removeTrainingTime(String day) async { setState(() => _isLoading = true); try { @@ -205,6 +225,7 @@ class _ProfileTabState extends State { } } + /// Opens a time picker dialog for selecting a training time, then a dialog for duration. Future _selectTime(BuildContext context, String day) async { final TimeOfDay? picked = await showTimePicker( context: context, @@ -223,6 +244,7 @@ class _ProfileTabState extends State { } } + /// Saves the user's profile data (name, club, training times, durations) to Firebase. Future _saveUserData() async { if (FirebaseAuth.instance.currentUser == null) return; @@ -268,6 +290,7 @@ class _ProfileTabState extends State { Widget build(BuildContext context) { final user = FirebaseAuth.instance.currentUser; if (user == null) { + // Show message if user is not logged in. return const Center(child: Text('Nicht eingeloggt')); } @@ -278,6 +301,7 @@ class _ProfileTabState extends State { IconButton( icon: const Icon(Icons.logout), onPressed: () async { + // Log out the user and call the callback if provided. await FirebaseAuth.instance.signOut(); if (widget.onLogoutSuccess != null) { widget.onLogoutSuccess!(); @@ -297,6 +321,7 @@ class _ProfileTabState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Card for personal information fields. Card( elevation: 4, child: Padding( @@ -312,6 +337,7 @@ class _ProfileTabState extends State { ), ), const SizedBox(height: 16), + // Editable name field. TextField( controller: TextEditingController(text: _name), decoration: const InputDecoration( @@ -322,6 +348,7 @@ class _ProfileTabState extends State { onChanged: (value) => _name = value, ), const SizedBox(height: 16), + // Non-editable email field. TextField( controller: TextEditingController(text: _email), decoration: const InputDecoration( @@ -332,6 +359,7 @@ class _ProfileTabState extends State { enabled: false, ), const SizedBox(height: 16), + // Editable club field. TextField( controller: TextEditingController(text: _club), decoration: const InputDecoration( @@ -343,7 +371,7 @@ class _ProfileTabState extends State { onChanged: (value) => _club = value, ), const SizedBox(height: 16), - // Rolle + // Non-editable role field. TextField( controller: TextEditingController(text: _userRole == 'trainer' ? 'Trainer' : 'Spieler'), decoration: const InputDecoration( @@ -355,6 +383,7 @@ class _ProfileTabState extends State { ), if (_joinDate != null) ...[ const SizedBox(height: 16), + // Non-editable join date field. TextField( controller: TextEditingController(text: DateFormat('dd.MM.yyyy').format(_joinDate!)), decoration: const InputDecoration( @@ -371,6 +400,7 @@ class _ProfileTabState extends State { ), if (_userRole == 'trainer') ...[ const SizedBox(height: 24), + // Card for managing training times (visible only to trainers). Card( elevation: 4, child: Padding( @@ -386,6 +416,7 @@ class _ProfileTabState extends State { ), ), const SizedBox(height: 16), + // List of training days with time and duration controls. ...['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'] .map((day) => Card( margin: const EdgeInsets.only(bottom: 8), @@ -400,6 +431,7 @@ class _ProfileTabState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ + // Edit or add training time button. IconButton( icon: Icon( _trainingTimes[day] != null @@ -411,6 +443,7 @@ class _ProfileTabState extends State { : () => _selectTime(context, day), ), if (_trainingTimes[day] != null) + // Remove training time button. IconButton( icon: const Icon(Icons.delete), onPressed: _isLoading @@ -434,6 +467,7 @@ class _ProfileTabState extends State { } } +/// Dialog for selecting the duration of a training session. class _DurationDialog extends StatefulWidget { final int initialDuration; @@ -449,6 +483,7 @@ class _DurationDialogState extends State<_DurationDialog> { @override void initState() { super.initState(); + // Initialize duration with the provided initial value. _duration = widget.initialDuration; } @@ -464,6 +499,7 @@ class _DurationDialogState extends State<_DurationDialog> { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + // Decrement duration button (minimum 15 minutes). IconButton( icon: const Icon(Icons.remove), onPressed: () { @@ -476,6 +512,7 @@ class _DurationDialogState extends State<_DurationDialog> { '$_duration Minuten', style: const TextStyle(fontSize: 18), ), + // Increment duration button (maximum 300 minutes). IconButton( icon: const Icon(Icons.add), onPressed: _duration < 300 @@ -487,10 +524,12 @@ class _DurationDialogState extends State<_DurationDialog> { ], ), actions: [ + // Cancel button. TextButton( onPressed: () => Navigator.pop(context), child: const Text('Abbrechen'), ), + // Confirm button. ElevatedButton( onPressed: () => Navigator.pop(context, _duration), child: const Text('Bestätigen'), diff --git a/trainerbox/lib/screens/search_tab.dart b/trainerbox/lib/screens/search_tab.dart index 02a3f74..e365453 100644 --- a/trainerbox/lib/screens/search_tab.dart +++ b/trainerbox/lib/screens/search_tab.dart @@ -6,8 +6,11 @@ import 'package:image_picker/image_picker.dart'; import 'dart:io'; import 'training_detail_screen.dart'; +/// The SearchTab displays a searchable and filterable list of training exercises. class SearchTab extends StatefulWidget { + /// If true, enables selection mode for choosing exercises. final bool selectMode; + /// Remaining time for selection mode (optional). final int? remainingTime; const SearchTab({ @@ -21,7 +24,9 @@ class SearchTab extends StatefulWidget { } class _SearchTabState extends State { + // Controller for the search input field. final TextEditingController _searchController = TextEditingController(); + // List of available exercise categories. final List _categories = [ 'Aufwärmen & Mobilisation', 'Wurf- & Torabschluss', @@ -30,15 +35,21 @@ class _SearchTabState extends State { 'Pass', 'Koordination', ]; + // Currently selected category for filtering. String? _selectedCategory; + // Current search term entered by the user. String _searchTerm = ''; + // Indicates if the user is a trainer. bool _isTrainer = false; + // Indicates if the trainer check has completed. bool _trainerChecked = false; + // Set of favorite exercise IDs. Set _favorites = {}; @override void initState() { super.initState(); + // Listen for changes in the search field and update the search term. _searchController.addListener(() { setState(() { _searchTerm = _searchController.text.trim(); @@ -48,6 +59,7 @@ class _SearchTabState extends State { _loadFavorites(); } + /// Checks if the current user is a trainer and updates state. Future _checkIfTrainer() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return; @@ -58,6 +70,7 @@ class _SearchTabState extends State { }); } + /// Loads the user's favorite exercises from Firestore. Future _loadFavorites() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return; @@ -70,13 +83,15 @@ class _SearchTabState extends State { } } + /// Shows a dialog for creating a new training exercise (trainer only). void _showCreateTrainingDialog(BuildContext context) { showDialog( context: context, builder: (context) => _CreateTrainingDialog(categories: _categories), - ).then((_) => setState(() {})); // Refresh nach Hinzufügen + ).then((_) => setState(() {})); // Refresh after adding } + /// Toggles the favorite status of an exercise for the current user. Future _toggleFavorite(String trainingId, bool isFavorite) async { final user = FirebaseAuth.instance.currentUser; if (user == null) return; @@ -89,7 +104,7 @@ class _SearchTabState extends State { 'favorites': FieldValue.arrayUnion([trainingId]), }); } - await _loadFavorites(); // Aktualisiere die Favoriten nach dem Toggle + await _loadFavorites(); // Update favorites after toggling } @override @@ -112,6 +127,7 @@ class _SearchTabState extends State { ), body: Column( children: [ + // Search input field. Padding( padding: const EdgeInsets.all(8.0), child: TextField( @@ -136,6 +152,7 @@ class _SearchTabState extends State { ), ), ), + // Category filter chips. Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -164,6 +181,7 @@ class _SearchTabState extends State { ], ), ), + // Exercise grid view. Expanded( child: FutureBuilder( future: FirebaseFirestore.instance.collection('Training').get(), @@ -180,6 +198,7 @@ class _SearchTabState extends State { return const Center(child: Text('Keine Übungen gefunden')); } + // Filter exercises by search term and category. var exercises = snapshot.data!.docs.where((doc) { final data = doc.data() as Map; final title = data['title']?.toString().toLowerCase() ?? ''; @@ -210,6 +229,7 @@ class _SearchTabState extends State { final doc = exercises[index]; final data = doc.data() as Map; final duration = (data['duration'] as num?)?.toInt() ?? 0; + // Disable selection if duration exceeds remaining time in select mode. final isDisabled = widget.selectMode && duration > (widget.remainingTime ?? 0); return Card( @@ -220,6 +240,7 @@ class _SearchTabState extends State { ? null : () { if (widget.selectMode) { + // Return selected exercise data in select mode. Navigator.pop(context, { 'id': doc.id, 'title': data['title']?.toString() ?? 'Unbekannte Übung', @@ -227,6 +248,7 @@ class _SearchTabState extends State { 'duration': duration, }); } else { + // Navigate to exercise detail screen. Navigator.push( context, MaterialPageRoute( @@ -293,6 +315,7 @@ class _SearchTabState extends State { ], ), ), + // Favorite icon button (toggle favorite status). Positioned( top: 4, right: 4, @@ -324,6 +347,7 @@ class _SearchTabState extends State { } } +/// Dialog for creating a new training exercise. class _CreateTrainingDialog extends StatefulWidget { final List categories; const _CreateTrainingDialog({required this.categories}); @@ -333,16 +357,26 @@ class _CreateTrainingDialog extends StatefulWidget { } class _CreateTrainingDialogState extends State<_CreateTrainingDialog> { + // Form key for validating the create training form. final _formKey = GlobalKey(); + // Selected category for the new training. String? _category; + // Title of the new training. String? _title; + // Description of the new training. String? _description; + // Duration of the new training. int? _duration; + // Difficulty level or year. String? _year; + // Indicates if the dialog is loading (creating training). bool _loading = false; + // Selected image file for the training. File? _imageFile; + // Image picker instance. final _picker = ImagePicker(); + /// Opens the image picker to select an image from the gallery. Future _pickImage() async { final pickedFile = await _picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { @@ -352,6 +386,7 @@ class _CreateTrainingDialogState extends State<_CreateTrainingDialog> { } } + /// Uploads the selected image to Firebase Storage and returns the download URL. Future _uploadImage() async { if (_imageFile == null) return null; diff --git a/trainerbox/lib/screens/training_detail_screen.dart b/trainerbox/lib/screens/training_detail_screen.dart index cfd559f..5a0de2b 100644 --- a/trainerbox/lib/screens/training_detail_screen.dart +++ b/trainerbox/lib/screens/training_detail_screen.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; +/// The TrainingDetailScreen displays details and ratings for a specific training exercise. class TrainingDetailScreen extends StatefulWidget { + /// The ID of the training exercise to display. final String trainingId; const TrainingDetailScreen({super.key, required this.trainingId}); @@ -12,17 +14,23 @@ class TrainingDetailScreen extends StatefulWidget { } class _TrainingDetailScreenState extends State { + // The current user's rating for this exercise. double? _userRating; + // Indicates if a loading operation is in progress. bool _isLoading = false; + // Indicates if the current user is a player (not a trainer). bool _isPlayer = false; + // Indicates if the user role check has completed. bool _userRoleChecked = false; @override void initState() { super.initState(); + // Check the user's role when the widget is initialized. _checkUserRole(); } + /// Checks the current user's role (player or trainer) and updates state. Future _checkUserRole() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return; @@ -40,6 +48,7 @@ class _TrainingDetailScreenState extends State { } } + /// Submits a rating for the current training exercise by the user. Future _submitRating(double rating) async { if (!_isPlayer) { ScaffoldMessenger.of(context).showSnackBar( @@ -65,23 +74,23 @@ class _TrainingDetailScreenState extends State { final data = trainingDoc.data() as Map; List ratings = List.from(data['ratings'] ?? []); - // Entferne alte Bewertung des Users falls vorhanden + // Remove old rating from this user if present. ratings.removeWhere((r) => r['userId'] == user.uid); - // Füge neue Bewertung hinzu + // Add new rating. ratings.add({ 'userId': user.uid, 'rating': rating, - 'timestamp': DateTime.now().toIso8601String(), // Verwende ISO-String statt FieldValue + 'timestamp': DateTime.now().toIso8601String(), // Use ISO string for timestamp }); - // Berechne neue Gesamtbewertung + // Calculate new overall rating. double overallRating = 0; if (ratings.isNotEmpty) { overallRating = ratings.map((r) => (r['rating'] as num).toDouble()).reduce((a, b) => a + b) / ratings.length; } - // Aktualisiere das Dokument + // Update the training document with new ratings and overall rating. await trainingRef.update({ 'ratings': ratings, 'rating overall': overallRating, @@ -123,7 +132,7 @@ class _TrainingDetailScreenState extends State { } final data = snapshot.data!.data() as Map; - // Hole die Bewertung des aktuellen Users + // Get the current user's rating for this exercise if not already loaded. final user = FirebaseAuth.instance.currentUser; if (user != null && _userRating == null) { final ratings = List.from(data['ratings'] ?? []); @@ -141,6 +150,7 @@ class _TrainingDetailScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Display the exercise image if available. Container( width: double.infinity, height: 200, @@ -155,6 +165,7 @@ class _TrainingDetailScreenState extends State { ), ), const SizedBox(height: 16), + // Display the exercise title. Text( data['title'] ?? 'Unbekannt', style: const TextStyle( @@ -163,21 +174,25 @@ class _TrainingDetailScreenState extends State { ), ), const SizedBox(height: 8), + // Display the exercise description. Text( data['description'] ?? 'Keine Beschreibung', style: TextStyle(color: Colors.grey[600]), ), const SizedBox(height: 16), + // Display the exercise duration. Text( 'Dauer: ${data['duration'] ?? '-'} Minuten', style: TextStyle(color: Colors.grey[600]), ), const SizedBox(height: 8), + // Display the exercise level/year. Text( 'Level: ${data['year'] ?? '-'}', style: TextStyle(color: Colors.grey[600]), ), const SizedBox(height: 16), + // Display the average rating. Row( children: [ const Icon(Icons.star, color: Colors.amber), @@ -189,10 +204,12 @@ class _TrainingDetailScreenState extends State { ], ), const SizedBox(height: 8), + // Display the number of ratings. Text( 'Anzahl Bewertungen: ${(data['ratings'] ?? []).length}', style: TextStyle(color: Colors.grey[600]), ), + // Show rating controls if the user is a player. if (_userRoleChecked && _isPlayer) ...[ const SizedBox(height: 16), const Text( @@ -222,6 +239,7 @@ class _TrainingDetailScreenState extends State { ), ], const SizedBox(height: 8), + // Display the exercise category. Text( 'Kategorie: ${data['category'] ?? '-'}', style: TextStyle(color: Colors.grey[600]), diff --git a/trainerbox/lib/utils/responsive.dart b/trainerbox/lib/utils/responsive.dart index 4f86a5b..a69bb77 100644 --- a/trainerbox/lib/utils/responsive.dart +++ b/trainerbox/lib/utils/responsive.dart @@ -1,28 +1,40 @@ +// responsive.dart +// Utility class for handling responsive design in Flutter apps. Provides helpers for device type checks, scaling, and adaptive sizing. + import 'package:flutter/material.dart'; +/// The Responsive class provides static methods to help adapt UI to different screen sizes (mobile, tablet, desktop). class Responsive { + /// Returns true if the device is considered a mobile (width < 600px). static bool isMobile(BuildContext context) => MediaQuery.of(context).size.width < 600; + /// Returns true if the device is considered a tablet (600px <= width < 1200px). static bool isTablet(BuildContext context) => MediaQuery.of(context).size.width >= 600 && MediaQuery.of(context).size.width < 1200; + /// Returns true if the device is considered a desktop (width >= 1200px). static bool isDesktop(BuildContext context) => MediaQuery.of(context).size.width >= 1200; + /// Returns the current screen width in logical pixels. static double getWidth(BuildContext context) => MediaQuery.of(context).size.width; + /// Returns the current screen height in logical pixels. static double getHeight(BuildContext context) => MediaQuery.of(context).size.height; + /// Returns a width scaled by the given percentage of the screen width. static double getScaledWidth(BuildContext context, double percentage) => getWidth(context) * (percentage / 100); + /// Returns a height scaled by the given percentage of the screen height. static double getScaledHeight(BuildContext context, double percentage) => getHeight(context) * (percentage / 100); + /// Returns appropriate padding based on device type (mobile/tablet/desktop). static EdgeInsets getPadding(BuildContext context) { if (isMobile(context)) { return const EdgeInsets.all(16.0); @@ -33,6 +45,7 @@ class Responsive { } } + /// Returns a font size scaled for the device type. static double getFontSize(BuildContext context, double baseSize) { if (isMobile(context)) { return baseSize; @@ -43,6 +56,7 @@ class Responsive { } } + /// Returns an icon size scaled for the device type. static double getIconSize(BuildContext context, double baseSize) { if (isMobile(context)) { return baseSize; diff --git a/trainerbox/lib/widgets/category_circle.dart b/trainerbox/lib/widgets/category_circle.dart index 9e7b56f..8294c3e 100644 --- a/trainerbox/lib/widgets/category_circle.dart +++ b/trainerbox/lib/widgets/category_circle.dart @@ -1,11 +1,17 @@ // widgets/category_circle.dart +// Widget for a circular category view // Widget für eine runde Kategorie-Ansicht import 'package:flutter/material.dart'; +/// A widget that displays a category as a circle with an icon and a title below. +/// Widget, das eine Kategorie als Kreis mit Icon und Titel darunter darstellt. class CategoryCircle extends StatelessWidget { + /// The title of the category. final String title; + /// The icon representing the category. final IconData icon; + /// Creates a CategoryCircle widget. const CategoryCircle({super.key, required this.title, required this.icon}); @override @@ -14,6 +20,7 @@ class CategoryCircle extends StatelessWidget { padding: const EdgeInsets.only(right: 16.0), child: Column( children: [ + // Circle with icon // Kreis mit Icon Container( width: 70, @@ -29,6 +36,7 @@ class CategoryCircle extends StatelessWidget { ), ), const SizedBox(height: 8), + // Title below the circle // Titel unter dem Kreis Text( title, diff --git a/trainerbox/lib/widgets/exercise_card.dart b/trainerbox/lib/widgets/exercise_card.dart index 2628c8b..5267d8c 100644 --- a/trainerbox/lib/widgets/exercise_card.dart +++ b/trainerbox/lib/widgets/exercise_card.dart @@ -1,12 +1,19 @@ // widgets/exercise_card.dart +// Widget for a training exercise card view // Widget für eine Trainingskarten-Ansicht import 'package:flutter/material.dart'; +/// A widget that displays a card for a training exercise, showing its icon, title, and category. +/// Widget, das eine Karte für eine Trainingsübung mit Icon, Titel und Kategorie anzeigt. class ExerciseCard extends StatelessWidget { + /// The title of the exercise. final String title; + /// The category of the exercise. final String category; + /// The icon representing the exercise. final IconData icon; + /// Creates an ExerciseCard widget. const ExerciseCard({ super.key, required this.title, @@ -26,9 +33,11 @@ class ExerciseCard extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + // Icon for the exercise // Icon für die Übung Icon(icon, size: 60, color: Theme.of(context).colorScheme.primary), const SizedBox(height: 16), + // Title of the exercise // Titel der Übung Text( title, @@ -36,6 +45,7 @@ class ExerciseCard extends StatelessWidget { textAlign: TextAlign.center, ), const SizedBox(height: 8), + // Category badge // Kategorie-Badge Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),