feat: implement autoregulation for kettlebell training

This commit is contained in:
Patryk Hegenberg 2025-06-22 17:07:56 +02:00
parent a3e36b1975
commit 4fac48e81e
9 changed files with 417 additions and 117 deletions

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/providers/settings_provider.dart';
import 'package:kettlebell_tracker/screens/home_screen.dart';
import 'package:kettlebell_tracker/screens/settings_screen.dart';
import 'package:kettlebell_tracker/screens/training_screen.dart';
@ -58,6 +59,14 @@ class _MainScreenState extends ConsumerState<MainScreen> {
});
}
@override
void initState() {
super.initState();
Future.microtask(() {
ref.read(settingsProvider.notifier).loadSettings();
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

View file

@ -6,7 +6,8 @@ class TrainingSession {
final double weightRight;
final int repsPerSet;
final int duration; // in seconds
final String notes;
final String program;
final int blockDay;
TrainingSession({
this.id,
@ -16,7 +17,8 @@ class TrainingSession {
required this.weightRight,
required this.repsPerSet,
required this.duration,
required this.notes,
required this.program,
required this.blockDay,
});
// Convert a TrainingSession into a Map. The keys must correspond to the names of the
@ -30,7 +32,8 @@ class TrainingSession {
'weightRight': weightRight,
'repsPerSet': repsPerSet,
'duration': duration,
'notes': notes,
'program': program,
'blockDay': blockDay,
};
}
@ -44,12 +47,37 @@ class TrainingSession {
weightRight: map['weightRight'],
repsPerSet: map['repsPerSet'],
duration: map['duration'],
notes: map['notes'],
program: map['program'] ?? 'giant_1.0',
blockDay: map['blockDay'] ?? 1,
);
}
TrainingSession copyWith({
int? id,
DateTime? date,
int? sets,
double? weightLeft,
double? weightRight,
int? repsPerSet,
int? duration,
String? program,
int? blockDay,
}) {
return TrainingSession(
id: id ?? this.id,
date: date ?? this.date,
sets: sets ?? this.sets,
weightLeft: weightLeft ?? this.weightLeft,
weightRight: weightRight ?? this.weightRight,
repsPerSet: repsPerSet ?? this.repsPerSet,
duration: duration ?? this.duration,
program: program ?? this.program,
blockDay: blockDay ?? this.blockDay,
);
}
@override
String toString() {
return 'TrainingSession{id: $id, date: $date, sets: $sets, notes: $notes}';
return 'TrainingSession{id: $id, date: $date, sets: $sets}';
}
}

View file

@ -1,37 +1,34 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsState {
final int trainingTimeMinutes;
final double weightLeft;
final double weightRight;
final int repsPerSet;
final int goalSets;
final String notes;
final String initialProgram;
SettingsState({
this.trainingTimeMinutes = 20,
this.weightLeft = 16.0,
this.weightRight = 16.0,
this.repsPerSet = 5,
this.goalSets = 5,
this.notes = "",
this.initialProgram = 'giant_1.0',
});
SettingsState copyWith({
int? trainingTimeMinutes,
double? weightLeft,
double? weightRight,
int? repsPerSet,
int? goalSets,
String? notes,
String? initialProgram,
}) {
return SettingsState(
trainingTimeMinutes: trainingTimeMinutes ?? this.trainingTimeMinutes,
weightLeft: weightLeft ?? this.weightLeft,
weightRight: weightRight ?? this.weightRight,
repsPerSet: repsPerSet ?? this.repsPerSet,
goalSets: goalSets ?? this.goalSets,
notes: notes ?? this.notes,
initialProgram: initialProgram ?? this.initialProgram,
);
}
}
@ -39,9 +36,30 @@ class SettingsState {
class SettingsNotifier extends StateNotifier<SettingsState> {
SettingsNotifier() : super(SettingsState());
void updateSettings(SettingsState newSettings) {
Future<void> loadSettings() async {
final prefs = await SharedPreferences.getInstance();
state = SettingsState(
trainingTimeMinutes: prefs.getInt('trainingTimeMinutes') ?? 20,
weightLeft: prefs.getDouble('weightLeft') ?? 16.0,
weightRight: prefs.getDouble('weightRight') ?? 16.0,
goalSets: prefs.getInt('goalSets') ?? 5,
initialProgram: prefs.getString('initialProgram') ?? 'giant_1.0',
);
}
Future<void> saveSettings(SettingsState newSettings) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('trainingTimeMinutes', newSettings.trainingTimeMinutes);
await prefs.setDouble('weightLeft', newSettings.weightLeft);
await prefs.setDouble('weightRight', newSettings.weightRight);
await prefs.setInt('goalSets', newSettings.goalSets);
await prefs.setString('initialProgram', newSettings.initialProgram);
state = newSettings;
}
Future<void> updateSettings(SettingsState newSettings) async {
await saveSettings(newSettings);
}
}
final settingsProvider = StateNotifierProvider<SettingsNotifier, SettingsState>(

View file

@ -16,6 +16,10 @@ class TrainingState {
final double progress;
final int secondsSinceLastSet;
final DateTime? lastSetTimestamp;
final String currentProgram;
final int currentBlockDay;
final int currentReps;
final int totalTrainingDays;
TrainingState({
this.isTrainingRunning = false,
@ -28,6 +32,10 @@ class TrainingState {
this.progress = 0.0,
this.secondsSinceLastSet = 0,
this.lastSetTimestamp,
this.currentProgram = 'giant_1.0',
this.currentBlockDay = 1,
this.currentReps = 5,
this.totalTrainingDays = 0,
});
TrainingState copyWith({
@ -42,6 +50,10 @@ class TrainingState {
int? secondsSinceLastSet,
DateTime? lastSetTimestamp,
bool clearLastSetTimestamp = false,
String? currentProgram,
int? currentBlockDay,
int? currentReps,
int? totalTrainingDays,
}) {
return TrainingState(
isTrainingRunning: isTrainingRunning ?? this.isTrainingRunning,
@ -57,6 +69,10 @@ class TrainingState {
lastSetTimestamp: clearLastSetTimestamp
? null
: lastSetTimestamp ?? this.lastSetTimestamp,
currentProgram: currentProgram ?? this.currentProgram,
currentBlockDay: currentBlockDay ?? this.currentBlockDay,
currentReps: currentReps ?? this.currentReps,
totalTrainingDays: totalTrainingDays ?? this.totalTrainingDays,
);
}
}
@ -65,14 +81,71 @@ class TrainingNotifier extends StateNotifier<TrainingState> {
final Ref ref;
TrainingNotifier(this.ref) : super(TrainingState());
void startTraining(int minutes, int reps, int goal) {
void _updateProgram() {
int newTotalDays = state.totalTrainingDays + 1;
String newProgram = state.currentProgram;
int newDay = (state.currentBlockDay % 3) + 1;
int newReps = state.currentReps;
if (newTotalDays > 0 && newTotalDays % 12 == 0) {
switch (state.currentProgram) {
case 'giant_1.0':
newProgram = 'giant_1.1';
break;
case 'giant_1.1':
newProgram = 'giant_1.2';
break;
case 'giant_1.2':
newProgram = 'ksk_1.0';
break;
case 'ksk_1.0':
newProgram = 'ksk_1.1';
break;
case 'ksk_1.1':
newProgram = 'ksk_1.2';
break;
case 'ksk_1.2':
newProgram = 'giant_1.0';
break;
default:
newProgram = 'giant_1.0';
}
newDay = 1;
}
if (newProgram == 'giant_1.0') {
newReps = [5, 6, 4][newDay - 1];
} else if (newProgram == 'giant_1.1') {
newReps = [6, 8, 7][newDay - 1];
} else if (newProgram == 'giant_1.2') {
newReps = [7, 9, 8][newDay - 1];
} else if (newProgram == 'ksk_1.0') {
newReps = [5, 6, 4][newDay - 1];
} else if (newProgram == 'ksk_1.1') {
newReps = [6, 8, 7][newDay - 1];
} else if (newProgram == 'ksk_1.2') {
newReps = [7, 9, 8][newDay - 1];
} else {
newReps = 5;
}
state = state.copyWith(
currentProgram: newProgram,
currentBlockDay: newDay,
currentReps: newReps,
totalTrainingDays: newTotalDays,
);
}
void startTraining(int minutes, int goal) {
_updateProgram();
final duration = minutes * 60;
state = TrainingState(
state = state.copyWith(
isTrainingRunning: true,
initialDurationSeconds: duration,
remainingSeconds: duration,
repsPerSet: reps,
goalSets: goal,
repsPerSet: state.currentReps,
);
}
@ -107,11 +180,19 @@ class TrainingNotifier extends StateNotifier<TrainingState> {
}
Future<void> finishTraining(TrainingSession session) async {
await DatabaseHelper().saveTraining(session);
await sendTrainingToBackend(
reps: session.repsPerSet,
rest: session.duration / session.sets,
sets: session.sets);
final updatedSession = session.copyWith(
program: state.currentProgram,
blockDay: state.currentBlockDay,
);
await DatabaseHelper().saveTraining(updatedSession);
try {
await sendTrainingToBackend(
reps: session.repsPerSet,
rest: session.duration / session.sets,
sets: session.sets);
} catch (e) {
print("Error sending information to backend");
}
ref.refresh(historyProvider);
resetTraining();
}

View file

@ -86,11 +86,6 @@ class HistoryItemCard extends StatelessWidget {
'Reps pro Satz: ${session.repsPerSet}'),
_buildInfoRow(
Icons.timer, 'Dauer: ${formatDuration(session.duration)}'),
if (session.notes.isNotEmpty) ...[
const SizedBox(height: 8),
_buildInfoRow(Icons.note, 'Notizen: ${session.notes}',
isNote: true),
]
],
),
);

View file

@ -18,9 +18,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
late TextEditingController _trainingTimeController;
late TextEditingController _weightLeftController;
late TextEditingController _weightRightController;
late TextEditingController _repsController;
late TextEditingController _goalSetsController;
late TextEditingController _notesController;
@override
void initState() {
@ -35,13 +33,9 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
_weightRightController = TextEditingController(
text: settings.weightRight.toString(),
);
_repsController = TextEditingController(
text: settings.repsPerSet.toString(),
);
_goalSetsController = TextEditingController(
text: settings.goalSets.toString(),
);
_notesController = TextEditingController(text: settings.notes);
}
@override
@ -49,9 +43,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
_trainingTimeController.dispose();
_weightLeftController.dispose();
_weightRightController.dispose();
_repsController.dispose();
_goalSetsController.dispose();
_notesController.dispose();
super.dispose();
}
@ -61,9 +53,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
trainingTimeMinutes: int.parse(_trainingTimeController.text),
weightLeft: double.parse(_weightLeftController.text),
weightRight: double.parse(_weightRightController.text),
repsPerSet: int.parse(_repsController.text),
goalSets: int.parse(_goalSetsController.text),
notes: _notesController.text,
);
ref.read(settingsProvider.notifier).updateSettings(newSettings);
ScaffoldMessenger.of(context).showSnackBar(
@ -123,12 +113,6 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
CustomTextField(
controller: _repsController,
labelText: "Reps pro Satz",
icon: Icons.repeat,
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
CustomTextField(
controller: _goalSetsController,
@ -139,25 +123,6 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
],
),
),
const SizedBox(height: 20),
CustomCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Zusätzliche Notizen",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
CustomTextField(
controller: _notesController,
labelText: "Notizen für nächstes Training",
icon: Icons.note,
maxLines: 4,
),
],
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _saveSettings,

View file

@ -34,7 +34,6 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
final settings = ref.read(settingsProvider);
ref.read(trainingProvider.notifier).startTraining(
settings.trainingTimeMinutes,
settings.repsPerSet,
settings.goalSets,
);
@ -82,9 +81,10 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
sets: state.setsDone,
weightLeft: settings.weightLeft,
weightRight: settings.weightRight,
repsPerSet: settings.repsPerSet,
repsPerSet: state.currentReps,
duration: state.initialDurationSeconds - state.remainingSeconds,
notes: settings.notes,
program: state.currentProgram,
blockDay: state.currentBlockDay,
);
ref.read(trainingProvider.notifier).finishTraining(session);
@ -121,7 +121,6 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
@override
Widget build(BuildContext context) {
final trainingState = ref.watch(trainingProvider);
final settings = ref.watch(settingsProvider);
final bool isTrainingRunning = trainingState.isTrainingRunning;
return Scaffold(
@ -129,47 +128,66 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header-Bereich mit Programm-Infos
CustomCard(
child: Column(
children: [
Text(
'Verbleibende Zeit: ${_formatDuration(trainingState.remainingSeconds)}',
'${trainingState.currentProgram.toUpperCase()}',
style: const TextStyle(
fontSize: 26,
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
const SizedBox(height: 12),
Text(
'Sätze: ${trainingState.setsDone}',
style: const TextStyle(
fontSize: 24,
color: AppTheme.oneDarkYellow,
),
),
const SizedBox(height: 8),
Text(
'Reps pro Satz: ${settings.repsPerSet}',
'Block Tag: ${trainingState.currentBlockDay}',
style: const TextStyle(
fontSize: 20,
color: AppTheme.oneDarkAccent,
),
),
const SizedBox(height: 12),
Text(
'Zeit seit letztem Satz: ${_formatDuration(trainingState.secondsSinceLastSet)}',
'Reps pro Satz: ${trainingState.currentReps}',
style: const TextStyle(
fontSize: 18,
fontSize: 22,
fontWeight: FontWeight.w500,
color: AppTheme.oneDarkYellow,
),
),
],
),
),
const SizedBox(height: 18),
// Timer und Fortschritt
CustomCard(
child: Column(
children: [
const Text(
'Verbleibende Zeit',
style: TextStyle(
fontSize: 16,
color: AppTheme.oneDarkTextWeak,
),
),
const SizedBox(height: 20),
Text(
_formatDuration(trainingState.remainingSeconds),
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
const SizedBox(height: 10),
LinearProgressIndicator(
value: trainingState.progress,
minHeight: 10,
borderRadius: BorderRadius.circular(5),
minHeight: 12,
borderRadius: BorderRadius.circular(6),
color: AppTheme.oneDarkGreen,
backgroundColor: AppTheme.oneDarkTextWeak,
),
const SizedBox(height: 8),
Text(
@ -182,51 +200,58 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
],
),
),
const SizedBox(height: 20),
const SizedBox(height: 18),
// Aktions-Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: isTrainingRunning ? null : _startTraining,
icon: const Icon(Icons.play_arrow),
label: const Text('Start'),
icon: const Icon(Icons.play_arrow, size: 28),
label: const Text('Start', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkGreen,
foregroundColor: AppTheme.oneDarkTextWeak,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
ElevatedButton.icon(
onPressed: !isTrainingRunning ? null : _completeSet,
icon: const Icon(Icons.check_circle),
label: const Text('Satz'),
icon: const Icon(Icons.check_circle, size: 28),
label: const Text('Satz', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkPrimary,
foregroundColor: AppTheme.oneDarkTextWeak,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
ElevatedButton.icon(
onPressed: !isTrainingRunning ? null : _finishTraining,
icon: const Icon(Icons.stop),
label: const Text('Beenden'),
icon: const Icon(Icons.stop, size: 28),
label: const Text('Beenden', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkRed,
foregroundColor: AppTheme.oneDarkTextWeak,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
],
),
const SizedBox(height: 20),
const Divider(),
const SizedBox(height: 22),
// Satz-Historie
Text(
"Satz-Historie",
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(color: AppTheme.oneDarkAccent),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: AppTheme.oneDarkAccent,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
CustomCard(
child: SizedBox(
height: 150,
height: 180,
child: trainingState.setTimes.isEmpty
? const Center(
child: Text(
@ -234,13 +259,25 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
style: TextStyle(color: AppTheme.oneDarkTextWeak),
),
)
: ListView.builder(
: ListView.separated(
itemCount: trainingState.setTimes.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final setTime = trainingState.setTimes[index];
return Center(
child: Text(
'#${index + 1} um ${DateFormat.Hms().format(setTime)} (${settings.repsPerSet} Reps)',
return ListTile(
leading: CircleAvatar(
backgroundColor: AppTheme.oneDarkGreen,
child: Text(
'${index + 1}',
style: const TextStyle(color: Colors.white),
),
),
title: Text(
'um ${DateFormat.Hms().format(setTime)}',
style: const TextStyle(fontSize: 16),
),
trailing: Text(
'${trainingState.currentReps} Reps',
style: const TextStyle(
fontSize: 16,
color: AppTheme.oneDarkGreen,
@ -256,4 +293,156 @@ class _TrainingScreenState extends ConsumerState<TrainingScreen> {
),
);
}
// @override
// Widget build(BuildContext context) {
// final trainingState = ref.watch(trainingProvider);
// final settings = ref.watch(settingsProvider);
// final bool isTrainingRunning = trainingState.isTrainingRunning;
//
// return Scaffold(
// appBar: AppBar(title: const Text('Aktuelles Training')),
// body: SingleChildScrollView(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// children: [
// CustomCard(
// child: Column(
// children: [
// Text(
// 'Programm: ${trainingState.currentProgram.toUpperCase()}',
// style: const TextStyle(
// fontSize: 20,
// color: AppTheme.oneDarkYellow,
// ),
// ),
// Text(
// 'Block Tag: ${trainingState.currentBlockDay}',
// style: const TextStyle(
// fontSize: 18,
// color: AppTheme.oneDarkAccent,
// ),
// ),
// Text(
// 'Verbleibende Zeit: ${_formatDuration(trainingState.remainingSeconds)}',
// style: const TextStyle(
// fontSize: 26,
// fontWeight: FontWeight.bold,
// color: AppTheme.oneDarkPrimary,
// ),
// ),
// const SizedBox(height: 12),
// Text(
// 'Sätze: ${trainingState.setsDone}',
// style: const TextStyle(
// fontSize: 24,
// color: AppTheme.oneDarkYellow,
// ),
// ),
// const SizedBox(height: 8),
// Text(
// 'Reps pro Satz: ${trainingState.currentReps}',
// style: const TextStyle(
// fontSize: 20,
// color: AppTheme.oneDarkAccent,
// ),
// ),
// const SizedBox(height: 12),
// Text(
// 'Zeit seit letztem Satz: ${_formatDuration(trainingState.secondsSinceLastSet)}',
// style: const TextStyle(
// fontSize: 18,
// color: AppTheme.oneDarkTextWeak,
// ),
// ),
// const SizedBox(height: 20),
// LinearProgressIndicator(
// value: trainingState.progress,
// minHeight: 10,
// borderRadius: BorderRadius.circular(5),
// ),
// const SizedBox(height: 8),
// Text(
// 'Fortschritt: ${(trainingState.progress * 100).toStringAsFixed(0)}%',
// style: const TextStyle(
// fontSize: 16,
// color: AppTheme.oneDarkText,
// ),
// ),
// ],
// ),
// ),
// const SizedBox(height: 20),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// ElevatedButton.icon(
// onPressed: isTrainingRunning ? null : _startTraining,
// icon: const Icon(Icons.play_arrow),
// label: const Text('Start'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkGreen,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ElevatedButton.icon(
// onPressed: !isTrainingRunning ? null : _completeSet,
// icon: const Icon(Icons.check_circle),
// label: const Text('Satz'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkPrimary,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ElevatedButton.icon(
// onPressed: !isTrainingRunning ? null : _finishTraining,
// icon: const Icon(Icons.stop),
// label: const Text('Beenden'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkRed,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ],
// ),
// const SizedBox(height: 20),
// const Divider(),
// Text(
// "Satz-Historie",
// style: Theme.of(
// context,
// ).textTheme.titleLarge?.copyWith(color: AppTheme.oneDarkAccent),
// ),
// const SizedBox(height: 10),
// CustomCard(
// child: SizedBox(
// height: 150,
// child: trainingState.setTimes.isEmpty
// ? const Center(
// child: Text(
// "Noch keine Sätze in dieser Einheit.",
// style: TextStyle(color: AppTheme.oneDarkTextWeak),
// ),
// )
// : ListView.builder(
// itemCount: trainingState.setTimes.length,
// itemBuilder: (context, index) {
// final setTime = trainingState.setTimes[index];
// return Center(
// child: Text(
// '#${index + 1} um ${DateFormat.Hms().format(setTime)} (${trainingState.currentReps} Reps)',
// style: const TextStyle(
// fontSize: 16,
// color: AppTheme.oneDarkGreen,
// ),
// ),
// );
// },
// ),
// ),
// ),
// ],
// ),
// ),
// );
}

View file

@ -9,17 +9,19 @@ Future<void> sendTrainingToBackend({
required int sets,
}) async {
final uuid = await DeviceIdService.getOrCreateUUID();
final url = Uri.parse('http://192.169.178.43:8000/trainings/');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'reps': reps,
'rest': rest,
'sets': sets,
'uuid': uuid,
}),
);
final url = Uri.parse('http://192.169.178.43:8080/trainings/');
final response = await http
.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'reps': reps,
'rest': rest,
'sets': sets,
'uuid': uuid,
}),
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
print('Training erfolgreich an Backend gesendet.');
} else {

View file

@ -20,7 +20,19 @@ class DatabaseHelper {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'giant_training.db');
return await openDatabase(path, version: 1, onCreate: _onCreate);
return await openDatabase(
path,
version: 2, // Version erhöhen!
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
void _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE training ADD COLUMN program TEXT');
await db.execute('ALTER TABLE training ADD COLUMN blockDay INTEGER');
}
}
// Create the database table
@ -34,7 +46,8 @@ class DatabaseHelper {
weightRight REAL,
repsPerSet INTEGER,
duration INTEGER,
notes TEXT
program TEXT,
blockDay INTEGER
)
''');
}