WT 1.1.20(4) Training plan improvements

This commit is contained in:
bossanyit
2021-07-05 22:10:32 +02:00
parent 211307e63e
commit 48a0c3f7de
52 changed files with 2086 additions and 1229 deletions
+20 -11
View File
@@ -36,11 +36,11 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
double bmiAngle = 0;
double bmiTop = 0;
double bmiLeft = 0;
late double weight;
late double height;
double? weight;
double? height;
double bmrEnergy = 0;
late int birthYear;
late String fitnessLevel;
int? birthYear;
String? fitnessLevel;
bool changedWeight = false;
bool changedSizes = false;
@@ -155,6 +155,12 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
Cache().initBadges();
Track().track(TrackingEvent.exercise_new, eventValue: exerciseRepository.exerciseType!.name);
yield ExerciseNewSaved();
} else if (event is ExerciseNewSubmitNoRegistration) {
yield ExerciseNewLoading();
exerciseRepository.addExerciseNoRegistration();
menuBloc.add(MenuTreeDown(parent: 0));
Track().track(TrackingEvent.exercise_new_no_registration, eventValue: exerciseRepository.exerciseType!.name);
yield ExerciseNewSaved();
} else if (event is ExerciseNewBMIAnimate) {
yield ExerciseNewLoading();
yield ExerciseNewReady();
@@ -168,18 +174,18 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
}
double getBMI() {
if (height == 0 || weight == 0) {
if (weight == null || height == null || height == 0 || weight == 0) {
this.bmi = 0;
return 0;
}
this.bmi = weight / (height * height / 10000);
this.bmi = weight! / (height! * height! / 10000);
this.getGoalBMI();
return this.bmi;
}
double getBMR() {
var date = DateTime.now();
if (height == 0 || weight == 0) {
if (weight == null || height == null || height == 0 || weight == 0) {
this.bmi = 0;
return 0;
}
@@ -188,10 +194,10 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
if (customerRepository.customer!.sex == "m") {
//66.47 + ( 13.75 × tömeg kg-ban ) + ( 5.003 × magasság cm-ben ) ( 6.755 × életkor évben kifejezve )
bmr = 66.47 + (13.75 * weight) + (5.003 * height) - (6.755 * (year - customerRepository.customer!.birthYear!));
bmr = 66.47 + (13.75 * weight!) + (5.003 * height!) - (6.755 * (year - customerRepository.customer!.birthYear!));
} else {
//BMR = 655.1 + ( 9.563 × ömeg kg-ban ) + ( 1.85 × magasság cm-ben) ( 4.676 × életkor évben kifejezve )
bmr = 655.1 + (9.563 * weight) + (1.85 * height) - (4.676 * (year - customerRepository.customer!.birthYear!));
bmr = 655.1 + (9.563 * weight!) + (1.85 * height!) - (4.676 * (year - customerRepository.customer!.birthYear!));
}
bmrEnergy = bmr;
@@ -208,6 +214,9 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
}
double getGoalBMI() {
if (weight == null || height == null) {
return 0;
}
if (this.bmi == 0) {
getBMI();
}
@@ -226,8 +235,8 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
}
goalBMI = 24;
this.goalWeight = goalBMI * (height * height / 10000);
this.goalMilestoneWeight = goalMilestoneBMI * (height * height / 10000);
this.goalWeight = goalBMI * (height! * height! / 10000);
this.goalMilestoneWeight = goalMilestoneBMI * (height! * height! / 10000);
//print("Angle: " + bmiAngle.toStringAsFixed(1));
@@ -83,6 +83,10 @@ class ExerciseNewSubmit extends ExerciseNewEvent {
const ExerciseNewSubmit();
}
class ExerciseNewSubmitNoRegistration extends ExerciseNewEvent {
const ExerciseNewSubmitNoRegistration();
}
class ExerciseNewAddError extends ExerciseNewEvent {
final String message;
const ExerciseNewAddError({required this.message});
+6
View File
@@ -29,6 +29,7 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
String? salesText;
String? premiumFunctions = "";
String? trial = "";
Future<void> init() async {
if (Cache().userLoggedIn == null) {
@@ -47,6 +48,11 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
premiumFunctions = "";
}
trial = descriptionRepository.getDescriptionByName("trial");
if (trial == null || trial!.isEmpty) {
trial = "";
}
await RevenueCatPurchases().getOfferings();
this.getProductSet();
Track().track(TrackingEvent.sales_page);
@@ -0,0 +1,274 @@
import 'dart:async';
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/customer_training_plan_details.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
import 'package:aitrainer_app/model/training_evaluation_exercise.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
part 'training_evaluation_event.dart';
part 'training_evaluation_state.dart';
class TrainingEvaluationBloc extends Bloc<TrainingEvaluationEvent, TrainingEvaluationState> {
final TrainingPlanBloc trainingPlanBloc;
final String day;
TrainingEvaluationBloc({required this.trainingPlanBloc, required this.day}) : super(TrainingEvaluationInitial());
String duration = "-";
String totalLift = "0";
String maxTotalLift = "0";
String maxRepeats = "0";
DateTime? end;
List<TrainingEvaluationExercise> evaluationList = [];
@override
Stream<TrainingEvaluationState> mapEventToState(
TrainingEvaluationEvent event,
) async* {
try {
if (event is TrainingEvaluationLoad) {
yield TrainingEvaluationLoading();
await saveResult();
getDuration();
getTotalLift();
getMaxRepeats();
createEvaluationData();
//getMaxTotalLift();
if (end == null || DateTime.now().difference(end!).inMinutes > 5) {
yield TrainingEvaluationReady();
} else {
yield TrainingEvaluationVictoryReady();
}
}
} on Exception catch (e) {
yield TrainingEvaluationError(message: e.toString());
}
}
Future<void> saveResult() async {}
void createEvaluationData() {
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
return;
}
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
if (!element.state.equalsTo(ExercisePlanDetailState.extra)) {
if (!findExerciseInEvaluationList(element.exerciseTypeId!)) {
addEvaluationExercise(element);
} else {
//editEvaluationExercise(element);
}
}
});
}
bool findExerciseInEvaluationList(int exerciseTypeId) {
bool found = false;
for (var exercise in evaluationList) {
if (exercise.exerciseTypeId == exerciseTypeId) {
found = true;
break;
}
}
return found;
}
void addEvaluationExercise(CustomerTrainingPlanDetails detail) {
TrainingEvaluationExercise exercise = TrainingEvaluationExercise();
exercise.exerciseTypeId = detail.exerciseTypeId!;
exercise.name = detail.exerciseType!.nameTranslation;
exercise.state = detail.state;
if (detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
TrainingEvaluationExercise exercise = TrainingEvaluationExercise();
exercise.exerciseTypeId = detail.exerciseTypeId!;
exercise.name = detail.exerciseType!.nameTranslation;
exercise.state = ExercisePlanDetailState.skipped;
exercise.type = TrainingEvaluationExerciseType.weightBased;
this.evaluationList.add(exercise);
} else {
if (detail.exerciseType!.unitQuantityUnit == null) {
exercise.type = TrainingEvaluationExerciseType.repeatBased;
exercise.repeats = detail.repeats;
exercise.maxRepeats = getMaxRepeatsByExerciseType(detail.exerciseTypeId!);
exercise.trend = getTrendEvaluationRepeats(exercise);
} else {
exercise.type = TrainingEvaluationExerciseType.weightBased;
exercise.oneRepMax = Common.calculate1RM(detail.weight!, detail.repeats!.toDouble());
exercise.max1RM = getMax1RMByExerciseType(detail.exerciseTypeId!);
exercise.totalLift = (detail.weight! * detail.repeats!).round();
exercise.maxTotalLift = getMaxTotalLiftByExerciseType(detail.exerciseTypeId!).round();
exercise.trend = this.getTrendWeight(exercise.oneRepMax!, exercise.max1RM, exercise.totalLift, exercise.maxTotalLift);
}
exercise.trendText = getTrendText(exercise.trend!);
exercise.trendColor = getTrendColor(exercise.trend!);
this.evaluationList.add(exercise);
}
}
double getTrendEvaluationRepeats(TrainingEvaluationExercise exercise) {
double rate = exercise.repeats! / exercise.maxRepeats!;
return rate;
}
String getTrendText(double rate) {
if (rate > 1.10) {
return "Strongly Growing";
} else if (rate > 1.05) {
return "Growing";
} else if (rate < 0.90) {
return "Strongly Sinking";
} else if (rate < 0.95) {
return "Sinking";
} else {
return "Stagnant";
}
}
Color getTrendColor(double rate) {
if (rate > 1.10) {
return Colors.green[900]!;
} else if (rate > 1.05) {
return Colors.green[300]!;
} else if (rate < 0.90) {
return Colors.red[900]!;
} else if (rate < 0.95) {
return Colors.green[400]!;
} else {
return Colors.white;
}
}
void getDuration() {
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
return;
}
int index = 0;
DateTime? start;
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
if (element.state.equalsTo(ExercisePlanDetailState.finished)) {
if (index++ == 0) {
start = element.exercises[0].dateAdd!;
} else {
if (element.exercises.length > 0) {
this.end = element.exercises[element.exercises.length - 1].dateAdd!;
}
}
//print("Exercise Date ${element.exercises[0].dateAdd!}");
}
});
if (start == null) {
this.duration = "00:00:00";
} else if (this.end == null) {
this.duration = "00:01:00";
this.end = start;
} else {
this.duration = end!.difference(start!).inMinutes.toString();
}
}
void getTotalLift() {
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
return;
}
double total = 0;
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
if (element.state.equalsTo(ExercisePlanDetailState.finished) && element.exerciseType!.unitQuantityUnit != null) {
total += element.weight! * element.repeats!;
}
});
totalLift = total.toStringAsFixed(0);
}
void getMaxRepeats() {
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
return;
}
int max = 0;
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
if (element.state.equalsTo(ExercisePlanDetailState.finished) && element.exerciseType!.unit != "second") {
print("Repeats ${element.repeats}");
if (max < element.repeats!) {
max = element.repeats!;
}
}
});
maxRepeats = max.toStringAsFixed(0);
}
double getMaxTotalLiftByExerciseType(int exerciseTypeId) {
if (Cache().getExercises() == null || Cache().getExercises()!.length == 0) {
return 0;
}
double maxTotal = 0;
Cache().getExercises()!.forEach((element) {
if (element.exerciseTypeId == exerciseTypeId) {
final double total = element.quantity! * element.unitQuantity!;
if (maxTotal < total) {
maxTotal = total;
}
}
});
return maxTotal;
}
double getMax1RMByExerciseType(int exerciseTypeId) {
if (Cache().getExercises() == null || Cache().getExercises()!.length == 0) {
return 0;
}
double max1RM = 0;
Cache().getExercises()!.forEach((element) {
if (element.exerciseTypeId == exerciseTypeId) {
final double oneRepMax = Common.calculate1RM(element.unitQuantity!, element.quantity!);
if (max1RM < oneRepMax) {
max1RM = oneRepMax;
}
}
});
return max1RM;
}
int getMaxRepeatsByExerciseType(int exerciseTypeId) {
if (Cache().getExercises() == null || Cache().getExercises()!.length == 0) {
return 0;
}
int maxRepeats = 0;
Cache().getExercises()!.forEach((element) {
if (element.exerciseTypeId == exerciseTypeId) {
final int repeats = element.quantity!.toInt();
if (maxRepeats < repeats) {
maxRepeats = repeats;
}
}
});
return maxRepeats;
}
double getOneRepMax(CustomerTrainingPlanDetails detail) {
if (detail.weight == null) {
return 0;
}
return Common.calculate1RM(detail.weight!, detail.repeats!.toDouble());
}
double getTotalLiftExercise(CustomerTrainingPlanDetails detail) {
if (detail.weight == null) {
return 0;
}
return detail.weight! * detail.repeats!;
}
double getTrendWeight(double oneRepMax, max1RM, totalLift, maxTotalLift) {
double oneRepMaxRate = oneRepMax / max1RM;
double totalLiftRate = totalLift / maxTotalLift;
return (oneRepMaxRate + totalLiftRate) / 2;
}
}
@@ -0,0 +1,12 @@
part of 'training_evaluation_bloc.dart';
abstract class TrainingEvaluationEvent extends Equatable {
const TrainingEvaluationEvent();
@override
List<Object> get props => [];
}
class TrainingEvaluationLoad extends TrainingEvaluationEvent {
const TrainingEvaluationLoad();
}
@@ -0,0 +1,32 @@
part of 'training_evaluation_bloc.dart';
abstract class TrainingEvaluationState extends Equatable {
const TrainingEvaluationState();
@override
List<Object> get props => [];
}
class TrainingEvaluationInitial extends TrainingEvaluationState {
const TrainingEvaluationInitial();
}
class TrainingEvaluationLoading extends TrainingEvaluationState {
const TrainingEvaluationLoading();
}
class TrainingEvaluationReady extends TrainingEvaluationState {
const TrainingEvaluationReady();
}
class TrainingEvaluationVictoryReady extends TrainingEvaluationState {
const TrainingEvaluationVictoryReady();
}
class TrainingEvaluationError extends TrainingEvaluationState {
final String message;
const TrainingEvaluationError({required this.message});
@override
List<Object> get props => [message];
}
+65 -29
View File
@@ -11,7 +11,9 @@ 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';
@@ -56,12 +58,22 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
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();
@@ -77,38 +89,51 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
yield TrainingPlanReady();
} else if (event is TrainingPlanSaveExercise) {
yield TrainingPlanLoading();
event.detail.state = ExercisePlanDetailState.inProgress;
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!) {
if (event.detail.weight == -3) {
print("DropSet");
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) {
if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId && nextDetail.weight == -2) {
trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail, nextDetail);
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();
}
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());
@@ -128,6 +153,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
} else if (event is TrainingPlanFinishDay) {
yield TrainingPlanLoading();
celebrating = true;
Track().track(TrackingEvent.training_plan_finished);
yield TrainingPlanDayFinished();
} else if (event is TrainingPlanGoToRestart) {
yield TrainingPlanLoading();
@@ -178,6 +204,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
} else {
_myDetail!.weight = 0;
}
Track().track(TrackingEvent.training_plan_custom, eventValue: event.exerciseType.name);
yield TrainingPlanReady();
}
} on Exception catch (e) {
@@ -186,6 +213,9 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
void addNewPlan() {
if (Cache().userLoggedIn == null) {
throw Exception("Please log in");
}
if (_myPlan == null) {
_myPlan = CustomerTrainingPlan();
} else {
@@ -254,6 +284,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
_myPlan!.days[dayName]!.add(element);
});
this.addExtraExerciseType("Stretching", previousDay);
if (dayNames.length == 0) {
dayName = "";
@@ -280,6 +311,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
detail.parallel = false;
detail.restingTime = 0;
detail.exerciseType = exerciseType;
detail.state = ExercisePlanDetailState.extra;
if (_myPlan!.days[dayName] == null) {
_myPlan!.days[dayName] = [];
}
@@ -372,7 +404,10 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
bool isDone = true;
final String day = dayNames[activeDayIndex];
for (var detail in _myPlan!.days[day]!) {
if (!detail.state.equalsTo(ExercisePlanDetailState.finished) && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
//print("daydone ${detail.state}");
if (!detail.state.equalsTo(ExercisePlanDetailState.extra) &&
!detail.state.equalsTo(ExercisePlanDetailState.finished) &&
!detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
isDone = false;
}
}
@@ -400,6 +435,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
int index = indexInStart > indexInProgress ? indexInStart : indexInProgress;
offset = index * 80;
print("Offset: $offset day: $day ($indexInStart, $indexInProgress)");
return offset;
}
@@ -28,6 +28,15 @@ class TrainingPlanWeightChange extends TrainingPlanEvent {
List<Object> get props => [weight, detail];
}
class TrainingPlanWeightChangeRecalculate extends TrainingPlanEvent {
final CustomerTrainingPlanDetails detail;
final double weight;
const TrainingPlanWeightChangeRecalculate({required this.weight, required this.detail});
@override
List<Object> get props => [weight, detail];
}
class TrainingPlanRepeatsChange extends TrainingPlanEvent {
final CustomerTrainingPlanDetails detail;
final int repeats;