Compare commits

...

2 Commits

8 changed files with 665 additions and 38 deletions

View File

@ -0,0 +1,4 @@
@echo off
git add .
git commit -m "Implementiere alle Tabs der App: Search, Favorites, Calendar und Profile"
git push

View File

@ -1,16 +1,173 @@
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<CalendarTab> createState() => _CalendarTabState();
}
class _CalendarTabState extends State<CalendarTab> {
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
Map<DateTime, List<Map<String, dynamic>>> _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<Map<String, dynamic>> _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),
),
);
}

View File

@ -5,12 +5,175 @@ 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<Map<String, dynamic>> 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
},
),
],
),
),
),
);
},
),
);
}

View File

@ -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),
],
),
),

View File

@ -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<String, dynamic> 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),
),
),
],
),
),
),
],
),
);
}
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])),
],
),
);
}
}

View File

@ -1,17 +1,131 @@
import 'package:flutter/material.dart';
class SearchTab extends StatelessWidget {
class SearchTab extends StatefulWidget {
const SearchTab({super.key});
@override
State<SearchTab> createState() => _SearchTabState();
}
class _SearchTabState extends State<SearchTab> {
final TextEditingController _searchController = TextEditingController();
final List<String> _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),
),
),
],
),
);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
}

View File

@ -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:

View File

@ -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