feat: implement autoregulation for kettlebell training
This commit is contained in:
parent
a3e36b1975
commit
4fac48e81e
9 changed files with 417 additions and 117 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue