kettlebell-tracker/lib/providers/training_provider.dart

213 lines
6.1 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/models/training_session.dart';
import 'package:kettlebell_tracker/services/api_service.dart';
import 'package:kettlebell_tracker/services/database_helper.dart';
import 'dart:async';
// Training State
class TrainingState {
final bool isTrainingRunning;
final int remainingSeconds;
final int initialDurationSeconds;
final int setsDone;
final int goalSets;
final int repsPerSet;
final List<DateTime> setTimes;
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,
this.remainingSeconds = 0,
this.initialDurationSeconds = 0,
this.setsDone = 0,
this.goalSets = 5,
this.repsPerSet = 5,
this.setTimes = const [],
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({
bool? isTrainingRunning,
int? remainingSeconds,
int? initialDurationSeconds,
int? setsDone,
int? goalSets,
int? repsPerSet,
List<DateTime>? setTimes,
double? progress,
int? secondsSinceLastSet,
DateTime? lastSetTimestamp,
bool clearLastSetTimestamp = false,
String? currentProgram,
int? currentBlockDay,
int? currentReps,
int? totalTrainingDays,
}) {
return TrainingState(
isTrainingRunning: isTrainingRunning ?? this.isTrainingRunning,
remainingSeconds: remainingSeconds ?? this.remainingSeconds,
initialDurationSeconds:
initialDurationSeconds ?? this.initialDurationSeconds,
setsDone: setsDone ?? this.setsDone,
goalSets: goalSets ?? this.goalSets,
repsPerSet: repsPerSet ?? this.repsPerSet,
setTimes: setTimes ?? this.setTimes,
progress: progress ?? this.progress,
secondsSinceLastSet: secondsSinceLastSet ?? this.secondsSinceLastSet,
lastSetTimestamp: clearLastSetTimestamp
? null
: lastSetTimestamp ?? this.lastSetTimestamp,
currentProgram: currentProgram ?? this.currentProgram,
currentBlockDay: currentBlockDay ?? this.currentBlockDay,
currentReps: currentReps ?? this.currentReps,
totalTrainingDays: totalTrainingDays ?? this.totalTrainingDays,
);
}
}
class TrainingNotifier extends StateNotifier<TrainingState> {
final Ref ref;
TrainingNotifier(this.ref) : super(TrainingState());
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 = state.copyWith(
isTrainingRunning: true,
initialDurationSeconds: duration,
remainingSeconds: duration,
goalSets: goal,
repsPerSet: state.currentReps,
);
}
void tick() {
if (state.remainingSeconds > 0) {
state = state.copyWith(remainingSeconds: state.remainingSeconds - 1);
}
}
void tickLastSetTimer() {
if (state.isTrainingRunning && state.lastSetTimestamp != null) {
state = state.copyWith(
secondsSinceLastSet:
DateTime.now().difference(state.lastSetTimestamp!).inSeconds,
);
}
}
void completeSet() {
final newSetsDone = state.setsDone + 1;
final newSetTimes = List<DateTime>.from(state.setTimes)
..add(DateTime.now());
final newProgress = state.goalSets > 0 ? newSetsDone / state.goalSets : 0.0;
state = state.copyWith(
setsDone: newSetsDone,
setTimes: newSetTimes,
progress: newProgress > 1.0 ? 1.0 : newProgress,
lastSetTimestamp: DateTime.now(),
secondsSinceLastSet: 0,
);
}
Future<void> finishTraining(TrainingSession session) async {
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();
}
void resetTraining() {
state = TrainingState();
}
}
final historyProvider = FutureProvider<List<TrainingSession>>((ref) async {
return DatabaseHelper().getHistory();
});
final trainingProvider = StateNotifierProvider<TrainingNotifier, TrainingState>(
(ref) {
return TrainingNotifier(ref);
},
);