diff --git a/trainerbox/lib/screens/calendar_tab.dart b/trainerbox/lib/screens/calendar_tab.dart index 8ba8019..1563008 100644 --- a/trainerbox/lib/screens/calendar_tab.dart +++ b/trainerbox/lib/screens/calendar_tab.dart @@ -1,17 +1,174 @@ import 'package:flutter/material.dart'; +import 'package:table_calendar/table_calendar.dart'; -class CalendarTab extends StatelessWidget { +class CalendarTab extends StatefulWidget { const CalendarTab({super.key}); + @override + State createState() => _CalendarTabState(); +} + +class _CalendarTabState extends State { + CalendarFormat _calendarFormat = CalendarFormat.month; + DateTime _focusedDay = DateTime.now(); + DateTime? _selectedDay; + Map>> _events = {}; + + @override + void initState() { + super.initState(); + final now = DateTime.now(); + _focusedDay = DateTime(now.year, now.month, now.day); + _selectedDay = _focusedDay; + + // Beispiel-Events + _events = { + _focusedDay: [ + { + 'title': 'Ganzkörper Workout', + 'time': '09:00', + 'duration': '45 Minuten', + }, + {'title': 'Yoga Session', 'time': '17:30', 'duration': '30 Minuten'}, + ], + _focusedDay.add(const Duration(days: 1)): [ + {'title': 'HIIT Training', 'time': '08:00', 'duration': '30 Minuten'}, + ], + }; + } + + List> _getEventsForDay(DateTime day) { + return _events[DateTime(day.year, day.month, day.day)] ?? []; + } + @override Widget build(BuildContext context) { - return SafeArea( - child: Center( - child: Text( - 'Kalender', - style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), - ), + final firstDay = DateTime.utc(2024, 1, 1); + final lastDay = DateTime.utc(2024, 12, 31); + + if (_focusedDay.isBefore(firstDay)) { + _focusedDay = firstDay; + } else if (_focusedDay.isAfter(lastDay)) { + _focusedDay = lastDay; + } + + return Scaffold( + appBar: AppBar( + title: const Text('Trainingsplan'), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + // TODO: Implement add workout to calendar + }, + ), + ], + ), + body: Column( + children: [ + TableCalendar( + firstDay: firstDay, + lastDay: lastDay, + focusedDay: _focusedDay, + calendarFormat: _calendarFormat, + selectedDayPredicate: (day) { + return isSameDay(_selectedDay, day); + }, + onDaySelected: (selectedDay, focusedDay) { + setState(() { + _selectedDay = selectedDay; + _focusedDay = focusedDay; + }); + }, + onFormatChanged: (format) { + setState(() { + _calendarFormat = format; + }); + }, + onPageChanged: (focusedDay) { + setState(() { + _focusedDay = focusedDay; + }); + }, + eventLoader: _getEventsForDay, + calendarStyle: const CalendarStyle( + markersMaxCount: 1, + markerDecoration: BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + ), + ), + const Divider(), + Expanded( + child: + _getEventsForDay(_selectedDay!).isEmpty + ? const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.event_busy, size: 64, color: Colors.grey), + SizedBox(height: 16), + Text( + 'Keine Trainings geplant', + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + SizedBox(height: 8), + Text( + 'Füge Trainings zu deinem Kalender hinzu', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: _getEventsForDay(_selectedDay!).length, + itemBuilder: (context, index) { + final event = _getEventsForDay(_selectedDay!)[index]; + return Card( + margin: const EdgeInsets.only(bottom: 16), + child: ListTile( + leading: Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: Colors.blue[100], + borderRadius: BorderRadius.circular(8), + ), + 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), ), ); } -} \ No newline at end of file +} diff --git a/trainerbox/lib/screens/favorites_tab.dart b/trainerbox/lib/screens/favorites_tab.dart index 6a64259..5e98f1a 100644 --- a/trainerbox/lib/screens/favorites_tab.dart +++ b/trainerbox/lib/screens/favorites_tab.dart @@ -5,13 +5,176 @@ class FavoritesTab extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Center( - child: Text( - 'Favoriten', - style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), - ), + // Beispiel-Daten für favorisierte Trainings + final List> favoriteWorkouts = [ + { + 'title': 'Ganzkörper Workout', + 'duration': '45 Minuten', + 'level': 'Fortgeschritten', + 'category': 'Krafttraining', + 'isFavorite': true, + }, + { + 'title': 'Yoga Flow', + 'duration': '30 Minuten', + 'level': 'Anfänger', + 'category': 'Yoga', + 'isFavorite': true, + }, + { + 'title': 'HIIT Session', + 'duration': '20 Minuten', + 'level': 'Mittel', + 'category': 'HIIT', + 'isFavorite': true, + }, + ]; + + return Scaffold( + appBar: AppBar( + title: const Text('Favoriten'), + actions: [ + IconButton( + icon: const Icon(Icons.sort), + onPressed: () { + // TODO: Implement sorting functionality + }, + ), + ], ), + body: + favoriteWorkouts.isEmpty + ? const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.favorite_border, size: 64, color: Colors.grey), + SizedBox(height: 16), + Text( + 'Noch keine Favoriten', + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + SizedBox(height: 8), + Text( + 'Füge Trainings zu deinen Favoriten hinzu', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: favoriteWorkouts.length, + itemBuilder: (context, index) { + final workout = favoriteWorkouts[index]; + return Card( + margin: const EdgeInsets.only(bottom: 16), + child: InkWell( + onTap: () { + // TODO: Navigate to workout details + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.fitness_center, + size: 32, + color: Colors.grey, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + workout['title'], + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + workout['duration'], + style: TextStyle(color: Colors.grey[600]), + ), + const SizedBox(height: 4), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.blue[100], + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Text( + workout['level'], + style: TextStyle( + color: Colors.blue[700], + fontSize: 12, + ), + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.green[100], + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Text( + workout['category'], + style: TextStyle( + color: Colors.green[700], + fontSize: 12, + ), + ), + ), + ], + ), + ], + ), + ), + IconButton( + icon: Icon( + workout['isFavorite'] + ? Icons.favorite + : Icons.favorite_border, + color: + workout['isFavorite'] + ? Colors.red + : Colors.grey, + ), + onPressed: () { + // TODO: Implement favorite toggle + }, + ), + ], + ), + ), + ), + ); + }, + ), ); } -} \ No newline at end of file +} diff --git a/trainerbox/lib/screens/home_tab.dart b/trainerbox/lib/screens/home_tab.dart index 6a9b7f9..098700a 100644 --- a/trainerbox/lib/screens/home_tab.dart +++ b/trainerbox/lib/screens/home_tab.dart @@ -72,10 +72,7 @@ class HomeTab extends StatelessWidget { title: 'Passen', icon: Icons.sports_volleyball, ), - CategoryCircle( - title: 'Torhüter', - icon: Icons.sports_soccer, - ), + CategoryCircle(title: 'Torhüter', icon: Icons.sports_soccer), ], ), ), @@ -118,4 +115,4 @@ class HomeTab extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/trainerbox/lib/screens/profile_tab.dart b/trainerbox/lib/screens/profile_tab.dart index 6b71be2..6bd9bd0 100644 --- a/trainerbox/lib/screens/profile_tab.dart +++ b/trainerbox/lib/screens/profile_tab.dart @@ -5,13 +5,180 @@ class ProfileTab extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: Center( - child: Text( - 'Profil', - style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), - ), + // Beispiel-Benutzerdaten + final Map userData = { + 'name': 'Max Mustermann', + 'email': 'max.mustermann@example.com', + 'level': 'Fortgeschritten', + 'joinedDate': '01.01.2024', + 'workoutsCompleted': 42, + 'totalMinutes': 1260, + }; + + return Scaffold( + body: 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), + ), + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoCard( + title: 'Statistiken', + child: Column( + children: [ + _buildStatisticRow( + 'Trainings absolviert', + userData['workoutsCompleted'].toString(), + Icons.fitness_center, + ), + const Divider(), + _buildStatisticRow( + 'Gesamtzeit', + '${userData['totalMinutes']} Minuten', + Icons.timer, + ), + ], + ), + ), + const SizedBox(height: 16), + _buildInfoCard( + title: 'Persönliche Informationen', + child: Column( + children: [ + _buildInfoRow('E-Mail', userData['email'], Icons.email), + const Divider(), + _buildInfoRow('Level', userData['level'], Icons.star), + const Divider(), + _buildInfoRow( + 'Mitglied seit', + userData['joinedDate'], + Icons.calendar_today, + ), + ], + ), + ), + 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) { + // TODO: Implement notification settings + }, + ), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.dark_mode), + title: const Text('Dark Mode'), + trailing: Switch( + value: false, + onChanged: (value) { + // TODO: Implement dark mode + }, + ), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.language), + title: const Text('Sprache'), + trailing: const Text('Deutsch'), + onTap: () { + // TODO: Implement language selection + }, + ), + ], + ), + ), + const SizedBox(height: 16), + Center( + child: TextButton.icon( + onPressed: () { + // TODO: Implement logout + }, + icon: const Icon(Icons.logout), + label: const Text('Abmelden'), + style: TextButton.styleFrom(foregroundColor: Colors.red), + ), + ), + ], + ), + ), + ), + ], ), ); } -} \ No newline at end of file + + 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 _buildStatisticRow(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: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ); + } + + 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])), + ], + ), + ); + } +} diff --git a/trainerbox/lib/screens/search_tab.dart b/trainerbox/lib/screens/search_tab.dart index 17d3f5b..14dd3cd 100644 --- a/trainerbox/lib/screens/search_tab.dart +++ b/trainerbox/lib/screens/search_tab.dart @@ -1,17 +1,131 @@ import 'package:flutter/material.dart'; -class SearchTab extends StatelessWidget { +class SearchTab extends StatefulWidget { const SearchTab({super.key}); + @override + State createState() => _SearchTabState(); +} + +class _SearchTabState extends State { + final TextEditingController _searchController = TextEditingController(); + final List _categories = [ + 'Krafttraining', + 'Ausdauer', + 'Yoga', + 'HIIT', + 'Mobility', + 'Rehabilitation', + ]; + @override Widget build(BuildContext context) { - return SafeArea( - child: Center( - child: Text( - 'Suche', - style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), - ), + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + floating: true, + title: TextField( + controller: _searchController, + decoration: InputDecoration( + hintText: 'Suche nach Training...', + border: InputBorder.none, + prefixIcon: const Icon(Icons.search), + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () => _searchController.clear(), + ), + ), + ), + ), + SliverPadding( + 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), + onSelected: (bool selected) { + // TODO: Implement category filtering + }, + ); + }).toList(), + ), + ], + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(16.0), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 0.75, + ), + delegate: SliverChildBuilderDelegate((context, index) { + return Card( + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: 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( + 'Training ${index + 1}', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + '${30 + index * 5} Minuten', + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + ); + }, childCount: 6), + ), + ), + ], ), ); } -} \ No newline at end of file + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } +} diff --git a/trainerbox/pubspec.lock b/trainerbox/pubspec.lock index 4016704..8a9c140 100644 --- a/trainerbox/pubspec.lock +++ b/trainerbox/pubspec.lock @@ -104,6 +104,14 @@ packages: description: flutter source: sdk version: "0.0.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" leak_tracker: dependency: transitive description: @@ -176,6 +184,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + simple_gesture_detector: + dependency: transitive + description: + name: simple_gesture_detector + sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 + url: "https://pub.dev" + source: hosted + version: "0.2.1" sky_engine: dependency: transitive description: flutter @@ -213,6 +229,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + table_calendar: + dependency: "direct main" + description: + name: table_calendar + sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" + url: "https://pub.dev" + source: hosted + version: "3.2.0" term_glyph: dependency: transitive description: diff --git a/trainerbox/pubspec.yaml b/trainerbox/pubspec.yaml index 02d9935..7cd8d89 100644 --- a/trainerbox/pubspec.yaml +++ b/trainerbox/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 firebase_core: ^3.13.0 + table_calendar: ^3.0.9 dev_dependencies: flutter_test: @@ -59,9 +60,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - images/ + - images/prototype/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images