// 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'; import '../models/categories.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}); @override State createState() => _FavoritesTabState(); } /// State for the FavoritesTab, manages category selection and favorite loading. class _FavoritesTabState extends State { List get _categories => kTrainingCategories.map((c) => c.name).toList(); String? _selectedCategory; // Level categories for filter final List _levelCategories = [ 'Bambini', 'F-Jugend', 'E-Jugend', 'D-Jugend', 'C-Jugend', 'B-Jugend', 'A-Jugend', 'Herren', ]; String? _selectedLevel; // Filter state variables for duration and rating double _minDuration = 0; // Minimum duration filter double _maxDuration = 120; // Maximum duration filter double _minRating = 0.0; // Minimum rating filter // Temporary filter state for modal double _tempMinDuration = 0; double _tempMaxDuration = 120; String? _tempSelectedLevel; double _tempMinRating = 0.0; @override void initState() { super.initState(); _selectedCategory = widget.categoryFilter; } /// Loads the user's favorite exercises from Firestore. Future> _loadFavorites() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return {}; final doc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get(); final data = doc.data(); if (data != null && data['favorites'] != null) { return Set.from(data['favorites']); } return {}; } /// 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; if (isFavorite) { await FirebaseFirestore.instance.collection('User').doc(user.uid).update({ 'favorites': FieldValue.arrayRemove([trainingId]), }); } else { await FirebaseFirestore.instance.collection('User').doc(user.uid).update({ 'favorites': FieldValue.arrayUnion([trainingId]), }); } // Refresh the favorites list setState(() {}); } /// Opens the filter modal for advanced filtering (duration, level, rating). void _openFilterModal() async { _tempMinDuration = _minDuration; _tempMaxDuration = _maxDuration; _tempSelectedLevel = _selectedLevel; _tempMinRating = _minRating; await showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) { return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, left: 16, right: 16, top: 24, ), child: StatefulBuilder( builder: (context, setModalState) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Filter', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 16), // Duration filter (RangeSlider) Row( children: [ const Text('Dauer:'), Expanded( child: RangeSlider( values: RangeValues(_tempMinDuration, _tempMaxDuration), min: 0, max: 300, divisions: 30, labels: RangeLabels('${_tempMinDuration.toInt()} min', '${_tempMaxDuration.toInt()} min'), onChanged: (values) { setModalState(() { _tempMinDuration = values.start; _tempMaxDuration = values.end; }); }, ), ), ], ), // Level filter (Dropdown) Row( children: [ const Text('Level:'), const SizedBox(width: 8), Expanded( child: DropdownButton( value: _tempSelectedLevel, hint: const Text('Alle'), items: [ const DropdownMenuItem(value: null, child: Text('Alle')), ..._levelCategories.map((y) => DropdownMenuItem(value: y, child: Text(y))), ], onChanged: (value) { setModalState(() { _tempSelectedLevel = value; }); }, ), ), ], ), // Minimum rating filter (Slider) Row( children: [ const Text('Mindestbewertung:'), Expanded( child: Slider( value: _tempMinRating, min: 0, max: 5, divisions: 10, label: _tempMinRating.toStringAsFixed(1), onChanged: (value) { setModalState(() { _tempMinRating = value; }); }, ), ), Text(_tempMinRating.toStringAsFixed(1)), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Abbrechen'), ), ElevatedButton( onPressed: () { setState(() { _minDuration = _tempMinDuration; _maxDuration = _tempMaxDuration; _selectedLevel = _tempSelectedLevel; _minRating = _tempMinRating; }); Navigator.pop(context); }, child: const Text('Anwenden'), ), ], ), const SizedBox(height: 8), ], ); }, ), ); }, ); } @override Widget build(BuildContext context) { final user = FirebaseAuth.instance.currentUser; if (user == null) { return const Center(child: Text('Nicht eingeloggt')); } return Scaffold( appBar: AppBar( title: const Text('Favoriten'), ), body: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Category filter chips and filter button Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Kategorien', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), // Filter button opens the modal with advanced filters ElevatedButton.icon( onPressed: _openFilterModal, icon: const Icon(Icons.filter_list), label: const Text('Filter'), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), textStyle: const TextStyle(fontSize: 14), ), ), ], ), const SizedBox(height: 12), // Horizontal scrollable category chips SizedBox( height: 40, child: ListView( scrollDirection: Axis.horizontal, children: [ // "Alle" chip Padding( padding: const EdgeInsets.only(right: 8.0), child: FilterChip( label: const Text('Alle'), selected: _selectedCategory == null, onSelected: (selected) { setState(() { _selectedCategory = null; }); }, ), ), // Category chips ..._categories.map((category) => Padding( padding: const EdgeInsets.only(right: 8.0), child: FilterChip( label: Text(category), selected: _selectedCategory == category, onSelected: (selected) { setState(() { _selectedCategory = selected ? category : null; }); }, ), )).toList(), ], ), ), ], ), ), // Favorites list Expanded( child: FutureBuilder>( future: _loadFavorites(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('Fehler: ${snapshot.error}')); } final allFavorites = snapshot.data ?? {}; if (allFavorites.isEmpty) { return const Center(child: Text('Keine Favoriten gefunden')); } return FutureBuilder>( future: Future.wait(allFavorites.map((id) => FirebaseFirestore.instance.collection('Training').doc(id).get() )), builder: (context, multiSnapshot) { if (multiSnapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (!multiSnapshot.hasData) { return const Center(child: Text('Favoriten konnten nicht geladen werden')); } // Filter favorites by selected category and filter options final filteredFavorites = multiSnapshot.data!.where((doc) { if (!doc.exists) return false; final trainingData = doc.data() as Map; final duration = (trainingData['duration'] as num?)?.toInt() ?? 0; final rating = (trainingData['rating overall'] as num?)?.toDouble() ?? 0.0; final matchesCategory = _selectedCategory == null || trainingData['category'] == _selectedCategory; final matchesLevel = _selectedLevel == null || trainingData['year'] == _selectedLevel; final matchesDuration = duration >= _minDuration && duration <= _maxDuration; final matchesRating = rating >= _minRating; // Only show favorites matching all selected filters return matchesCategory && matchesLevel && matchesDuration && matchesRating; }).toList(); if (filteredFavorites.isEmpty) { return const Center(child: Text('Keine Favoriten in dieser Kategorie gefunden')); } return GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 300, childAspectRatio: 0.75, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemCount: filteredFavorites.length, itemBuilder: (context, index) { final doc = filteredFavorites[index]; final trainingData = doc.data() as Map; return Card( child: Stack( children: [ InkWell( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => TrainingDetailScreen(trainingId: doc.id), ), ); }, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: Container( color: Colors.grey[200], child: const Center( child: Icon(Icons.fitness_center, size: 50), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( trainingData['title'] ?? 'Unbekannt', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ const Icon(Icons.star, color: Colors.amber, size: 16), const SizedBox(width: 4), Text( (trainingData['rating overall'] ?? 0.0).toStringAsFixed(1), style: const TextStyle(fontSize: 12), ), ], ), const SizedBox(height: 4), Text( 'Dauer: \t${trainingData['duration'] ?? '-'} Minuten', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ], ), ), Positioned( top: 4, right: 4, child: IconButton( icon: const Icon(Icons.favorite, color: Colors.red), onPressed: () async { await FirebaseFirestore.instance.collection('User').doc(user.uid).update({ 'favorites': FieldValue.arrayRemove([doc.id]), }); }, ), ), ], ), ); }, ); }, ); }, ), ), ], ), ); } }