Trainingsplan erstellen
parent
cc5e51b9ec
commit
b8af193bde
|
|
@ -5,6 +5,7 @@ import 'package:firebase_core/firebase_core.dart';
|
|||
import 'package:trainerbox/firebase_options.dart';
|
||||
import 'screens/home_screen.dart';
|
||||
import 'screens/login_screen.dart';
|
||||
import 'screens/search_tab.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -48,6 +49,12 @@ class _MyAppState extends State<MyApp> {
|
|||
home: _loggedIn
|
||||
? HomeScreen(onLogoutSuccess: _handleLogoutSuccess)
|
||||
: LoginScreen(onLoginSuccess: _handleLoginSuccess),
|
||||
routes: {
|
||||
'/search': (context) => SearchTab(
|
||||
selectMode: (ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?)?['selectMode'] ?? false,
|
||||
remainingTime: (ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?)?['remainingTime'] as int?,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'training_detail_screen.dart';
|
||||
|
||||
class CalendarTab extends StatefulWidget {
|
||||
const CalendarTab({super.key});
|
||||
|
|
@ -18,6 +19,15 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
bool _isLoading = false;
|
||||
String? _currentUserId;
|
||||
String? _userRole;
|
||||
final _exerciseController = TextEditingController();
|
||||
final _durationController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_exerciseController.dispose();
|
||||
_durationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -52,17 +62,37 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
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: _currentUserId)
|
||||
.get();
|
||||
} else {
|
||||
// Spieler sehen alle Trainings
|
||||
final userDoc = await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(_currentUserId)
|
||||
.get();
|
||||
|
||||
if (!userDoc.exists) {
|
||||
setState(() => _isLoading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
final userData = userDoc.data() as Map<String, dynamic>;
|
||||
final userClub = userData['club'] as String?;
|
||||
|
||||
if (userClub == null || userClub.isEmpty) {
|
||||
setState(() {
|
||||
_events = {};
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
trainersSnapshot = await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.where('role', isEqualTo: 'trainer')
|
||||
.where('club', isEqualTo: userClub)
|
||||
.get();
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +103,7 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
final trainingTimes = trainerData['trainingTimes'] as Map<String, dynamic>? ?? {};
|
||||
final trainingDurations = trainerData['trainingDurations'] as Map<String, dynamic>? ?? {};
|
||||
final cancelledTrainings = trainerData['cancelledTrainings'] as List<dynamic>? ?? [];
|
||||
final trainingExercises = trainerData['trainingExercises'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
trainingTimes.forEach((day, timeStr) {
|
||||
if (timeStr == null) return;
|
||||
|
|
@ -88,12 +119,11 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
final daysUntilNext = _getDaysUntilNext(day, now.weekday);
|
||||
final eventDate = DateTime(now.year, now.month, now.day + daysUntilNext, hour, minute);
|
||||
|
||||
// Erstelle Trainings für ein ganzes Jahr
|
||||
for (var i = 0; i < 52; i++) {
|
||||
final date = eventDate.add(Duration(days: i * 7));
|
||||
final normalizedDate = DateTime(date.year, date.month, date.day);
|
||||
final dateString = normalizedDate.toIso8601String();
|
||||
|
||||
// Prüfe, ob das Training an diesem Tag abgesagt wurde
|
||||
final isCancelled = cancelledTrainings.any((cancelled) {
|
||||
if (cancelled is Map<String, dynamic>) {
|
||||
final cancelledDate = DateTime.parse(cancelled['date'] as String);
|
||||
|
|
@ -103,6 +133,14 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
});
|
||||
|
||||
if (!isCancelled) {
|
||||
final exercises = trainingExercises[dateString] as List<dynamic>? ?? [];
|
||||
final totalExerciseDuration = exercises.fold<int>(0, (sum, exercise) {
|
||||
if (exercise is Map<String, dynamic>) {
|
||||
return sum + (exercise['duration'] as int? ?? 0);
|
||||
}
|
||||
return sum;
|
||||
});
|
||||
|
||||
final event = {
|
||||
'trainerName': trainerData['name'] ?? 'Unbekannter Trainer',
|
||||
'time': timeStr,
|
||||
|
|
@ -110,7 +148,10 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
'trainerId': trainerDoc.id,
|
||||
'isCurrentUser': trainerDoc.id == _currentUserId,
|
||||
'day': day,
|
||||
'date': normalizedDate.toIso8601String(),
|
||||
'date': dateString,
|
||||
'exercises': exercises,
|
||||
'remainingTime': duration - totalExerciseDuration,
|
||||
'club': trainerData['club'] ?? 'Kein Verein',
|
||||
};
|
||||
|
||||
if (events.containsKey(normalizedDate)) {
|
||||
|
|
@ -133,6 +174,67 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _addExercise(Map<String, dynamic> event) async {
|
||||
if (_userRole != 'trainer' || !event['isCurrentUser']) return;
|
||||
|
||||
// Navigiere zum Suchbildschirm und warte auf das Ergebnis
|
||||
final result = await Navigator.pushNamed(
|
||||
context,
|
||||
'/search',
|
||||
arguments: {
|
||||
'selectMode': true,
|
||||
'remainingTime': event['remainingTime'],
|
||||
},
|
||||
);
|
||||
|
||||
// Wenn eine Übung ausgewählt wurde
|
||||
if (result != null && result is Map<String, dynamic>) {
|
||||
try {
|
||||
final userDoc = await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(_currentUserId)
|
||||
.get();
|
||||
|
||||
if (!userDoc.exists) return;
|
||||
|
||||
final data = userDoc.data() as Map<String, dynamic>;
|
||||
final trainingExercises = Map<String, dynamic>.from(data['trainingExercises'] ?? {});
|
||||
final exercises = List<Map<String, dynamic>>.from(trainingExercises[event['date']] ?? []);
|
||||
|
||||
exercises.add({
|
||||
'id': result['id'],
|
||||
'name': result['title'],
|
||||
'description': result['description'],
|
||||
'duration': result['duration'],
|
||||
});
|
||||
|
||||
trainingExercises[event['date']] = exercises;
|
||||
|
||||
await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(_currentUserId)
|
||||
.update({
|
||||
'trainingExercises': trainingExercises,
|
||||
});
|
||||
|
||||
await _loadEvents();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Übung wurde hinzugefügt')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error adding exercise: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Fehler beim Hinzufügen der Übung')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteTraining(Map<String, dynamic> event) async {
|
||||
if (_userRole != 'trainer' || !event['isCurrentUser']) return;
|
||||
|
||||
|
|
@ -147,7 +249,6 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
final data = userDoc.data() as Map<String, dynamic>;
|
||||
final cancelledTrainings = List<Map<String, dynamic>>.from(data['cancelledTrainings'] ?? []);
|
||||
|
||||
// Füge das Training zur Liste der abgesagten Trainings hinzu
|
||||
cancelledTrainings.add({
|
||||
'date': event['date'],
|
||||
'day': event['day'],
|
||||
|
|
@ -155,7 +256,6 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
'duration': event['duration'],
|
||||
});
|
||||
|
||||
// Aktualisiere die Daten in Firestore
|
||||
await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(_currentUserId)
|
||||
|
|
@ -163,7 +263,6 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
'cancelledTrainings': cancelledTrainings,
|
||||
});
|
||||
|
||||
// Lade die Events neu
|
||||
await _loadEvents();
|
||||
|
||||
if (mounted) {
|
||||
|
|
@ -181,6 +280,50 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _removeExercise(Map<String, dynamic> event, Map<String, dynamic> exercise) async {
|
||||
if (_userRole != 'trainer' || !event['isCurrentUser']) return;
|
||||
|
||||
try {
|
||||
final userDoc = await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(_currentUserId)
|
||||
.get();
|
||||
|
||||
if (!userDoc.exists) return;
|
||||
|
||||
final data = userDoc.data() as Map<String, dynamic>;
|
||||
final trainingExercises = Map<String, dynamic>.from(data['trainingExercises'] ?? {});
|
||||
final exercises = List<Map<String, dynamic>>.from(trainingExercises[event['date']] ?? []);
|
||||
|
||||
// Entferne die Übung aus der Liste
|
||||
exercises.removeWhere((e) => e['id'] == exercise['id']);
|
||||
|
||||
trainingExercises[event['date']] = exercises;
|
||||
|
||||
await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(_currentUserId)
|
||||
.update({
|
||||
'trainingExercises': trainingExercises,
|
||||
});
|
||||
|
||||
await _loadEvents();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Übung wurde entfernt')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error removing exercise: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Fehler beim Entfernen der Übung')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int _getDaysUntilNext(String day, int currentWeekday) {
|
||||
final weekdays = {
|
||||
'Montag': 1,
|
||||
|
|
@ -297,80 +440,183 @@ class _CalendarTabState extends State<CalendarTab> {
|
|||
itemBuilder: (context, index) {
|
||||
final event = _getEventsForDay(_selectedDay!)[index];
|
||||
final isCurrentUser = event['isCurrentUser'] as bool;
|
||||
final exercises = event['exercises'] as List<dynamic>;
|
||||
final remainingTime = event['remainingTime'] as int;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
color: isCurrentUser ? Colors.blue.withOpacity(0.1) : null,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
Icons.sports,
|
||||
color: isCurrentUser ? Colors.blue : null,
|
||||
),
|
||||
title: Text(
|
||||
isCurrentUser ? 'Training' : event['trainerName'],
|
||||
style: TextStyle(
|
||||
fontWeight: isCurrentUser ? FontWeight.bold : null,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${event['time']} - ${event['duration']} Minuten',
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(isCurrentUser ? 'Training' : event['trainerName']),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Zeit: ${event['time']}'),
|
||||
const SizedBox(height: 8),
|
||||
Text('Dauer: ${event['duration']} Minuten'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Schließen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.sports,
|
||||
color: isCurrentUser ? Colors.blue : null,
|
||||
),
|
||||
if (isCurrentUser)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Training löschen'),
|
||||
content: const Text('Möchten Sie dieses Training wirklich löschen?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_deleteTraining(event);
|
||||
},
|
||||
child: const Text('Löschen', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
isCurrentUser ? 'Training' : event['trainerName'],
|
||||
style: TextStyle(
|
||||
fontWeight: isCurrentUser ? FontWeight.bold : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${event['time']} - ${event['duration']} Minuten\nVerbleibende Zeit: $remainingTime Minuten',
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => _addExercise(event),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(isCurrentUser ? 'Training' : event['trainerName']),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Zeit: ${event['time']}'),
|
||||
const SizedBox(height: 8),
|
||||
Text('Dauer: ${event['duration']} Minuten'),
|
||||
const SizedBox(height: 8),
|
||||
Text('Verbleibende Zeit: $remainingTime Minuten'),
|
||||
if (exercises.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Text('Übungen:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
...exercises.map((exercise) {
|
||||
if (exercise is Map<String, dynamic>) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
'${exercise['name']} - ${exercise['duration']} Minuten',
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Schließen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (isCurrentUser)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Training löschen'),
|
||||
content: const Text('Möchten Sie dieses Training wirklich löschen?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_deleteTraining(event);
|
||||
},
|
||||
child: const Text('Löschen', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (exercises.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Divider(),
|
||||
const Text('Übungen:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
...exercises.map((exercise) {
|
||||
if (exercise is Map<String, dynamic>) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.fitness_center, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TrainingDetailScreen(
|
||||
trainingId: exercise['id'],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'${exercise['name']} - ${exercise['duration']} Minuten',
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isCurrentUser)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Übung entfernen'),
|
||||
content: const Text('Möchten Sie diese Übung wirklich entfernen?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_removeExercise(event, exercise);
|
||||
},
|
||||
child: const Text('Entfernen', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ProfileTab extends StatefulWidget {
|
||||
final VoidCallback? onLogoutSuccess;
|
||||
|
|
@ -14,8 +15,13 @@ class _ProfileTabState extends State<ProfileTab> {
|
|||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isTrainer = false;
|
||||
bool _isLoading = false;
|
||||
Map<String, TimeOfDay> _trainingTimes = {};
|
||||
Map<String, TimeOfDay?> _trainingTimes = {};
|
||||
Map<String, int> _trainingDurations = {};
|
||||
String _name = '';
|
||||
String _email = '';
|
||||
String _club = '';
|
||||
String? _userRole;
|
||||
DateTime? _joinDate;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -27,30 +33,49 @@ class _ProfileTabState extends State<ProfileTab> {
|
|||
final user = FirebaseAuth.instance.currentUser;
|
||||
if (user == null) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
final doc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get();
|
||||
if (doc.exists) {
|
||||
final data = doc.data()!;
|
||||
setState(() {
|
||||
_isTrainer = data['role'] == 'trainer';
|
||||
if (_isTrainer) {
|
||||
_trainingTimes = Map<String, TimeOfDay>.from(
|
||||
(data['trainingTimes'] ?? {}).map(
|
||||
(key, value) => MapEntry(
|
||||
key,
|
||||
TimeOfDay(
|
||||
hour: int.parse(value.split(':')[0]),
|
||||
minute: int.parse(value.split(':')[1]),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
_trainingDurations = Map<String, int>.from(data['trainingDurations'] ?? {});
|
||||
final trainingTimes = data['trainingTimes'] as Map<String, dynamic>? ?? {};
|
||||
final trainingDurations = data['trainingDurations'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
final convertedTrainingTimes = <String, TimeOfDay?>{};
|
||||
trainingTimes.forEach((key, value) {
|
||||
if (value != null) {
|
||||
final timeStr = value.toString();
|
||||
final timeParts = timeStr.split(':');
|
||||
if (timeParts.length == 2) {
|
||||
final hour = int.tryParse(timeParts[0]) ?? 0;
|
||||
final minute = int.tryParse(timeParts[1]) ?? 0;
|
||||
convertedTrainingTimes[key] = TimeOfDay(hour: hour, minute: minute);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setState(() {
|
||||
_name = data['name'] ?? '';
|
||||
_email = data['email'] ?? '';
|
||||
_club = data['club'] ?? '';
|
||||
_isTrainer = data['role'] == 'trainer';
|
||||
_userRole = data['role'] as String?;
|
||||
_trainingTimes = convertedTrainingTimes;
|
||||
_trainingDurations = Map<String, int>.from(trainingDurations);
|
||||
_joinDate = (data['createdAt'] as Timestamp?)?.toDate();
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading user data: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler beim Laden der Daten: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,15 +99,21 @@ class _ProfileTabState extends State<ProfileTab> {
|
|||
_trainingDurations[day] = duration;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Trainingszeit gespeichert')),
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Trainingszeit gespeichert')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler beim Speichern: $e')),
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler beim Speichern: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,15 +133,21 @@ class _ProfileTabState extends State<ProfileTab> {
|
|||
_trainingDurations.remove(day);
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Trainingszeit entfernt')),
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Trainingszeit entfernt')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler beim Entfernen: $e')),
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler beim Entfernen: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +169,47 @@ class _ProfileTabState extends State<ProfileTab> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _saveUserData() async {
|
||||
if (FirebaseAuth.instance.currentUser == null) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
final trainingTimesMap = <String, String>{};
|
||||
_trainingTimes.forEach((key, value) {
|
||||
if (value != null) {
|
||||
trainingTimesMap[key] = '${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
});
|
||||
|
||||
await FirebaseFirestore.instance
|
||||
.collection('User')
|
||||
.doc(FirebaseAuth.instance.currentUser!.uid)
|
||||
.update({
|
||||
'name': _name,
|
||||
'club': _club,
|
||||
'trainingTimes': trainingTimesMap,
|
||||
'trainingDurations': _trainingDurations,
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Profil wurde aktualisiert')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error saving user data: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Fehler beim Speichern des Profils')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
|
|
@ -152,92 +230,167 @@ class _ProfileTabState extends State<ProfileTab> {
|
|||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: _isLoading ? null : _saveUserData,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Persönliche Informationen',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FutureBuilder<DocumentSnapshot>(
|
||||
future: FirebaseFirestore.instance.collection('User').doc(user.uid).get(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (!snapshot.hasData || !snapshot.data!.exists) {
|
||||
return const Center(child: Text('Keine Daten gefunden'));
|
||||
}
|
||||
final data = snapshot.data!.data() as Map<String, dynamic>;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Name: ${data['name'] ?? '-'}'),
|
||||
const SizedBox(height: 8),
|
||||
Text('E-Mail: ${user.email ?? '-'}'),
|
||||
const SizedBox(height: 8),
|
||||
Text('Rolle: ${data['role'] ?? '-'}'),
|
||||
if (_isTrainer) ...[
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Trainingszeiten',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Card(
|
||||
elevation: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Persönliche Informationen',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: TextEditingController(text: _name),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.person),
|
||||
),
|
||||
onChanged: (value) => _name = value,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: TextEditingController(text: _email),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'E-Mail',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.email),
|
||||
),
|
||||
enabled: false,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: TextEditingController(text: _club),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Verein',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.sports),
|
||||
hintText: 'Geben Sie Ihren Verein ein',
|
||||
),
|
||||
onChanged: (value) => _club = value,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.work),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Rolle: ${_userRole == 'trainer' ? 'Trainer' : 'Spieler'}',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_joinDate != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.calendar_today),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Beigetreten am: ${DateFormat('dd.MM.yyyy').format(_joinDate!)}',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_userRole == 'trainer') ...[
|
||||
const SizedBox(height: 24),
|
||||
Card(
|
||||
elevation: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Trainingszeiten',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
||||
.map((day) => Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.access_time),
|
||||
title: Text(day),
|
||||
subtitle: _trainingTimes[day] != null
|
||||
? Text(
|
||||
'${_trainingTimes[day]!.format(context)} - ${_trainingDurations[day]} Minuten',
|
||||
)
|
||||
: const Text('Keine Trainingszeit'),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_trainingTimes[day] != null
|
||||
? Icons.edit
|
||||
: Icons.add,
|
||||
),
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _selectTime(context, day),
|
||||
),
|
||||
if (_trainingTimes[day] != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _removeTrainingTime(day),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
||||
.map((day) => Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
title: Text(day),
|
||||
subtitle: _trainingTimes[day] != null
|
||||
? Text(
|
||||
'${_trainingTimes[day]!.format(context)} - ${_trainingDurations[day]} Minuten',
|
||||
)
|
||||
: const Text('Keine Trainingszeit'),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_trainingTimes[day] != null
|
||||
? Icons.edit
|
||||
: Icons.add,
|
||||
),
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _selectTime(context, day),
|
||||
),
|
||||
if (_trainingTimes[day] != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _removeTrainingTime(day),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,14 @@ import 'dart:io';
|
|||
import 'training_detail_screen.dart';
|
||||
|
||||
class SearchTab extends StatefulWidget {
|
||||
const SearchTab({super.key});
|
||||
final bool selectMode;
|
||||
final int? remainingTime;
|
||||
|
||||
const SearchTab({
|
||||
super.key,
|
||||
this.selectMode = false,
|
||||
this.remainingTime,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SearchTab> createState() => _SearchTabState();
|
||||
|
|
@ -63,7 +70,7 @@ class _SearchTabState extends State<SearchTab> {
|
|||
}
|
||||
}
|
||||
|
||||
void _showCreateTrainingDialog() {
|
||||
void _showCreateTrainingDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => _CreateTrainingDialog(categories: _categories),
|
||||
|
|
@ -88,191 +95,202 @@ class _SearchTabState extends State<SearchTab> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
title: TextField(
|
||||
appBar: AppBar(
|
||||
title: const Text('Übungen'),
|
||||
actions: [
|
||||
if (widget.selectMode)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
if (_isTrainer && !widget.selectMode)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => _showCreateTrainingDialog(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Suche nach Training...',
|
||||
border: InputBorder.none,
|
||||
hintText: 'Suche nach Übungen...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => _searchController.clear(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (_trainerChecked && _isTrainer)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
tooltip: 'Neues Training erstellen',
|
||||
onPressed: _showCreateTrainingDialog,
|
||||
),
|
||||
],
|
||||
),
|
||||
SliverPadding(
|
||||
if (widget.selectMode && widget.remainingTime != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
'Verbleibende Zeit: ${widget.remainingTime} Minuten',
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Kategorien',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: _categories.map((category) {
|
||||
return FilterChip(
|
||||
label: Text(category),
|
||||
selected: _selectedCategory == category,
|
||||
onSelected: (bool selected) {
|
||||
setState(() {
|
||||
_selectedCategory = selected ? category : null;
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Kategorien',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: _categories.map((category) {
|
||||
return FilterChip(
|
||||
label: Text(category),
|
||||
selected: _selectedCategory == category,
|
||||
onSelected: (bool selected) {
|
||||
setState(() {
|
||||
_selectedCategory = selected ? category : null;
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_searchTerm.isNotEmpty || _selectedCategory != null)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
sliver: FutureBuilder<QuerySnapshot>(
|
||||
future: FirebaseFirestore.instance.collection('Training').get(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(child: Text('Keine Trainings gefunden.')));
|
||||
}
|
||||
final docs = snapshot.data!.docs.where((doc) {
|
||||
Expanded(
|
||||
child: FutureBuilder<QuerySnapshot>(
|
||||
future: FirebaseFirestore.instance.collection('Training').get(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Fehler: ${snapshot.error}'));
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
||||
return const Center(child: Text('Keine Übungen gefunden'));
|
||||
}
|
||||
|
||||
var exercises = snapshot.data!.docs.where((doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
final title = data['title']?.toString().toLowerCase() ?? '';
|
||||
final description = data['description']?.toString().toLowerCase() ?? '';
|
||||
final category = data['category']?.toString() ?? '';
|
||||
final searchTerm = _searchTerm.toLowerCase();
|
||||
|
||||
final matchesSearch = title.contains(searchTerm) || description.contains(searchTerm);
|
||||
final matchesCategory = _selectedCategory == null || category == _selectedCategory;
|
||||
|
||||
return matchesSearch && matchesCategory;
|
||||
}).toList();
|
||||
|
||||
if (exercises.isEmpty) {
|
||||
return const Center(child: Text('Keine Übungen gefunden'));
|
||||
}
|
||||
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.all(8),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
childAspectRatio: 0.75,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
),
|
||||
itemCount: exercises.length,
|
||||
itemBuilder: (context, index) {
|
||||
final doc = exercises[index];
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
final title = (data['title'] ?? '').toString().toLowerCase();
|
||||
final description = (data['description'] ?? '').toString().toLowerCase();
|
||||
final category = (data['category'] ?? '').toString();
|
||||
final matchesSearch = _searchTerm.isEmpty ||
|
||||
title.contains(_searchTerm.toLowerCase()) ||
|
||||
description.contains(_searchTerm.toLowerCase());
|
||||
final matchesCategory = _selectedCategory == null || category == _selectedCategory;
|
||||
return matchesSearch && matchesCategory;
|
||||
}).toList();
|
||||
if (docs.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(child: Text('Keine Trainings gefunden.')));
|
||||
}
|
||||
return SliverGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
childAspectRatio: 0.75,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final data = docs[index].data() as Map<String, dynamic>;
|
||||
final isFavorite = _favorites.contains(docs[index].id);
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TrainingDetailScreen(trainingId: docs[index].id),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: (data['picture'] is String && data['picture'] != '')
|
||||
? Image.network(
|
||||
data['picture'],
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Container(
|
||||
color: Colors.grey[300],
|
||||
child: const Center(
|
||||
child: Icon(Icons.fitness_center, size: 40),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
data['title'] ?? '-',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
final duration = (data['duration'] as num?)?.toInt() ?? 0;
|
||||
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),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
data['description'] ?? '-',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${data['duration'] ?? '-'} Minuten',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.star, size: 16, color: Colors.amber),
|
||||
const SizedBox(width: 4),
|
||||
Text('${data['rating overall'] ?? '-'}'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('Level: ${data['year'] ?? '-'}'),
|
||||
const SizedBox(height: 4),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
color: isFavorite ? Colors.red : null,
|
||||
),
|
||||
onPressed: () => _toggleFavorite(docs[index].id, isFavorite),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, childCount: docs.length),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue