Trainer können Trainings erstellen/löschen

main
joschy2002 2025-05-26 09:28:37 +02:00
parent 34e959e5ca
commit cc5e51b9ec
4 changed files with 630 additions and 289 deletions

View File

@ -2,13 +2,15 @@
// Einstiegspunkt der App und globale Konfigurationen // Einstiegspunkt der App und globale Konfigurationen
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; import 'package:trainerbox/firebase_options.dart';
import 'screens/home_screen.dart'; import 'screens/home_screen.dart';
import 'screens/login_screen.dart'; import 'screens/login_screen.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp()); runApp(const MyApp());
} }
@ -37,10 +39,10 @@ class _MyAppState extends State<MyApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Trainer App', title: 'TrainerBox',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink), colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true, useMaterial3: true,
), ),
home: _loggedIn home: _loggedIn

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class CalendarTab extends StatefulWidget { class CalendarTab extends StatefulWidget {
const CalendarTab({super.key}); const CalendarTab({super.key});
@ -9,165 +11,373 @@ class CalendarTab extends StatefulWidget {
} }
class _CalendarTabState extends State<CalendarTab> { class _CalendarTabState extends State<CalendarTab> {
CalendarFormat _calendarFormat = CalendarFormat.month; CalendarFormat _calendarFormat = CalendarFormat.week;
DateTime _focusedDay = DateTime.now(); late DateTime _focusedDay;
DateTime? _selectedDay; DateTime? _selectedDay;
Map<DateTime, List<Map<String, dynamic>>> _events = {}; Map<DateTime, List<Map<String, dynamic>>> _events = {};
bool _isLoading = false;
String? _currentUserId;
String? _userRole;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedDay = DateTime.now(); _focusedDay = DateTime.now();
final now = DateTime.now(); _selectedDay = _focusedDay;
_focusedDay = DateTime(now.year, now.month, now.day); _currentUserId = FirebaseAuth.instance.currentUser?.uid;
_initializeData();
}
// Beispiel-Events Future<void> _initializeData() async {
_events = { if (_currentUserId != null) {
_focusedDay: [ final userDoc = await FirebaseFirestore.instance
{ .collection('User')
'title': 'Ganzkörper Workout', .doc(_currentUserId)
'time': '09:00', .get();
'duration': '45 Minuten',
}, if (userDoc.exists) {
{'title': 'Yoga Session', 'time': '17:30', 'duration': '30 Minuten'}, setState(() {
], _userRole = userDoc.data()?['role'] as String?;
_focusedDay.add(const Duration(days: 1)): [ });
{'title': 'HIIT Training', 'time': '08:00', 'duration': '30 Minuten'}, await _loadEvents();
], }
}
}
Future<void> _loadEvents() async {
if (_userRole == null) return;
setState(() => _isLoading = true);
try {
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
trainersSnapshot = await FirebaseFirestore.instance
.collection('User')
.where('role', isEqualTo: 'trainer')
.get();
}
final events = <DateTime, List<Map<String, dynamic>>>{};
for (var trainerDoc in trainersSnapshot.docs) {
final trainerData = trainerDoc.data() as Map<String, dynamic>;
final trainingTimes = trainerData['trainingTimes'] as Map<String, dynamic>? ?? {};
final trainingDurations = trainerData['trainingDurations'] as Map<String, dynamic>? ?? {};
final cancelledTrainings = trainerData['cancelledTrainings'] as List<dynamic>? ?? [];
trainingTimes.forEach((day, timeStr) {
if (timeStr == null) return;
final duration = trainingDurations[day] as int? ?? 60;
final timeParts = (timeStr as String).split(':');
if (timeParts.length != 2) return;
final hour = int.tryParse(timeParts[0]) ?? 0;
final minute = int.tryParse(timeParts[1]) ?? 0;
final now = DateTime.now();
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);
// 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);
return isSameDay(cancelledDate, normalizedDate);
}
return false;
});
if (!isCancelled) {
final event = {
'trainerName': trainerData['name'] ?? 'Unbekannter Trainer',
'time': timeStr,
'duration': duration,
'trainerId': trainerDoc.id,
'isCurrentUser': trainerDoc.id == _currentUserId,
'day': day,
'date': normalizedDate.toIso8601String(),
};
if (events.containsKey(normalizedDate)) {
events[normalizedDate]!.add(event);
} else {
events[normalizedDate] = [event];
}
}
}
});
}
setState(() {
_events = events;
_isLoading = false;
});
} catch (e) {
print('Error loading events: $e');
setState(() => _isLoading = false);
}
}
Future<void> _deleteTraining(Map<String, dynamic> event) 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 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'],
'time': event['time'],
'duration': event['duration'],
});
// Aktualisiere die Daten in Firestore
await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.update({
'cancelledTrainings': cancelledTrainings,
});
// Lade die Events neu
await _loadEvents();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Training wurde gelöscht')),
);
}
} catch (e) {
print('Error deleting training: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fehler beim Löschen des Trainings')),
);
}
}
}
int _getDaysUntilNext(String day, int currentWeekday) {
final weekdays = {
'Montag': 1,
'Dienstag': 2,
'Mittwoch': 3,
'Donnerstag': 4,
'Freitag': 5,
'Samstag': 6,
'Sonntag': 7,
}; };
final targetWeekday = weekdays[day] ?? 1;
var daysUntilNext = targetWeekday - currentWeekday;
if (daysUntilNext <= 0) {
daysUntilNext += 7;
}
return daysUntilNext;
} }
List<Map<String, dynamic>> _getEventsForDay(DateTime day) { List<Map<String, dynamic>> _getEventsForDay(DateTime day) {
return _events[DateTime(day.year, day.month, day.day)] ?? []; final normalizedDate = DateTime(day.year, day.month, day.day);
return _events[normalizedDate] ?? [];
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final firstDay = DateTime.utc(1900, 1, 1);
final lastDay = DateTime.utc(2999, 12, 31);
if (_focusedDay.isBefore(firstDay)) {
_focusedDay = firstDay;
} else if (_focusedDay.isAfter(lastDay)) {
_focusedDay = lastDay;
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Trainingsplan'), title: const Text('Kalender'),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.add), icon: const Icon(Icons.refresh),
onPressed: () { onPressed: _isLoading ? null : _loadEvents,
// TODO: Implement add workout to calendar
},
), ),
], ],
), ),
body: Column( body: SafeArea(
children: [ child: Column(
TableCalendar( children: [
firstDay: firstDay, Container(
lastDay: lastDay, padding: const EdgeInsets.all(8.0),
focusedDay: _focusedDay, child: TableCalendar(
calendarFormat: _calendarFormat, firstDay: DateTime.utc(2024, 1, 1),
selectedDayPredicate: (day) { lastDay: DateTime.utc(2025, 12, 31),
return isSameDay(_selectedDay, day); focusedDay: _focusedDay,
}, calendarFormat: _calendarFormat,
onDaySelected: (selectedDay, focusedDay) { selectedDayPredicate: (day) {
setState(() { return isSameDay(_selectedDay, day);
_selectedDay = selectedDay; },
_focusedDay = focusedDay; onDaySelected: (selectedDay, focusedDay) {
}); setState(() {
}, _selectedDay = selectedDay;
onFormatChanged: (format) { _focusedDay = focusedDay;
setState(() { });
_calendarFormat = format; },
}); onFormatChanged: (format) {
}, setState(() {
onPageChanged: (focusedDay) { _calendarFormat = format;
setState(() { });
_focusedDay = focusedDay; },
}); onPageChanged: (focusedDay) {
}, setState(() {
eventLoader: _getEventsForDay, _focusedDay = focusedDay;
calendarStyle: const CalendarStyle( });
markersMaxCount: 1, },
markerDecoration: BoxDecoration( eventLoader: _getEventsForDay,
color: Colors.blue, calendarStyle: const CalendarStyle(
shape: BoxShape.circle, markersMaxCount: 1,
markerDecoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
headerStyle: const HeaderStyle(
formatButtonVisible: true,
titleCentered: true,
),
calendarBuilders: CalendarBuilders(
markerBuilder: (context, date, events) {
if (events.isEmpty) return null;
return Positioned(
bottom: 1,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
);
},
),
startingDayOfWeek: StartingDayOfWeek.monday,
daysOfWeekStyle: const DaysOfWeekStyle(
weekdayStyle: TextStyle(fontWeight: FontWeight.bold),
weekendStyle: TextStyle(color: Colors.red),
),
availableCalendarFormats: const {
CalendarFormat.week: 'Woche',
CalendarFormat.month: 'Monat',
},
), ),
), ),
), const Divider(),
const Divider(), Expanded(
Expanded( child: _isLoading
child: ? const Center(child: CircularProgressIndicator())
_getEventsForDay(_selectedDay!).isEmpty : _selectedDay == null
? const Center( ? const Center(child: Text('Bitte wähle einen Tag aus'))
child: Column( : ListView.builder(
mainAxisAlignment: MainAxisAlignment.center, padding: const EdgeInsets.symmetric(horizontal: 16),
children: [ itemCount: _getEventsForDay(_selectedDay!).length,
Icon(Icons.event_busy, size: 64, color: Colors.grey), itemBuilder: (context, index) {
SizedBox(height: 16), final event = _getEventsForDay(_selectedDay!)[index];
Text( final isCurrentUser = event['isCurrentUser'] as bool;
'Keine Trainings geplant', return Card(
style: TextStyle(fontSize: 18, color: Colors.grey), margin: const EdgeInsets.only(bottom: 8),
), color: isCurrentUser ? Colors.blue.withOpacity(0.1) : null,
SizedBox(height: 8), child: ListTile(
Text( leading: Icon(
'Füge Trainings zu deinem Kalender hinzu', Icons.sports,
style: TextStyle(fontSize: 14, color: Colors.grey), color: isCurrentUser ? Colors.blue : null,
), ),
], title: Text(
), isCurrentUser ? 'Training' : event['trainerName'],
) style: TextStyle(
: ListView.builder( fontWeight: isCurrentUser ? FontWeight.bold : null,
padding: const EdgeInsets.all(16), ),
itemCount: _getEventsForDay(_selectedDay!).length, ),
itemBuilder: (context, index) { subtitle: Text(
final event = _getEventsForDay(_selectedDay!)[index]; '${event['time']} - ${event['duration']} Minuten',
return Card( ),
margin: const EdgeInsets.only(bottom: 16), trailing: Row(
child: ListTile( mainAxisSize: MainAxisSize.min,
leading: Container( children: [
width: 48, IconButton(
height: 48, icon: const Icon(Icons.info),
decoration: BoxDecoration( onPressed: () {
color: Colors.blue[100], showDialog(
borderRadius: BorderRadius.circular(8), 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'),
),
],
),
);
},
),
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)),
),
],
),
);
},
),
],
),
), ),
child: const Icon( );
Icons.fitness_center, },
color: Colors.blue, ),
), ),
), ],
title: Text( ),
event['title'],
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
'${event['time']}${event['duration']}',
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () {
// TODO: Implement delete workout from calendar
},
),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// TODO: Implement add workout to calendar
},
child: const Icon(Icons.add),
), ),
); );
} }

View File

@ -2,179 +2,308 @@ import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
class ProfileTab extends StatelessWidget { class ProfileTab extends StatefulWidget {
final VoidCallback? onLogoutSuccess; final VoidCallback? onLogoutSuccess;
const ProfileTab({super.key, this.onLogoutSuccess}); const ProfileTab({super.key, this.onLogoutSuccess});
Future<void> _logout(BuildContext context) async { @override
await FirebaseAuth.instance.signOut(); State<ProfileTab> createState() => _ProfileTabState();
if (onLogoutSuccess != null) { }
onLogoutSuccess!();
class _ProfileTabState extends State<ProfileTab> {
final _formKey = GlobalKey<FormState>();
bool _isTrainer = false;
bool _isLoading = false;
Map<String, TimeOfDay> _trainingTimes = {};
Map<String, int> _trainingDurations = {};
@override
void initState() {
super.initState();
_loadUserData();
}
Future<void> _loadUserData() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
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'] ?? {});
}
});
}
} catch (e) {
print('Error loading user data: $e');
}
}
Future<void> _saveTrainingTime(String day, TimeOfDay? time, int? duration) async {
if (time == null || duration == null) return;
setState(() => _isLoading = true);
try {
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
final timeString = '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
await FirebaseFirestore.instance.collection('User').doc(user.uid).update({
'trainingTimes.$day': timeString,
'trainingDurations.$day': duration,
});
setState(() {
_trainingTimes[day] = time;
_trainingDurations[day] = duration;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Trainingszeit gespeichert')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Fehler beim Speichern: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _removeTrainingTime(String day) async {
setState(() => _isLoading = true);
try {
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
await FirebaseFirestore.instance.collection('User').doc(user.uid).update({
'trainingTimes.$day': FieldValue.delete(),
'trainingDurations.$day': FieldValue.delete(),
});
setState(() {
_trainingTimes.remove(day);
_trainingDurations.remove(day);
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Trainingszeit entfernt')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Fehler beim Entfernen: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _selectTime(BuildContext context, String day) async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: _trainingTimes[day] ?? TimeOfDay.now(),
);
if (picked != null) {
final duration = await showDialog<int>(
context: context,
builder: (context) => _DurationDialog(
initialDuration: _trainingDurations[day] ?? 60,
),
);
if (duration != null) {
await _saveTrainingTime(day, picked, duration);
}
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser; final user = FirebaseAuth.instance.currentUser;
final email = user?.email ?? 'Keine E-Mail gefunden'; if (user == null) {
final uid = user?.uid; return const Center(child: Text('Nicht eingeloggt'));
}
return Scaffold( return Scaffold(
body: uid == null appBar: AppBar(
? const Center(child: Text('Nicht eingeloggt')) title: const Text('Profil'),
: FutureBuilder<DocumentSnapshot>( actions: [
future: FirebaseFirestore.instance.collection('User').doc(uid).get(), IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await FirebaseAuth.instance.signOut();
if (widget.onLogoutSuccess != null) {
widget.onLogoutSuccess!();
}
},
),
],
),
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) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (!snapshot.hasData || !snapshot.data!.exists) { if (!snapshot.hasData || !snapshot.data!.exists) {
return const Center(child: Text('Keine Profildaten gefunden')); return const Center(child: Text('Keine Daten gefunden'));
} }
final data = snapshot.data!.data() as Map<String, dynamic>; final data = snapshot.data!.data() as Map<String, dynamic>;
final name = data['name'] ?? 'Unbekannt'; return Column(
final role = data['role'] == 'trainer' ? 'Trainer' : 'Spieler'; crossAxisAlignment: CrossAxisAlignment.start,
final createdAt = (data['createdAt'] is Timestamp) children: [
? (data['createdAt'] as Timestamp).toDate() Text('Name: ${data['name'] ?? '-'}'),
: null; const SizedBox(height: 8),
final createdAtStr = createdAt != null Text('E-Mail: ${user.email ?? '-'}'),
? '${createdAt.day.toString().padLeft(2, '0')}.${createdAt.month.toString().padLeft(2, '0')}.${createdAt.year}' const SizedBox(height: 8),
: 'Unbekannt'; Text('Rolle: ${data['role'] ?? '-'}'),
// Beispiel-Benutzerdaten (Statistiken als Demo) if (_isTrainer) ...[
final Map<String, dynamic> userData = { const SizedBox(height: 24),
'name': name, const Text(
'level': role, 'Trainingszeiten',
'joinedDate': createdAtStr, style: TextStyle(
'workoutsCompleted': 42, fontSize: 18,
'totalMinutes': 1260, fontWeight: FontWeight.bold,
};
return CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text(userData['name']),
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue[700]!, Colors.blue[500]!],
),
),
child: const Center(
child: Icon(Icons.person, size: 80, color: Colors.white),
),
), ),
), ),
), const SizedBox(height: 16),
SliverToBoxAdapter( ...['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
child: Padding( .map((day) => Card(
padding: const EdgeInsets.all(16.0), margin: const EdgeInsets.only(bottom: 8),
child: Column( child: ListTile(
crossAxisAlignment: CrossAxisAlignment.start, title: Text(day),
children: [ subtitle: _trainingTimes[day] != null
_buildInfoCard( ? Text(
title: 'Persönliche Informationen', '${_trainingTimes[day]!.format(context)} - ${_trainingDurations[day]} Minuten',
child: Column( )
children: [ : const Text('Keine Trainingszeit'),
_buildInfoRow('E-Mail', email, Icons.email), trailing: Row(
const Divider(), mainAxisSize: MainAxisSize.min,
_buildInfoRow('Name', name, Icons.person), children: [
const Divider(), IconButton(
_buildInfoRow('Rolle', role, Icons.badge), icon: Icon(
const Divider(), _trainingTimes[day] != null
_buildInfoRow( ? Icons.edit
'Mitglied seit', : Icons.add,
userData['joinedDate'], ),
Icons.calendar_today, 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), ],
_buildInfoCard(
title: 'Einstellungen',
child: Column(
children: [
ListTile(
leading: const Icon(Icons.notifications),
title: const Text('Benachrichtigungen'),
trailing: Switch(
value: true,
onChanged: (value) {},
),
),
const Divider(),
ListTile(
leading: const Icon(Icons.dark_mode),
title: const Text('Dark Mode'),
trailing: Switch(
value: false,
onChanged: (value) {},
),
),
const Divider(),
ListTile(
leading: const Icon(Icons.language),
title: const Text('Sprache'),
trailing: const Text('Deutsch'),
onTap: () {},
),
],
),
),
const SizedBox(height: 16),
Center(
child: TextButton.icon(
onPressed: () => _logout(context),
icon: const Icon(Icons.logout),
label: const Text('Abmelden'),
style: TextButton.styleFrom(foregroundColor: Colors.red),
),
),
],
),
),
),
], ],
); );
}, },
), ),
); ],
} ),
Widget _buildInfoCard({required String title, required Widget child}) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
child,
],
),
);
}
Widget _buildInfoRow(String label, String value, IconData icon) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Row(
children: [
Icon(icon, color: Colors.blue),
const SizedBox(width: 16),
Expanded(child: Text(label)),
Text(value, style: TextStyle(color: Colors.grey[600])),
],
), ),
); );
} }
} }
class _DurationDialog extends StatefulWidget {
final int initialDuration;
const _DurationDialog({required this.initialDuration});
@override
State<_DurationDialog> createState() => _DurationDialogState();
}
class _DurationDialogState extends State<_DurationDialog> {
late int _duration;
@override
void initState() {
super.initState();
_duration = widget.initialDuration;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Trainingsdauer'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Wähle die Dauer der Trainingseinheit in Minuten:'),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
if (_duration > 15) {
setState(() => _duration -= 15);
}
},
),
Text(
'$_duration Minuten',
style: const TextStyle(fontSize: 18),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() => _duration += 15);
},
),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Abbrechen'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, _duration),
child: const Text('Bestätigen'),
),
],
);
}
}

View File

@ -316,10 +316,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.2" version: "0.19.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -465,10 +465,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: table_calendar name: table_calendar
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.0" version: "3.1.3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description: