Home auf Favoriten mit Filter added

main
joschy2002 2025-06-19 13:02:17 +02:00
parent 309333424c
commit fb3b9027c5
4 changed files with 249 additions and 167 deletions

View File

@ -3,8 +3,30 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'training_detail_screen.dart'; import 'training_detail_screen.dart';
class FavoritesTab extends StatelessWidget { class FavoritesTab extends StatefulWidget {
const FavoritesTab({super.key}); final String? categoryFilter;
const FavoritesTab({super.key, this.categoryFilter});
@override
State<FavoritesTab> createState() => _FavoritesTabState();
}
class _FavoritesTabState extends State<FavoritesTab> {
static const List<String> _categories = [
'Aufwärmen & Mobilisation',
'Wurf- & Torabschluss',
'Torwarttraining',
'Athletik',
'Pass',
'Koordination',
];
String? _selectedCategory;
@override
void initState() {
super.initState();
_selectedCategory = widget.categoryFilter;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -17,118 +39,169 @@ class FavoritesTab extends StatelessWidget {
appBar: AppBar( appBar: AppBar(
title: const Text('Favoriten'), title: const Text('Favoriten'),
), ),
body: StreamBuilder<DocumentSnapshot>( body: Column(
stream: FirebaseFirestore.instance.collection('User').doc(user.uid).snapshots(), crossAxisAlignment: CrossAxisAlignment.start,
builder: (context, snapshot) { children: [
if (snapshot.connectionState == ConnectionState.waiting) { // Filter-Chip-Leiste
return const Center(child: CircularProgressIndicator()); Padding(
} padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
if (!snapshot.hasData || !snapshot.data!.exists) { child: Wrap(
return const Center(child: Text('Keine Favoriten gefunden')); spacing: 8,
} runSpacing: 8,
final data = snapshot.data!.data() as Map<String, dynamic>; children: [
final favorites = List<String>.from(data['favorites'] ?? []); FilterChip(
label: const Text('Alle'),
if (favorites.isEmpty) { selected: _selectedCategory == null,
return const Center(child: Text('Keine Favoriten gefunden')); onSelected: (selected) {
} setState(() => _selectedCategory = null);
},
return GridView.builder( ),
padding: const EdgeInsets.all(8), ..._categories.map((cat) => FilterChip(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( label: Text(cat),
maxCrossAxisExtent: 300, selected: _selectedCategory == cat,
childAspectRatio: 0.75, onSelected: (selected) {
crossAxisSpacing: 10, setState(() => _selectedCategory = selected ? cat : null);
mainAxisSpacing: 10, },
)),
],
), ),
itemCount: favorites.length, ),
itemBuilder: (context, index) { Expanded(
return FutureBuilder<DocumentSnapshot>( child: StreamBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance.collection('Training').doc(favorites[index]).get(), stream: FirebaseFirestore.instance.collection('User').doc(user.uid).snapshots(),
builder: (context, trainingSnapshot) { builder: (context, snapshot) {
if (!trainingSnapshot.hasData || !trainingSnapshot.data!.exists) { if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox.shrink(); return const Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData || !snapshot.data!.exists) {
return const Center(child: Text('Keine Favoriten gefunden'));
}
final data = snapshot.data!.data() as Map<String, dynamic>;
final allFavorites = List<String>.from(data['favorites'] ?? []);
if (allFavorites.isEmpty) {
return const Center(child: Text('Keine Favoriten gefunden'));
}
// Filtere Favoriten nach Kategorie
Future<List<Map<String, dynamic>>> getFilteredFavorites() async {
List<Map<String, dynamic>> filtered = [];
for (final favId in allFavorites) {
final doc = await FirebaseFirestore.instance.collection('Training').doc(favId).get();
if (!doc.exists) continue;
final trainingData = doc.data() as Map<String, dynamic>;
if (_selectedCategory == null || trainingData['category'] == _selectedCategory) {
filtered.add({'id': favId, 'data': trainingData});
}
} }
final trainingData = trainingSnapshot.data!.data() as Map<String, dynamic>; return filtered;
return Card( }
child: Stack(
children: [ return FutureBuilder<List<Map<String, dynamic>>>(
InkWell( future: getFilteredFavorites(),
onTap: () { builder: (context, favSnapshot) {
Navigator.push( if (!favSnapshot.hasData) {
context, return const Center(child: CircularProgressIndicator());
MaterialPageRoute( }
builder: (context) => TrainingDetailScreen(trainingId: favorites[index]), final filteredFavorites = favSnapshot.data!;
), if (filteredFavorites.isEmpty) {
); return const Center(child: Text('Keine Favoriten gefunden'));
}, }
child: Column( return GridView.builder(
crossAxisAlignment: CrossAxisAlignment.stretch, padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300,
childAspectRatio: 0.75,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: filteredFavorites.length,
itemBuilder: (context, index) {
final fav = filteredFavorites[index];
final trainingData = fav['data'] as Map<String, dynamic>;
final favId = fav['id'] as String;
return Card(
child: Stack(
children: [ children: [
Expanded( InkWell(
child: Container( onTap: () {
color: Colors.grey[200], Navigator.push(
child: const Center( context,
child: Icon(Icons.fitness_center, size: 50), MaterialPageRoute(
), builder: (context) => TrainingDetailScreen(trainingId: favId),
), ),
), );
Padding( },
padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text( Expanded(
trainingData['title'] ?? 'Unbekannt', child: Container(
style: const TextStyle( color: Colors.grey[200],
fontWeight: FontWeight.bold, child: const Center(
fontSize: 16, child: Icon(Icons.fitness_center, size: 50),
),
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), Padding(
Text( padding: const EdgeInsets.all(8.0),
'Dauer: ${trainingData['duration'] ?? '-'} Minuten', child: Column(
style: const TextStyle(fontSize: 12, color: Colors.grey), 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([favId]),
});
},
),
),
], ],
), ),
), );
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]]), ],
});
},
),
),
],
),
);
},
);
},
);
},
), ),
); );
} }

