import 'dart:async'; import 'package:aitrainer_app/main.dart'; 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/training_plan.dart'; import 'package:aitrainer_app/model/training_plan_detail.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/customer_repository.dart'; import 'package:aitrainer_app/repository/mautic_repository.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 { final TrainingPlanRepository trainingPlanRepository; final MenuBloc menuBloc; TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial()); CustomerTrainingPlan? _myPlan; CustomerTrainingPlanDetails? _myDetail; TrainingPlan? _myTrainingPlan; final List trainingPlanDayNames = []; bool started = false; final List dayNames = []; bool restarting = false; bool celebrating = false; int activeDayIndex = 0; CustomerTrainingPlan? getMyPlan() => this._myPlan; setMyPlan(CustomerTrainingPlan? myPlan) => this._myPlan = myPlan; TrainingPlan? getMyTrainingPlan() => this._myTrainingPlan; setMyTrainingPlan(TrainingPlan? myTrainingPlan) => this._myTrainingPlan = myTrainingPlan; CustomerTrainingPlanDetails? getMyDetail() => this._myDetail; setMyDetail(CustomerTrainingPlanDetails? value) => this._myDetail = value; @override Stream mapEventToState(TrainingPlanEvent event) async* { try { if (event is TrainingPlanActivate) { yield TrainingPlanLoading(); _myPlan = trainingPlanRepository.activateTrainingPlan(event.trainingPlanId); _myPlan!.type = CustomerTrainingPlanType.template; menuBloc.menuTreeRepository.sortedTree.forEach((name, list) { final List menuList = list as List; 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; 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.repeats == -1) { yield TrainingPlanReady(); throw Exception("Please type your repeats!"); } 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 (this.isAllDetailsSameExerciseFinished(event.detail)) { 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) { print("NextDetail detail: $nextDetail"); 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); } else if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId && nextDetail.weight == -1 && nextDetail.set! == 1) { print("recalculating -1, set 1 ${event.detail.customerTrainingPlanDetailsId}"); nextDetail = trainingPlanRepository.recalculateDetailFixRepeatsSet1(_myPlan!.trainingPlanId!, nextDetail, event.detail); } } } exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId; // save Exercise Exercise savedExercise = await ExerciseApi().addExercise(exercise); Cache().addExercise(savedExercise); Cache().myTrainingPlan = _myPlan; await Cache().saveMyTrainingPlan(); } if (!isInDebugMode) { CustomerRepository customerRepository = CustomerRepository(); customerRepository.customer = Cache().userLoggedIn; MauticRepository mauticRepository = MauticRepository(customerRepository: customerRepository); await mauticRepository.sendMauticExercise(); } 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 TrainingPlanSkipEntireExercise) { yield TrainingPlanLoading(); List list = getAllDetailsSameExercise(event.detail); list.forEach((element) { if (!element.state.equalsTo(ExercisePlanDetailState.finished)) { element.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 activateTrainingPlanDays() { if (_myTrainingPlan == null || _myTrainingPlan!.details == null) { return; } trainingPlanDayNames.clear(); String dayName = "."; _myTrainingPlan!.details!.forEach((element) { if (element.day != null && element.day != dayName) { trainingPlanDayNames.add(element.day!); dayName = element.day!; } }); if (trainingPlanDayNames.length == 0) { dayName = ""; trainingPlanDayNames.add(dayName); } } List trainingPlanDetailSummary(TrainingPlan plan, String dayName) { List details = []; TrainingPlanDetail? prev; plan.details!.forEach((element) { if (prev == null || element.exerciseTypeId != prev!.exerciseTypeId) { if (element.day! == dayName) { element.summary = getSummary(element); details.add(element); } prev = element; } }); return details; } List getAllTrainingPlanDetailsSameExercise(TrainingPlanDetail detail) { List list = []; getMyTrainingPlan()!.details!.forEach((element) { if (detail.exerciseTypeId == element.exerciseTypeId) { list.add(element); } }); return list; } String getSummary(TrainingPlanDetail detail) { String summary = ""; String set = "1"; set = detail.set.toString() + "/ "; List details = getAllTrainingPlanDetailsSameExercise(detail); int index = 0; String quantities = ""; details.forEach((element) { String delimiter = ", "; if (index == 0) { delimiter = ""; } if (element.repeats == -1) { quantities += delimiter + " MAX "; } else { quantities += delimiter + "${element.repeats}"; } index++; }); //quantities += " / ? kg"; summary = quantities; return summary; } 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; } int getStep(CustomerTrainingPlanDetails detail) { List details = getAllDetailsSameExercise(detail); int step = 0; int indexElement = 0; details.forEach((element) { if (indexElement == 0) { step = element.exercises.length; } else { step += element.exercises.length; } indexElement++; }); //print("STEP: $step "); return step; } 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 && step < detail.set! && !isAllDetailsSameExerciseFinished(detail) && !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]; CustomerTrainingPlanDetails? prev; for (var detail in _myPlan!.days[day]!) { //print("Offset detail $detail"); if (detail.state == ExercisePlanDetailState.inProgress || detail.state == ExercisePlanDetailState.start) { prev = detail; break; } if (prev != null && prev.exerciseTypeId != detail.exerciseTypeId || detail.state == ExercisePlanDetailState.extra) { //print(" --- offset + 1"); indexInStart++; indexInProgress++; } prev = detail; } int index = indexInStart > indexInProgress ? indexInStart : indexInProgress; offset = (index) * 300; 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? listWorkoutTree = menuBloc.menuTreeRepository.sortedTree[name]; if (listWorkoutTree != null) { listWorkoutTree.forEach((element) { if (element.exerciseType!.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added)) { exists = true; } }); } return exists; } List getAllDetailsSameExercise(CustomerTrainingPlanDetails detail) { List list = []; getMyPlan()!.details.forEach((element) { if (detail.exerciseTypeId == element.exerciseTypeId) { list.add(element); } }); return list; } bool isAllDetailsSameExerciseFinished(CustomerTrainingPlanDetails detail) { bool allFinished = true; List list = getAllDetailsSameExercise(detail); for (var listDetail in list) { allFinished = allFinished && (listDetail.exercises.length >= listDetail.set! || listDetail.state.equalsTo(ExercisePlanDetailState.skipped)); } return allFinished; } }