577 lines
19 KiB
Dart
577 lines
19 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
|
import 'package:aitrainer_app/model/cache.dart';
|
|
import 'package:aitrainer_app/model/customer_training_plan.dart';
|
|
import 'package:aitrainer_app/model/customer_training_plan_details.dart';
|
|
import 'package:aitrainer_app/model/exercise.dart';
|
|
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
|
import 'package:aitrainer_app/model/exercise_type.dart';
|
|
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
|
import 'package:aitrainer_app/repository/training_plan_repository.dart';
|
|
import 'package:aitrainer_app/service/exercise_service.dart';
|
|
import 'package:aitrainer_app/util/app_language.dart';
|
|
import 'package:aitrainer_app/util/common.dart';
|
|
import 'package:aitrainer_app/util/enums.dart';
|
|
import 'package:aitrainer_app/util/track.dart';
|
|
import 'package:bloc/bloc.dart';
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
part 'training_plan_event.dart';
|
|
part 'training_plan_state.dart';
|
|
|
|
class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
|
final TrainingPlanRepository trainingPlanRepository;
|
|
final MenuBloc menuBloc;
|
|
TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial());
|
|
|
|
CustomerTrainingPlan? _myPlan;
|
|
CustomerTrainingPlanDetails? _myDetail;
|
|
|
|
bool started = false;
|
|
final List<String> dayNames = [];
|
|
bool restarting = false;
|
|
bool celebrating = false;
|
|
int activeDayIndex = 0;
|
|
|
|
CustomerTrainingPlan? getMyPlan() => this._myPlan;
|
|
setMyPlan(CustomerTrainingPlan? myPlan) => this._myPlan = myPlan;
|
|
|
|
CustomerTrainingPlanDetails? getMyDetail() => this._myDetail;
|
|
setMyDetail(CustomerTrainingPlanDetails? value) => this._myDetail = value;
|
|
|
|
@override
|
|
Stream<TrainingPlanState> mapEventToState(TrainingPlanEvent event) async* {
|
|
try {
|
|
if (event is TrainingPlanActivate) {
|
|
yield TrainingPlanLoading();
|
|
_myPlan = await trainingPlanRepository.activateTrainingPlan(event.trainingPlanId);
|
|
_myPlan!.type = CustomerTrainingPlanType.template;
|
|
|
|
menuBloc.menuTreeRepository.sortedTree.forEach((name, list) {
|
|
final List<WorkoutMenuTree> menuList = list as List<WorkoutMenuTree>;
|
|
menuList.forEach((element) {
|
|
element.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.none;
|
|
});
|
|
});
|
|
this.activateDays();
|
|
Cache().myTrainingPlan = _myPlan;
|
|
await Cache().saveMyTrainingPlan();
|
|
Track().track(TrackingEvent.training_plan_start, eventValue: event.trainingPlanId.toString());
|
|
yield TrainingPlanFinished();
|
|
} else if (event is TrainingPlanWeightChange) {
|
|
yield TrainingPlanExerciseLoading();
|
|
event.detail.weight = event.weight;
|
|
|
|
yield TrainingPlanExerciseReady();
|
|
yield TrainingPlanReady();
|
|
} else if (event is TrainingPlanWeightChangeRecalculate) {
|
|
yield TrainingPlanExerciseLoading();
|
|
|
|
event.detail.repeats =
|
|
Common.reCalculateRepeatsByChangedWeight(event.detail.weight!, event.detail.repeats!.toDouble(), event.weight);
|
|
event.detail.weight = event.weight;
|
|
print(" weight: ${event.weight} new repeats: ${event.detail.repeats}");
|
|
|
|
yield TrainingPlanReady();
|
|
} else if (event is TrainingPlanRepeatsChange) {
|
|
yield TrainingPlanExerciseLoading();
|
|
|
|
event.detail.repeats = event.repeats;
|
|
yield TrainingPlanExerciseReady();
|
|
yield TrainingPlanReady();
|
|
} else if (event is TrainingPlanSetChange) {
|
|
yield TrainingPlanLoading();
|
|
|
|
event.detail.set = event.set;
|
|
|
|
yield TrainingPlanReady();
|
|
} else if (event is TrainingPlanSaveExercise) {
|
|
yield TrainingPlanLoading();
|
|
if (event.detail.weight == -3) {
|
|
print("DropSet");
|
|
event.detail.state = ExercisePlanDetailState.finished;
|
|
yield TrainingPlanReady();
|
|
} else {
|
|
final Exercise exercise = Exercise();
|
|
exercise.customerId = Cache().userLoggedIn!.customerId!;
|
|
exercise.exerciseTypeId = event.detail.exerciseTypeId;
|
|
exercise.quantity = event.detail.repeats!.toDouble();
|
|
exercise.unit = event.detail.exerciseType!.unit;
|
|
exercise.unitQuantity = event.detail.weight;
|
|
exercise.dateAdd = DateTime.now();
|
|
event.detail.exercises.add(exercise);
|
|
if (event.detail.exercises.length >= event.detail.set!) {
|
|
event.detail.state = ExercisePlanDetailState.finished;
|
|
} else if (event.detail.exercises.length >= 0) {
|
|
event.detail.state = ExercisePlanDetailState.inProgress;
|
|
}
|
|
// recalculate the weight to the original planned repeats for the next details
|
|
|
|
if (exercise.unitQuantity != null && exercise.unitQuantity! > 0) {
|
|
for (var nextDetail in _myPlan!.details) {
|
|
double weightFromPlan = trainingPlanRepository.getOriginalWeight(this.getMyPlan()!.trainingPlanId!, nextDetail);
|
|
if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId &&
|
|
nextDetail.weight == -2 &&
|
|
nextDetail.customerTrainingPlanDetailsId != event.detail.customerTrainingPlanDetailsId) {
|
|
print("recalculating -2 ${event.detail.customerTrainingPlanDetailsId}");
|
|
trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail, nextDetail);
|
|
} else if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId && weightFromPlan == -1 && nextDetail.set! > 1) {
|
|
print("recalculating -1 ${event.detail.customerTrainingPlanDetailsId}");
|
|
nextDetail = trainingPlanRepository.recalculateDetailFixRepeats(_myPlan!.trainingPlanId!, nextDetail);
|
|
}
|
|
}
|
|
}
|
|
|
|
exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;
|
|
|
|
// save Exercise
|
|
Exercise savedExercise = await ExerciseApi().addExercise(exercise);
|
|
Cache().addExercise(savedExercise);
|
|
|
|
Cache().myTrainingPlan = _myPlan;
|
|
await Cache().saveMyTrainingPlan();
|
|
}
|
|
Track().track(TrackingEvent.training_plan_execute, eventValue: event.detail.exerciseType!.name);
|
|
|
|
if (isDayDone()) {
|
|
this.add(TrainingPlanFinishDay());
|
|
} else {
|
|
yield TrainingPlanReady();
|
|
}
|
|
} else if (event is TrainingPlanSkipExercise) {
|
|
yield TrainingPlanLoading();
|
|
event.detail.state = ExercisePlanDetailState.skipped;
|
|
Cache().myTrainingPlan = _myPlan;
|
|
await Cache().saveMyTrainingPlan();
|
|
if (isDayDone()) {
|
|
this.add(TrainingPlanFinishDay());
|
|
} else {
|
|
yield TrainingPlanReady();
|
|
}
|
|
} else if (event is TrainingPlanFinishDay) {
|
|
yield TrainingPlanLoading();
|
|
celebrating = true;
|
|
Track().track(TrackingEvent.training_plan_finished);
|
|
yield TrainingPlanDayFinished();
|
|
} else if (event is TrainingPlanGoToRestart) {
|
|
yield TrainingPlanLoading();
|
|
restarting = true;
|
|
yield TrainingPlanDayReadyToRestart();
|
|
} else if (event is TrainingPlanAddExerciseType) {
|
|
if (_myDetail == null) {
|
|
throw Exception("Create new Detail");
|
|
}
|
|
yield TrainingPlanLoading();
|
|
_myDetail!.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.added;
|
|
_myPlan!.details.add(this._myDetail!);
|
|
yield TrainingPlanReady();
|
|
} else if (event is TrainingPlanDeleteExerciseType) {
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
throw Exception("No MyPlan");
|
|
}
|
|
yield TrainingPlanLoading();
|
|
CustomerTrainingPlanDetails? remove;
|
|
for (var detail in _myPlan!.details) {
|
|
if (event.exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
|
remove = detail;
|
|
break;
|
|
}
|
|
}
|
|
if (remove != null) {
|
|
_myPlan!.details.remove(remove);
|
|
}
|
|
event.exerciseType.trainingPlanState = ExerciseTypeTrainingPlanState.none;
|
|
yield TrainingPlanReady();
|
|
} else if (event is TrainingPlanCustomAddLoad) {
|
|
yield TrainingPlanLoading();
|
|
addNewPlan();
|
|
|
|
_myDetail = CustomerTrainingPlanDetails();
|
|
_myDetail!.exerciseType = event.exerciseType;
|
|
_myDetail!.exerciseTypeId = event.exerciseType.exerciseTypeId;
|
|
_myDetail!.repeats = 12;
|
|
_myDetail!.weight = 30;
|
|
_myDetail!.set = 3;
|
|
_myDetail!.parallel = false;
|
|
_myDetail!.day = "";
|
|
_myDetail!.restingTime = 2;
|
|
_myDetail!.state = ExercisePlanDetailState.start;
|
|
|
|
if (_myDetail!.exerciseType!.unitQuantityUnit != null) {
|
|
_myDetail = trainingPlanRepository.getCalculatedWeightRepeats(event.exerciseType.exerciseTypeId, _myDetail!);
|
|
} else {
|
|
_myDetail!.weight = 0;
|
|
}
|
|
Track().track(TrackingEvent.training_plan_custom, eventValue: event.exerciseType.name);
|
|
yield TrainingPlanReady();
|
|
}
|
|
} on Exception catch (e) {
|
|
yield TrainingPlanError(message: e.toString());
|
|
}
|
|
}
|
|
|
|
void addNewPlan() {
|
|
if (Cache().userLoggedIn == null) {
|
|
throw Exception("Please log in");
|
|
}
|
|
if (_myPlan == null) {
|
|
_myPlan = CustomerTrainingPlan();
|
|
} else {
|
|
if (!_myPlan!.type.equalsTo(CustomerTrainingPlanType.custom)) {
|
|
_myPlan!.details.clear();
|
|
}
|
|
}
|
|
_myPlan!.trainingPlanId = 0;
|
|
_myPlan!.name = "Custom";
|
|
_myPlan!.dateAdd = DateTime.now();
|
|
_myPlan!.customerId = Cache().userLoggedIn == null ? Cache().userLoggedIn!.customerId : 0;
|
|
|
|
_myPlan!.type = CustomerTrainingPlanType.custom;
|
|
|
|
restart();
|
|
|
|
print("New custom plan: $_myPlan");
|
|
}
|
|
|
|
void restart() {
|
|
if (_myPlan == null) {
|
|
return;
|
|
}
|
|
for (var day in dayNames) {
|
|
_myPlan!.days[day]!.clear();
|
|
}
|
|
_myPlan!.details.forEach((element) {
|
|
element.state = ExercisePlanDetailState.start;
|
|
element.exercises.clear();
|
|
});
|
|
dayNames.clear();
|
|
Cache().myTrainingPlan = _myPlan;
|
|
Cache().saveMyTrainingPlan();
|
|
restarting = false;
|
|
print("Restarting finished");
|
|
}
|
|
|
|
void activateDays() {
|
|
if (_myPlan == null) {
|
|
return;
|
|
}
|
|
|
|
if (isDone100Percent()) {
|
|
this.add(TrainingPlanGoToRestart());
|
|
}
|
|
dayNames.clear();
|
|
_myPlan!.days.clear();
|
|
String dayName = ".";
|
|
String previousDay = ".";
|
|
_myPlan!.details.forEach((element) {
|
|
if (element.day != null && element.day != dayName) {
|
|
dayNames.add(element.day!);
|
|
dayName = element.day!;
|
|
if (previousDay != ".") {
|
|
this.addExtraExerciseType("Stretching", previousDay);
|
|
}
|
|
}
|
|
if (_myPlan!.days[dayName] == null) {
|
|
if (dayName == ".") {
|
|
dayName = "";
|
|
}
|
|
_myPlan!.days[dayName] = [];
|
|
|
|
this.addExtraExerciseType("Warming Up", dayName);
|
|
previousDay = dayName;
|
|
}
|
|
_myPlan!.days[dayName]!.add(element);
|
|
});
|
|
this.addExtraExerciseType("Stretching", previousDay);
|
|
|
|
if (dayNames.length == 0) {
|
|
dayName = "";
|
|
dayNames.add(dayName);
|
|
_myPlan!.days[dayName] = [];
|
|
_myPlan!.days[dayName]!.addAll(_myPlan!.details);
|
|
}
|
|
getActiveDayIndex();
|
|
}
|
|
|
|
void addExtraExerciseType(String name, String dayName) {
|
|
if (Cache().getExerciseTypes() == null) {
|
|
return;
|
|
}
|
|
for (var exerciseType in Cache().getExerciseTypes()!) {
|
|
if (exerciseType.name == name) {
|
|
CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails();
|
|
detail.customerTrainingPlanDetailsId = 0;
|
|
detail.trainingPlanDetailsId = 0;
|
|
detail.exerciseTypeId = exerciseType.exerciseTypeId;
|
|
detail.repeats = 1;
|
|
detail.set = 1;
|
|
detail.day = "";
|
|
detail.parallel = false;
|
|
detail.restingTime = 0;
|
|
detail.exerciseType = exerciseType;
|
|
detail.state = ExercisePlanDetailState.extra;
|
|
if (_myPlan!.days[dayName] == null) {
|
|
_myPlan!.days[dayName] = [];
|
|
}
|
|
_myPlan!.days[dayName]!.add(detail);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) {
|
|
CustomerTrainingPlanDetails? detail;
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
for (final det in this._myPlan!.details) {
|
|
if (det.customerTrainingPlanDetailsId == trainingPlanDetailsId) {
|
|
detail = det;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return detail;
|
|
}
|
|
|
|
int? getActualWorkoutTreeId(int exerciseTypeId) {
|
|
final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId);
|
|
if (workoutTree == null) {
|
|
return null;
|
|
}
|
|
return workoutTree.id;
|
|
}
|
|
|
|
String getActualImageName(int exerciseTypeId) {
|
|
if (exerciseTypeId <= 0) {
|
|
return "";
|
|
}
|
|
final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId);
|
|
if (workoutTree == null) {
|
|
return "";
|
|
}
|
|
|
|
return workoutTree.imageName;
|
|
}
|
|
|
|
CustomerTrainingPlanDetails? getNext() {
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
CustomerTrainingPlanDetails? next;
|
|
int minStep = 99;
|
|
for (final detail in this._myPlan!.details) {
|
|
if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) {
|
|
final day = dayNames[this.activeDayIndex];
|
|
if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) {
|
|
next = detail;
|
|
minStep = 1;
|
|
break;
|
|
} else {
|
|
final int step = detail.exercises.length;
|
|
if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) {
|
|
next = detail;
|
|
minStep = step;
|
|
if (detail.parallel != true) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//print("Next detail $next");
|
|
|
|
return next;
|
|
}
|
|
|
|
bool isStarted() {
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
if (_myPlan!.details[0].state == ExercisePlanDetailState.start) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool isDayDone() {
|
|
bool isDone = true;
|
|
final String day = dayNames[activeDayIndex];
|
|
for (var detail in _myPlan!.days[day]!) {
|
|
//print("daydone ${detail.state}");
|
|
if (!detail.state.equalsTo(ExercisePlanDetailState.extra) &&
|
|
!detail.state.equalsTo(ExercisePlanDetailState.finished) &&
|
|
!detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
|
isDone = false;
|
|
}
|
|
}
|
|
print("Is Day '$day' done: $isDone");
|
|
return isDone;
|
|
}
|
|
|
|
double getOffset() {
|
|
double offset = 5;
|
|
if (_myPlan == null) {
|
|
return offset;
|
|
}
|
|
int indexInProgress = 0;
|
|
int indexInStart = 0;
|
|
final String day = dayNames[this.activeDayIndex];
|
|
for (var detail in _myPlan!.days[day]!) {
|
|
if (detail.state == ExercisePlanDetailState.inProgress) {
|
|
break;
|
|
}
|
|
if (detail.state == ExercisePlanDetailState.start) {
|
|
break;
|
|
}
|
|
indexInStart++;
|
|
indexInProgress++;
|
|
}
|
|
int index = indexInStart > indexInProgress ? indexInStart : indexInProgress;
|
|
offset = index * 80;
|
|
print("Offset: $offset day: $day ($indexInStart, $indexInProgress)");
|
|
return offset;
|
|
}
|
|
|
|
bool isDone100Percent() {
|
|
bool done = true;
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return false;
|
|
}
|
|
_myPlan!.details.forEach((element) {
|
|
if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
|
done = false;
|
|
}
|
|
});
|
|
return done;
|
|
}
|
|
|
|
int getActiveDayIndex() {
|
|
activeDayIndex = 0;
|
|
if (restarting) {
|
|
return 0;
|
|
}
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return 0;
|
|
}
|
|
|
|
if (dayNames.isEmpty || dayNames.length == 1) {
|
|
activeDayIndex = 0;
|
|
return 0;
|
|
}
|
|
|
|
for (var day in dayNames) {
|
|
if (_myPlan!.days[day] == null) {
|
|
throw Exception("Wrong activated day: $day does not exist");
|
|
}
|
|
bool isDone = true;
|
|
_myPlan!.days[day]!.forEach((element) {
|
|
if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
|
isDone = false;
|
|
}
|
|
});
|
|
if (!isDone) {
|
|
break;
|
|
}
|
|
activeDayIndex++;
|
|
}
|
|
|
|
if (activeDayIndex >= dayNames.length) {
|
|
activeDayIndex = 0;
|
|
this.add(TrainingPlanGoToRestart());
|
|
}
|
|
print("ActiveDayIndex $activeDayIndex");
|
|
return activeDayIndex;
|
|
}
|
|
|
|
String getCustomAddSummary() {
|
|
String summary = "";
|
|
if (_myDetail == null || _myDetail!.set == null || _myDetail!.repeats == null) {
|
|
return summary;
|
|
}
|
|
summary = getMyDetail()!.set!.toStringAsFixed(0) + " x " + getMyDetail()!.repeats!.toStringAsFixed(0);
|
|
return summary;
|
|
}
|
|
|
|
String getExerciseName(Locale locale) {
|
|
String exerciseName = "";
|
|
if (_myDetail == null || _myDetail!.exerciseType == null) {
|
|
return exerciseName;
|
|
}
|
|
exerciseName =
|
|
AppLanguage().appLocal == Locale("en") ? getMyDetail()!.exerciseType!.name : getMyDetail()!.exerciseType!.nameTranslation;
|
|
return exerciseName;
|
|
}
|
|
|
|
String getWeightByExerciseType(ExerciseType exerciseType) {
|
|
double weight = 0;
|
|
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return weight.toStringAsFixed(0);
|
|
}
|
|
|
|
for (var detail in _myPlan!.details) {
|
|
if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
|
weight = detail.weight!;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int decimal = weight % weight.round() == 0 ? 0 : 1;
|
|
return weight.toStringAsFixed(decimal);
|
|
}
|
|
|
|
String getSetByExerciseType(ExerciseType exerciseType) {
|
|
int value = 0;
|
|
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return value.toStringAsFixed(0);
|
|
}
|
|
|
|
for (var detail in _myPlan!.details) {
|
|
if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
|
value = detail.set!;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return value.toStringAsFixed(0);
|
|
}
|
|
|
|
String getRepeatsByExerciseType(ExerciseType exerciseType) {
|
|
int value = 0;
|
|
|
|
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
|
return value.toStringAsFixed(0);
|
|
}
|
|
|
|
for (var detail in _myPlan!.details) {
|
|
if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
|
value = detail.repeats!;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return value.toStringAsFixed(0);
|
|
}
|
|
|
|
bool existsAddedExerciseTypeInTree(String name) {
|
|
bool exists = false;
|
|
final List<WorkoutMenuTree>? listWorkoutTree = menuBloc.menuTreeRepository.sortedTree[name];
|
|
if (listWorkoutTree != null) {
|
|
listWorkoutTree.forEach((element) {
|
|
if (element.exerciseType!.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added)) {
|
|
exists = true;
|
|
}
|
|
});
|
|
}
|
|
return exists;
|
|
}
|
|
}
|