View File

@ -22,6 +22,7 @@ class _HomeScreenState extends State<HomeScreen> {
Map<String, dynamic>? _nextTraining; Map<String, dynamic>? _nextTraining;
bool _isLoading = true; bool _isLoading = true;
DateTime? _calendarInitialDate; DateTime? _calendarInitialDate;
String? _favoriteCategoryFilter;
@override @override
void initState() { void initState() {
@ -165,7 +166,7 @@ class _HomeScreenState extends State<HomeScreen> {
List<Widget> get _screens => [ List<Widget> get _screens => [
_buildHomeTab(), _buildHomeTab(),
const SearchTab(), const SearchTab(),
const FavoritesTab(), FavoritesTab(categoryFilter: _favoriteCategoryFilter),
CalendarTab(initialDate: _calendarInitialDate), CalendarTab(initialDate: _calendarInitialDate),
ProfileTab(onLogoutSuccess: widget.onLogoutSuccess), ProfileTab(onLogoutSuccess: widget.onLogoutSuccess),
]; ];
@ -349,25 +350,33 @@ class _HomeScreenState extends State<HomeScreen> {
Widget _buildFavoriteCircle(String label, IconData icon, Color color) { Widget _buildFavoriteCircle(String label, IconData icon, Color color) {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 16.0), padding: const EdgeInsets.only(right: 16.0),
child: Column( child: GestureDetector(
children: [ onTap: () {
CircleAvatar( setState(() {
radius: 24, _favoriteCategoryFilter = label;
backgroundColor: color.withOpacity(0.2), _selectedIndex = 2;
child: Icon(icon, color: color, size: 26), });
), },
const SizedBox(height: 4), child: Column(
SizedBox( children: [
width: 60, CircleAvatar(
child: Text( radius: 24,
label, backgroundColor: color.withOpacity(0.2),
style: const TextStyle(fontSize: 12), child: Icon(icon, color: color, size: 26),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), const SizedBox(height: 4),
], SizedBox(
width: 60,
child: Text(
label,
style: const TextStyle(fontSize: 12),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
), ),
); );
} }

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: dda4fd7909a732a014239009aa52537b136f8ce568de23c212587097887e2307 sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.56" version: "1.3.35"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -45,26 +45,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cloud_firestore name: cloud_firestore
sha256: "80cafe016ea12e4b76737487784dc659720065705445be547d2f47aaaa3f2874" sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.6.9" version: "4.17.5"
cloud_firestore_platform_interface: cloud_firestore_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: cloud_firestore_platform_interface name: cloud_firestore_platform_interface
sha256: "9c5adf444b6e4eca15ecbc497678fba6d9944041fbf22810b756269ddefa75c7" sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.6.9" version: "6.2.5"
cloud_firestore_web: cloud_firestore_web:
dependency: transitive dependency: transitive
description: description:
name: cloud_firestore_web name: cloud_firestore_web
sha256: "884c153ac66cdef469ca5e55bf71d979c3b53dc20a4c1662fae5c07bf603d878" sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.9" version: "3.12.5"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -141,34 +141,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_auth name: firebase_auth
sha256: "10ddd766bd3d3baf1cc8fed544ef83a2c0e6027514a020e56114212ffe1036f2" sha256: cfc2d970829202eca09e2896f0a5aa7c87302817ecc0bdfa954f026046bf10ba
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.6.0" version: "4.20.0"
firebase_auth_platform_interface: firebase_auth_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_auth_platform_interface name: firebase_auth_platform_interface
sha256: fde52352bfd553f5e38239b868e1e6fe6c1a6af542f5cc547554c61fef30b99b sha256: a0270e1db3b2098a14cb2a2342b3cd2e7e458e0c391b1f64f6f78b14296ec093
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.7.0" version: "7.3.0"
firebase_auth_web: firebase_auth_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_auth_web name: firebase_auth_web
sha256: "33a0e6521a1c8c476949cc4f2f7b7d9ed8f99514b08dd905fdb9546d2391e5d9" sha256: "64e067e763c6378b7e774e872f0f59f6812885e43020e25cde08f42e9459837b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.15.0" version: "5.12.0"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: "420d9111dcf095341f1ea8fdce926eef750cf7b9745d21f38000de780c94f608" sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.14.0" version: "2.32.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -181,34 +181,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.23.0" version: "2.17.5"
firebase_storage: firebase_storage:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_storage name: firebase_storage
sha256: a14390edb4b7a119297ddd09773421233ffddcfa1f8dcb670d9ce71672fdb624 sha256: "2ae478ceec9f458c1bcbf0ee3e0100e4e909708979e83f16d5d9fba35a5b42c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.4.7" version: "11.7.7"
firebase_storage_platform_interface: firebase_storage_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_storage_platform_interface name: firebase_storage_platform_interface
sha256: d6c7a32c714cd7d59b61a5c610137695547986b8a5abe71aaf71197d6d255006 sha256: "4e18662e6a66e2e0e181c06f94707de06d5097d70cfe2b5141bf64660c5b5da9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.7" version: "5.1.22"
firebase_storage_web: firebase_storage_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_storage_web name: firebase_storage_web
sha256: "45ad71124862ef6d3a68770edc0aaefb854c0b4781aa96e9b0ec10400af03861" sha256: "3a44aacd38a372efb159f6fe36bb4a7d79823949383816457fd43d3d47602a53"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.10.14" version: "3.9.7"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -226,10 +226,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "5.0.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -332,10 +332,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.1" version: "0.20.2"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -364,10 +364,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "5.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -489,10 +489,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: table_calendar name: table_calendar
sha256: "1e3521a3e6d3fc7f645a58b135ab663d458ab12504f1ea7f9b4b81d47086c478" sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.9" version: "3.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -545,10 +545,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "0.5.1"
sdks: sdks:
dart: ">=3.8.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.27.0"

View File

@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=3.2.3 <4.0.0' sdk: '>=3.7.0 <4.0.0'
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -33,16 +33,16 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.8
firebase_core: ^3.14.0 firebase_core: ^2.30.1
table_calendar: ^3.0.9 table_calendar: ^3.1.1
cloud_firestore: ^5.6.9 cloud_firestore: ^4.15.8
firebase_auth: ^5.6.0 firebase_auth: ^4.17.8
firebase_storage: ^12.4.7 firebase_storage: ^11.6.6
image_picker: ^1.0.7 image_picker: ^1.1.1
provider: ^6.1.1 provider: ^6.1.2
uuid: ^4.2.1 uuid: ^4.3.3
intl: ^0.18.0 intl: ^0.20.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -53,7 +53,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^6.0.0 flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec