diff --git a/trainerbox/lib/screens/favorites_tab.dart b/trainerbox/lib/screens/favorites_tab.dart index d2e2eef..f4ab0a9 100644 --- a/trainerbox/lib/screens/favorites_tab.dart +++ b/trainerbox/lib/screens/favorites_tab.dart @@ -33,70 +33,95 @@ class FavoritesTab extends StatelessWidget { return const Center(child: Text('Keine Favoriten gefunden')); } - return ListView.builder( + return GridView.builder( + padding: const EdgeInsets.all(8), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 300, + childAspectRatio: 0.75, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), itemCount: favorites.length, itemBuilder: (context, index) { return FutureBuilder( future: FirebaseFirestore.instance.collection('Training').doc(favorites[index]).get(), builder: (context, trainingSnapshot) { if (!trainingSnapshot.hasData || !trainingSnapshot.data!.exists) { - return const ListTile(title: Text('Training nicht gefunden')); + return const SizedBox.shrink(); } final trainingData = trainingSnapshot.data!.data() as Map; return Card( - margin: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TrainingDetailScreen(trainingId: favorites[index]), - ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - trainingData['title'] ?? 'Unbekannt', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: Stack( + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TrainingDetailScreen(trainingId: favorites[index]), ), - ), - const SizedBox(height: 8), - Text( - trainingData['description'] ?? 'Keine Beschreibung', - style: TextStyle(color: Colors.grey[600]), - ), - const SizedBox(height: 8), - Text( - 'Dauer: ${trainingData['duration'] ?? '-'} Minuten', - style: TextStyle(color: Colors.grey[600]), - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Level: ${trainingData['year'] ?? '-'}', - style: TextStyle(color: Colors.grey[600]), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + color: Colors.grey[200], + child: const Center( + child: Icon(Icons.fitness_center, size: 50), + ), ), - IconButton( - icon: const Icon(Icons.favorite, color: Colors.red), - onPressed: () async { - await FirebaseFirestore.instance.collection('User').doc(user.uid).update({ - 'favorites': FieldValue.arrayRemove([favorites[index]]), - }); - }, + ), + 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: ${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([favorites[index]]), + }); + }, + ), + ), + ], ), ); }, diff --git a/trainerbox/lib/screens/home_screen.dart b/trainerbox/lib/screens/home_screen.dart index a696ad0..f72f7b6 100644 --- a/trainerbox/lib/screens/home_screen.dart +++ b/trainerbox/lib/screens/home_screen.dart @@ -299,18 +299,20 @@ class _HomeScreenState extends State { ), ], ), - SizedBox( - height: 70, - child: ListView( + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: SingleChildScrollView( 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']!), - ], + 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), @@ -352,10 +354,19 @@ class _HomeScreenState extends State { CircleAvatar( radius: 24, backgroundColor: color.withOpacity(0.2), - child: Icon(icon, color: color, size: 28), + child: Icon(icon, color: color, size: 26), ), const SizedBox(height: 4), - Text(label, style: const TextStyle(fontSize: 12)), + SizedBox( + width: 60, + child: Text( + label, + style: const TextStyle(fontSize: 12), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), ], ), ); diff --git a/trainerbox/lib/screens/search_tab.dart b/trainerbox/lib/screens/search_tab.dart index d019a03..02a3f74 100644 --- a/trainerbox/lib/screens/search_tab.dart +++ b/trainerbox/lib/screens/search_tab.dart @@ -199,8 +199,8 @@ class _SearchTabState extends State { return GridView.builder( padding: const EdgeInsets.all(8), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 300, childAspectRatio: 0.75, crossAxisSpacing: 10, mainAxisSpacing: 10, @@ -213,77 +213,98 @@ class _SearchTabState extends State { final isDisabled = widget.selectMode && duration > (widget.remainingTime ?? 0); return Card( - child: InkWell( - onTap: isDisabled - ? null - : () { - if (widget.selectMode) { - Navigator.pop(context, { - 'id': doc.id, - 'title': data['title']?.toString() ?? 'Unbekannte Übung', - 'description': data['description']?.toString() ?? '', - 'duration': duration, - }); - } else { - 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( - data['title']?.toString() ?? 'Unbekannte Übung', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 4), - Text( - '${data['description']?.toString() ?? ''}\nDauer: $duration Minuten', - style: TextStyle( - color: Colors.grey[600], - fontSize: 12, - ), - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - if (isDisabled) - const Padding( - padding: EdgeInsets.only(top: 4), - child: Text( - 'Passt nicht in die verbleibende Zeit', - style: TextStyle( - color: Colors.orange, - fontSize: 12, + child: Stack( + children: [ + InkWell( + onTap: isDisabled + ? null + : () { + if (widget.selectMode) { + Navigator.pop(context, { + 'id': doc.id, + 'title': data['title']?.toString() ?? 'Unbekannte Übung', + 'description': data['description']?.toString() ?? '', + 'duration': duration, + }); + } else { + 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( + data['title']?.toString() ?? 'Unbekannte Übung', + 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( + (data['rating overall'] ?? 0.0).toStringAsFixed(1), + style: const TextStyle(fontSize: 12), + ), + ], + ), + const SizedBox(height: 4), + Text( + 'Dauer: ${duration} Minuten', + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + if (isDisabled) + const Padding( + padding: EdgeInsets.only(top: 4), + child: Text( + 'Passt nicht in die verbleibende Zeit', + style: TextStyle( + color: Colors.orange, + fontSize: 12, + ), + ), + ), + ], + ), + ), + ], ), - ], - ), + ), + Positioned( + top: 4, + right: 4, + child: IconButton( + icon: Icon( + _favorites.contains(doc.id) ? Icons.favorite : Icons.favorite_border, + color: _favorites.contains(doc.id) ? Colors.red : Colors.grey, + ), + onPressed: () => _toggleFavorite(doc.id, _favorites.contains(doc.id)), + ), + ), + ], ), ); },