diff --git a/i18n/en.json b/i18n/en.json index dd7634a..3982823 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,5 +1,5 @@ { - "Customers And Exercises": "Customers And Exercises", + "Network Error, please try again later": "Network Error, please try again later", "Home": "Home", "Customers": "Customers", "Exercises": "Exercises", @@ -66,7 +66,9 @@ "Do you save this exercise with these parameters?":"Do you save this exercise with these parameters?", "The number of the exercise": "The number of the exercise", "The number of the exercise done with": "The number of the exercise done with", - "Please repeat with ": "Please repeat with ", + "Please repeat with": "Please repeat with", + "Execute the": "Execute the", + ". set!":". set!", "repeat": "repeat", @@ -109,6 +111,7 @@ "Description": "Description", "Make your first test": "Make your first test", + "finished": "finished", "Why do you need Exercise Control?" : "Why do you need Exercise Control?", "Your 1RM:":"Your 1RM:", @@ -149,5 +152,22 @@ "My Whole Body Development": "My Whole Body Development", "Development Of Muscles": "Development Of Muscles", "Predictions": "Predictions", - "My Trainee's Exercise Logs": "My Trainee's Exercise Logs" + "My Trainee's Exercise Logs": "My Trainee's Exercise Logs", + + "My Development By Muscle": "My Development By Muscle", + "Here you see you development in the last period." : "Here you see your development in the last period.", + "Sum Of Mass":"Sum Of Mass", + "Percent": "Percent", + "One Max Rep": "One Max Rep", + "Detailed": "Detailed", + "Weekly": "Weekly", + "Monthly": "Monthly", + "Yearly": "Yearly", + "times!": "times!", + + "Execute your active Exercise Plan!": "Execute your active Exercise Plan!", + "Select the muscle type and tap on the exercise. One the next page enter the weight and repeat.": "Select the muscle type and tap on the exercise. One the next page enter the weight and repeat.", + + "Custom Exercise Plan": "Custom Exercise Plan", + "Select manually the exercises what you would like to have in your plan. At the end don't forget to save.": "Select manually the exercises what you would like to have in your plan. At the end don't forget to save." } \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index fd1998c..e50589b 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -1,5 +1,5 @@ { - "Customers And Exercises": "Ügyfelek és gyakorlatok", + "Network Error, please try again later": "Hálózati hiba, kérlek próbáld meg később", "Home": "Főoldal", "Customers": "Ügyfelek", "Exercises": "Gyakorlatok", @@ -55,8 +55,9 @@ "Delete": "Törlés", "The number of the exercise": "Írd be a gyakorlat számát", "The number of the exercise done with": "Írd be, mennyivel csináltad a gyakorlatot", - "Please repeat with ": "Kérlek ismételd", - + "Please repeat with": "Kérlek ismételd", + "Execute the": "Hajtsd végre a(z)", + ". set!":". sorozatot!", "Name": "Név", "Exercise": "Gyakorlat", @@ -69,6 +70,7 @@ "with": "", "Do you save this exercise with these parameters?":"Elmented a gyakorlatot ezekkel az adatokkal?", + "repeat": "ismétlés", "meter": "meter", "percent": "százalék", @@ -109,6 +111,7 @@ "Description": "Leírás", "Make your first test": "Végezd el az első tesztet", + "finished": "végrehajtva", "Why do you need Exercise Control?": "Miért szükséges a kontrollgyakorlat?", "Your 1RM:":"Maxerőd:", "Your Real 1RM:":"Ellenőrzött maxerő:", @@ -149,5 +152,22 @@ "My Whole Body Development": "Testem fejlődése", "Development Of Muscles": "Izomcsoportok fejlődése", "Predictions": "Előrejelzések", - "My Trainee's Exercise Logs": "Kliensem edzésnaplőja" + "My Trainee's Exercise Logs": "Kliensem edzésnaplőja", + + "My Development By Muscle": "Izomcsoportok fejlődése", + "Here you see you development in the last period." : "Itt az izomcsoportok fejlődését látod az elmúlt időszakban. A pontos képhez három diagram közül választhatsz: 'Gyakorlat össztömeg', 'Maxerő' és 'Százalékos fejlődés', illetv választhatsz 4 különböző időszaki bontásból is: 'Részletes', 'Heti', 'Havi', 'Éves", + "Sum Of Mass":"Össztömeg", + "Percent": "Százalék", + "One Max Rep": "Maxerő", + "Detailed": "Részletes", + "Weekly": "Heti", + "Monthly": "Havi", + "Yearly": "Éves", + "times!": "ismétléssel!", + + "Execute your active Exercise Plan!": "Hajtsd végre az aktív edzéstervedet", + "Select the muscle type and tap on the exercise. One the next page enter the weight and repeat.": "Válaszd ki az izomcsoporton belül a gyakorlatot, és a következő oldalon add meg a súlyt és az ismétlés számot.", + "Custom Exercise Plan": "Egyedi edzésterv", + "Select manually the exercises what you would like to have in your plan. At the end don't forget to save.": "Válaszd ki a gyakorlatokat, amelyeket szeretnél végrehajtani a tervedben. Utána ne felejtsd el elmenteni." + } \ No newline at end of file diff --git a/lib/bloc/development_by_muscle/development_by_muscle_bloc.dart b/lib/bloc/development_by_muscle/development_by_muscle_bloc.dart new file mode 100644 index 0000000..cb13553 --- /dev/null +++ b/lib/bloc/development_by_muscle/development_by_muscle_bloc.dart @@ -0,0 +1,292 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:aitrainer_app/localization/app_language.dart'; +import 'package:aitrainer_app/model/exercise.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:aitrainer_app/repository/workout_tree_repository.dart'; +import 'package:aitrainer_app/util/calculate.dart'; +import 'package:aitrainer_app/util/common.dart'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:fl_chart/fl_chart.dart'; + +part 'development_by_muscle_event.dart'; + +part 'development_by_muscle_state.dart'; + +class DateRate { + static String daily = "daily"; + static String weekly = "weekly"; + static String monthly = "monthly"; + static String yearly = "yearly"; +} + +class DiagramType { + static String sumMass = "sumMass"; + static String oneRepMax = "oneRepMax"; + static String percent = "percent"; +} + +class ChartDataExtended { + final List data; + final double interval; + final int gridInterval; + + const ChartDataExtended({this.data, this.interval, this.gridInterval}); + + Map toJson() => + { + "data": data.toString(), + "interval" : interval.toString(), + "gridInterval" : gridInterval, + }; + +} + +class DevelopmentByMuscleBloc extends Bloc with Calculate, Common { + final WorkoutTreeRepository workoutTreeRepository; + final ExerciseRepository exerciseRepository = ExerciseRepository(); + LinkedHashMap listChartData = LinkedHashMap(); + List chartData; + String diagramType = DiagramType.sumMass; + String dateRate = DateRate.daily; + double basePercent = 0; + + @override + DevelopmentByMuscleBloc({ + this.workoutTreeRepository}) : + super(DevelopmentByMuscleStateInitial()); + + Future getData() async { + + workoutTreeRepository.sortedTree = null; + workoutTreeRepository.sortByMuscleType(); + + workoutTreeRepository.sortedTree.forEach((key, value) { + List listWorkoutTree = value; + listWorkoutTree.forEach((workoutTree) { + workoutTree.selected = false; + }); + }); + + this.getChartData(); + + } + + void getChartData() { + List exercises = exerciseRepository.getExerciseList(); + exercises.sort( (a, b) => a.exerciseTypeId.compareTo(b.exerciseTypeId)); + exercises = this.groupByDate(exercises); + + int origExerciseTypeId = 0; + int x = 0; + chartData = List(); + double maxData = 0; + double minData = 9999999999999; + exercises.forEach((exercise) { + if ( exercise.unitQuantity != null ) { + if (origExerciseTypeId != exercise.exerciseTypeId && diagramType == DiagramType.percent) { + this.basePercent = getBasePercent(exercise); + } + double diagramValue = this.getDiagramValue(exercise); + + if (maxData < diagramValue) { + maxData = diagramValue; + } + if (minData > diagramValue) { + minData = diagramValue; + } + //print("exercise " + exercise.exerciseTypeId.toString() + " d " + getDateFormat(exercise.dateAdd) + " mass " + sumMass.toString()); + //print("date " + exercise.dateAdd.toIso8601String() + " epoch " + exercise.dateAdd.millisecondsSinceEpoch.toString() ); + BarChartGroupData data = BarChartGroupData( + x: exercise.dateAdd.millisecondsSinceEpoch, + barRods: [ + BarChartRodData(y: diagramValue, width: 12, color: Colors.lightBlue) + ] + ); + if (origExerciseTypeId != exercise.exerciseTypeId) { + if (origExerciseTypeId == 0) { + origExerciseTypeId = exercise.exerciseTypeId; + chartData.add(data); + } else { + double dInterval = ((maxData + minData) / 8).roundToDouble(); + int gridInterval = (dInterval / 3).floor(); + ChartDataExtended extended = ChartDataExtended( + data: chartData, interval: dInterval, gridInterval: gridInterval); + listChartData[origExerciseTypeId] = extended; + maxData = 0; + minData = 9999999999999; + chartData = List(); + chartData.add(data); + origExerciseTypeId = exercise.exerciseTypeId; + } + } else { + origExerciseTypeId = exercise.exerciseTypeId; + chartData.add(data); + } + } + }); + + listChartData.forEach((key, value) { + print ("typeid " + key.toString() + " chardata " + value.toJson().toString()); + }); + return; + } + + String getDatePart(DateTime date) { + String datePart = DateFormat('MM.dd', AppLanguage().appLocal.toString()).format(date);; + if ( this.dateRate == DateRate.weekly ) { + datePart = weekNumber(date).toString(); + } else if ( this.dateRate == DateRate.monthly ) { + datePart = DateFormat('MMM', AppLanguage().appLocal.toString()).format(date); + } else if ( this.dateRate == DateRate.yearly ) { + datePart = DateFormat('y', AppLanguage().appLocal.toString()).format(date); + } else if ( this.dateRate == DateRate.daily ) { + datePart = DateFormat('MM.dd', AppLanguage().appLocal.toString()).format(date); + } + return datePart; + } + + List groupByDate(List exercises) { + List groupedExercises = List(); + + String origDatePart; + + int countExercises = 0; + double sumQuantity = 0; + double maxQuantity = 0; + Exercise origExercise; + exercises.forEach((exercise) { + String exerciseDatePart = getDatePart(exercise.dateAdd); + if ( origExercise != null ) { + if ( origExercise.exerciseTypeId != exercise.exerciseTypeId || origDatePart != exerciseDatePart ) { + Exercise newExercise = origExercise.copy(); + newExercise.datePart = origDatePart; + if (this.diagramType == DiagramType.oneRepMax || this.diagramType == DiagramType.percent) { + newExercise.quantity = maxQuantity; + } else { + newExercise.quantity = sumQuantity / countExercises; + } + groupedExercises.add(newExercise); + countExercises = 0; + sumQuantity = 0; + maxQuantity = 0; + } + } + origExercise = exercise; + origDatePart = exerciseDatePart; + + double newQuantity = getQuantityByDate(exercise); + sumQuantity += newQuantity; + if (maxQuantity < newQuantity) { + maxQuantity = newQuantity; + } + countExercises++; + + }); + + groupedExercises.forEach((element) { + print("grouped " + element.toJson().toString()); + }); + + + return groupedExercises; + } + + double getQuantityByDate(Exercise exercise) { + double sum = 0; + if ( this.diagramType == DiagramType.sumMass ) { + if ( exercise.unitQuantity != null ) { + sum = exercise.quantity * exercise.unitQuantity; + } else { + sum = exercise.quantity; + } + } else if ( this.diagramType == DiagramType.oneRepMax || this.diagramType == DiagramType.percent ) { + if ( exercise.unitQuantity != null ) { + sum = calculate1RM(exercise.quantity, exercise.unitQuantity); + } else { + sum = exercise.quantity; + } + } + return sum; + } + + double getBasePercent(Exercise exercise) { + if ( exercise.unitQuantity != null ) { + this.basePercent = calculate1RM(exercise.quantity, exercise.unitQuantity); + } else { + this.basePercent = exercise.quantity; + } + //print("Base percent of " + exercise.exerciseTypeId.toString() + ": " + basePercent.toString()); + return this.basePercent; + } + + double getDiagramValue(Exercise exercise) { + double value = 0; + /*if ( diagramType == DiagramType.sumMass) { + value = exercise.quantity; + if ( exercise.unitQuantity != null ) { + value *= exercise.unitQuantity; + } + } else if ( diagramType == DiagramType.oneRepMax) { + if ( exercise.unitQuantity != null ) { + value = calculate1RM(exercise.quantity, exercise.unitQuantity); + } + } else if ( diagramType == DiagramType.percent) { + if ( exercise.unitQuantity != null ) { + double oneRepMax = calculate1RM(exercise.quantity, exercise.unitQuantity); + value = (oneRepMax / this.basePercent) * 100; + //print("Percent of " + exercise.exerciseTypeId.toString() + ": " + value.toString() + " date " + exercise.dateAdd.toIso8601String() + " 1RM " + oneRepMax.toString()); + } else { + value = (exercise.quantity / this.basePercent ) * 100; + }*/ + + if ( diagramType == DiagramType.percent) { + if ( exercise.unitQuantity != null ) { + double oneRepMax = calculate1RM(exercise.quantity, exercise.unitQuantity); + value = (oneRepMax / this.basePercent) * 100; + //print("Percent of " + exercise.exerciseTypeId.toString() + ": " + value.toString() + " date " + exercise.dateAdd.toIso8601String() + " 1RM " + oneRepMax.toString()); + } else { + value = (exercise.quantity / this.basePercent ) * 100; + } + } else { + return exercise.quantity; + } + return value; + } + + String getDateFormat(DateTime datetime) { + return DateFormat('yMd', AppLanguage().appLocal.toString()).format(datetime); + } + + @override + Stream mapEventToState(DevelopmentByMuscleEvent event) async* { + try { + if (event is DevelopmentByMuscleLoad) { + yield DevelopmentByMuscleLoadingState(); + await getData(); + yield DevelopmentByMuscleReadyState(); + } else if ( event is DevelopmentByMuscleDiagramTypeChange ) { + yield DevelopmentByMuscleLoadingState(); + String type = event.diagramType; + this.diagramType = type; + getChartData(); + yield DevelopmentByMuscleReadyState(); + } else if ( event is DevelopmentByMuscleDateRateChange ) { + yield DevelopmentByMuscleLoadingState(); + String dateRate = event.dateRate; + this.dateRate = dateRate; + getChartData(); + yield DevelopmentByMuscleReadyState(); + } + } on Exception catch (e) { + yield DevelopmentByMuscleErrorState(message: e.toString()); + } + } +} diff --git a/lib/bloc/development_by_muscle/development_by_muscle_event.dart b/lib/bloc/development_by_muscle/development_by_muscle_event.dart new file mode 100644 index 0000000..95c1111 --- /dev/null +++ b/lib/bloc/development_by_muscle/development_by_muscle_event.dart @@ -0,0 +1,29 @@ +part of 'development_by_muscle_bloc.dart'; + +@immutable +abstract class DevelopmentByMuscleEvent extends Equatable { + const DevelopmentByMuscleEvent(); + + @override + List get props => []; +} + +class DevelopmentByMuscleLoad extends DevelopmentByMuscleEvent { + const DevelopmentByMuscleLoad(); +} + +class DevelopmentByMuscleDateRateChange extends DevelopmentByMuscleEvent { + final String dateRate; + const DevelopmentByMuscleDateRateChange({this.dateRate}); + + @override + List get props => [dateRate]; +} + +class DevelopmentByMuscleDiagramTypeChange extends DevelopmentByMuscleEvent { + final String diagramType; + const DevelopmentByMuscleDiagramTypeChange({this.diagramType}); + + @override + List get props => [diagramType]; +} diff --git a/lib/bloc/development_by_muscle/development_by_muscle_state.dart b/lib/bloc/development_by_muscle/development_by_muscle_state.dart new file mode 100644 index 0000000..d2241af --- /dev/null +++ b/lib/bloc/development_by_muscle/development_by_muscle_state.dart @@ -0,0 +1,30 @@ +part of 'development_by_muscle_bloc.dart'; + +@immutable +abstract class DevelopmentByMuscleState extends Equatable{ + const DevelopmentByMuscleState(); + + @override + List get props => []; +} + +class DevelopmentByMuscleStateInitial extends DevelopmentByMuscleState { + const DevelopmentByMuscleStateInitial(); +} + +class DevelopmentByMuscleLoadingState extends DevelopmentByMuscleState { + const DevelopmentByMuscleLoadingState(); +} + +class DevelopmentByMuscleReadyState extends DevelopmentByMuscleState { + const DevelopmentByMuscleReadyState(); +} + + +class DevelopmentByMuscleErrorState extends DevelopmentByMuscleState { + final String message; + const DevelopmentByMuscleErrorState({this.message}); + + @override + List get props => [message]; +} diff --git a/lib/bloc/exercise_add_by_plan_bloc.dart b/lib/bloc/exercise_add_by_plan_bloc.dart index 3d72ae1..e1bcfad 100644 --- a/lib/bloc/exercise_add_by_plan_bloc.dart +++ b/lib/bloc/exercise_add_by_plan_bloc.dart @@ -1,6 +1,6 @@ import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/customer.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; @@ -8,7 +8,7 @@ import 'package:flutter_form_bloc/flutter_form_bloc.dart'; class ExerciseAddByPlanFormBloc extends FormBloc { final ExerciseRepository exerciseRepository; final ExercisePlanRepository exercisePlanRepository; - final WorkoutTree workoutTree; + final WorkoutMenuTree workoutTree; final customerId; Customer customer; int step = 1; @@ -40,12 +40,12 @@ class ExerciseAddByPlanFormBloc extends FormBloc { } else if ( Cache().getTrainee().customerId == customerId) { customer = Cache().getTrainee(); } - exercisePlanRepository.setActualPlanDetail(workoutTree.exerciseType); + exercisePlanRepository.setActualPlanDetailByExerciseType(workoutTree.exerciseType); exerciseRepository.customer = customer; - countSteps = exercisePlanRepository.actualPlanDetail.serie; + countSteps = exercisePlanRepository.getActualPlanDetail().serie; - unitQuantity1Field.updateInitialValue(exercisePlanRepository.actualPlanDetail.weightEquation); - quantity1Field.updateInitialValue(exercisePlanRepository.actualPlanDetail.repeats.toString()); + unitQuantity1Field.updateInitialValue(exercisePlanRepository.getActualPlanDetail().weightEquation); + quantity1Field.updateInitialValue(exercisePlanRepository.getActualPlanDetail().repeats.toString()); } @@ -59,7 +59,7 @@ class ExerciseAddByPlanFormBloc extends FormBloc { exerciseRepository.setUnitQuantity(unitQuantity1Field.valueToDouble); exerciseRepository.setQuantity(quantity1Field.valueToDouble); exerciseRepository.exercise.exercisePlanDetailId = - exercisePlanRepository.actualPlanDetail.exercisePlanDetailId; + exercisePlanRepository.getActualPlanDetail().exercisePlanDetailId; exerciseRepository.exercise.unit = workoutTree.exerciseType.unit; workoutTree.executed = true; print("On Submitting Add Exercise By Plan " + exerciseRepository.exercise.toJson().toString()); diff --git a/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart b/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart index 339929e..42fb016 100644 --- a/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart +++ b/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart @@ -1,8 +1,7 @@ import 'dart:async'; import 'package:aitrainer_app/model/exercise_type.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; -import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/repository/workout_tree_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -13,12 +12,11 @@ part 'exercise_by_plan_event.dart'; part 'exercise_by_plan_state.dart'; class ExerciseByPlanBloc extends Bloc { - final ExerciseRepository exerciseRepository; final WorkoutTreeRepository menuTreeRepository; final ExercisePlanRepository exercisePlanRepository = ExercisePlanRepository(); int customerId; @override - ExerciseByPlanBloc({this.exerciseRepository, this.menuTreeRepository}) : super(ExerciseByPlanStateInitial()); + ExerciseByPlanBloc({this.menuTreeRepository}) : super(ExerciseByPlanStateInitial()); Future getData() async { exercisePlanRepository.setCustomerId(customerId); @@ -28,11 +26,11 @@ class ExerciseByPlanBloc extends Bloc menuTreeRepository.sortByMuscleType(); menuTreeRepository.sortedTree.forEach((key, value) { - List listWorkoutTree = value; + List listWorkoutTree = value; listWorkoutTree.forEach((workoutTree) { workoutTree.selected = false; - if (exercisePlanRepository.exercisePlanDetails.length > 0) { - if (exercisePlanRepository.exercisePlanDetails[workoutTree.exerciseTypeId] != null) { + if (exercisePlanRepository.getExercisePlanDetailSize() > 0) { + if (exercisePlanRepository.getExercisePlanDetailByExerciseId(workoutTree.exerciseTypeId) != null) { workoutTree.selected = true; } } diff --git a/lib/bloc/exercise_plan/exercise_plan_bloc.dart b/lib/bloc/exercise_plan/exercise_plan_bloc.dart index a60f57d..dae1f06 100644 --- a/lib/bloc/exercise_plan/exercise_plan_bloc.dart +++ b/lib/bloc/exercise_plan/exercise_plan_bloc.dart @@ -1,9 +1,7 @@ import 'dart:async'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; -import 'package:aitrainer_app/model/exercise_type.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; -import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/repository/workout_tree_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -15,11 +13,10 @@ part 'exercise_plan_state.dart'; class ExercisePlanBloc extends Bloc { final WorkoutTreeRepository menuTreeRepository; - final ExerciseRepository exerciseRepository; - final ExercisePlanRepository exercisePlanRepository = ExercisePlanRepository(); + ExercisePlanRepository exercisePlanRepository = ExercisePlanRepository(); int customerId; - ExercisePlanBloc({this.exerciseRepository, this.menuTreeRepository}) : super(ExercisePlanInitial()); + ExercisePlanBloc({this.menuTreeRepository}) : super(ExercisePlanInitial()); Future getData() async { exercisePlanRepository.setCustomerId(customerId); @@ -29,12 +26,11 @@ class ExercisePlanBloc extends Bloc { menuTreeRepository.sortByMuscleType(); menuTreeRepository.sortedTree.forEach((key, value) { - List listWorkoutTree = value; + List listWorkoutTree = value; listWorkoutTree.forEach((workoutTree) { workoutTree.selected = false; - if (exercisePlanRepository.exercisePlanDetails.length > 0) { - if (exercisePlanRepository.exercisePlanDetails[workoutTree.exerciseTypeId] != null) { - //print("bingo"); + if (exercisePlanRepository.getExercisePlanDetailSize() > 0) { + if (exercisePlanRepository.getExercisePlanDetailByExerciseId(workoutTree.exerciseTypeId) != null) { workoutTree.selected = true; } } @@ -42,6 +38,8 @@ class ExercisePlanBloc extends Bloc { }); } + void setExercisePlanRepository(ExercisePlanRepository repo ) => exercisePlanRepository = repo; + @override Stream mapEventToState(ExercisePlanEvent event) async* { try { @@ -50,38 +48,62 @@ class ExercisePlanBloc extends Bloc { await this.getData(); yield ExercisePlanReady(); } - if (event is ExercisePlanUpdate) { + + if (event is ExercisePlanUpdateUI) { yield ExercisePlanLoading(); - WorkoutTree workoutTree = event.workoutTree; + + WorkoutMenuTree workoutTree = event.workoutTree; if (workoutTree != null) { - exercisePlanRepository.addExerciseTypeToPlan(workoutTree.exerciseType); + exercisePlanRepository.setActualPlanDetailByExerciseType(workoutTree.exerciseType); workoutTree.selected = true; } + yield ExercisePlanReady(); - } else if (event is ExercisePlanRemoveExercise) { + } + + else if (event is ExercisePlanAddExercise) { yield ExercisePlanLoading(); ExercisePlanDetail planDetail = event.exercisePlanDetail; - exercisePlanRepository.removeExerciseTypeFromPlan(planDetail.exerciseType); + exercisePlanRepository.actualPlanDetail = planDetail; + + /*if ( exercisePlanRepository.exercisePlanDetails[planDetail.exerciseTypeId] == null ) { + print("Add plan detail " + planDetail.toJson().toString()); + exercisePlanRepository.actualPlanDetail.change = ExercisePlanDetailChange.add; + } else { + print("Update plan detail " + planDetail.toJson().toString()); + exercisePlanRepository.actualPlanDetail.change = ExercisePlanDetailChange.update; + } + exercisePlanRepository.addDetailToPlan();*/ + + if (exercisePlanRepository.getExercisePlanDetailSize() != 0) { + await exercisePlanRepository.saveExercisePlan(); + } + + yield ExercisePlanReady(); + } + + + else if (event is ExercisePlanRemoveExercise) { + yield ExercisePlanLoading(); + ExercisePlanDetail planDetail = event.exercisePlanDetail; + exercisePlanRepository.removeExerciseTypeFromPlanByExerciseTypeId(planDetail.exerciseTypeId); + this.menuTreeRepository.sortedTree.forEach((key, value) { - List listTreeItem = value; + List listTreeItem = value; listTreeItem.forEach((element) { if ( element.exerciseType.exerciseTypeId == planDetail.exerciseTypeId) { element.selected = false; } }); }); - yield ExercisePlanReady(); - } else if (event is ExercisePlanUpdateExercise) { - yield ExercisePlanReady(); - } else if (event is ExercisePlanSave) { - if (exercisePlanRepository.getExercisePlanDetailSize() == 0) { - throw Exception("Please select an exercise"); - } else { - yield ExercisePlanLoading(); + + if (exercisePlanRepository.getExercisePlanDetailSize() != 0) { exercisePlanRepository.saveExercisePlan(); - yield ExercisePlanReady(); } + + yield ExercisePlanReady(); } + } on Exception catch (e) { yield ExercisePlanError(message: e.toString()); } diff --git a/lib/bloc/exercise_plan/exercise_plan_event.dart b/lib/bloc/exercise_plan/exercise_plan_event.dart index 0faf92a..924030c 100644 --- a/lib/bloc/exercise_plan/exercise_plan_event.dart +++ b/lib/bloc/exercise_plan/exercise_plan_event.dart @@ -11,21 +11,16 @@ class ExercisePlanLoad extends ExercisePlanEvent { const ExercisePlanLoad(); } -class ExercisePlanUpdate extends ExercisePlanEvent { - final WorkoutTree workoutTree; - const ExercisePlanUpdate({this.workoutTree}); + +// update UI +class ExercisePlanUpdateUI extends ExercisePlanEvent { + final WorkoutMenuTree workoutTree; + const ExercisePlanUpdateUI({this.workoutTree}); @override List get props => [workoutTree]; } -class ExercisePlanAddExercise extends ExercisePlanEvent { - final ExerciseType exerciseType; - const ExercisePlanAddExercise({this.exerciseType}); - - @override - List get props => [exerciseType]; -} class ExercisePlanRemoveExercise extends ExercisePlanEvent { final ExercisePlanDetail exercisePlanDetail; @@ -35,15 +30,10 @@ class ExercisePlanRemoveExercise extends ExercisePlanEvent { List get props => [exercisePlanDetail]; } -class ExercisePlanUpdateExercise extends ExercisePlanEvent { +class ExercisePlanAddExercise extends ExercisePlanEvent { final ExercisePlanDetail exercisePlanDetail; - const ExercisePlanUpdateExercise({this.exercisePlanDetail}); + const ExercisePlanAddExercise({this.exercisePlanDetail}); @override List get props => [exercisePlanDetail]; -} - -class ExercisePlanSave extends ExercisePlanEvent { - const ExercisePlanSave(); -} - +} \ No newline at end of file diff --git a/lib/bloc/exercise_plan_custom_form.dart b/lib/bloc/exercise_plan_custom_form.dart index 43e714e..8038ef8 100644 --- a/lib/bloc/exercise_plan_custom_form.dart +++ b/lib/bloc/exercise_plan_custom_form.dart @@ -1,4 +1,5 @@ import 'package:aitrainer_app/bloc/exercise_plan/exercise_plan_bloc.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; @@ -36,29 +37,29 @@ class ExercisePlanCustomerFormBloc extends FormBloc { weightField ]); - String repeatsInitial = exercisePlanRepository.actualPlanDetail != null && - exercisePlanRepository.actualPlanDetail.repeats != null ? - exercisePlanRepository.actualPlanDetail.repeats.toString() : "12"; + String repeatsInitial = exercisePlanRepository.getActualPlanDetail() != null && + exercisePlanRepository.getActualPlanDetail().repeats != null ? + exercisePlanRepository.getActualPlanDetail().repeats.toString() : "12"; quantityField.updateInitialValue(repeatsInitial); - String serieInitial = exercisePlanRepository.actualPlanDetail != null && - exercisePlanRepository.actualPlanDetail.serie != null ? - exercisePlanRepository.actualPlanDetail.serie.toString() : "3"; + String serieInitial = exercisePlanRepository.getActualPlanDetail() != null && + exercisePlanRepository.getActualPlanDetail().serie != null ? + exercisePlanRepository.getActualPlanDetail().serie.toString() : "3"; serieField.updateInitialValue(serieInitial); - String weightInitial = exercisePlanRepository.actualPlanDetail != null && - exercisePlanRepository.actualPlanDetail.weightEquation != null ? - exercisePlanRepository.actualPlanDetail.weightEquation : "30"; + String weightInitial = exercisePlanRepository.getActualPlanDetail() != null && + exercisePlanRepository.getActualPlanDetail().weightEquation != null ? + exercisePlanRepository.getActualPlanDetail().weightEquation : "30"; weightField.updateInitialValue(weightInitial); quantityField.onValueChanges(onData: (previous, current) async* { - exercisePlanRepository.actualPlanDetail.repeats = current.valueToInt; + exercisePlanRepository.getActualPlanDetail().repeats = current.valueToInt; }); serieField.onValueChanges(onData: (previous, current) async* { - exercisePlanRepository.actualPlanDetail.serie = current.valueToInt; + exercisePlanRepository.getActualPlanDetail().serie = current.valueToInt; }); weightField.onValueChanges(onData: (previous, current) async* { - exercisePlanRepository.actualPlanDetail.weightEquation = current.value; + exercisePlanRepository.getActualPlanDetail().weightEquation = current.value; }); } @@ -69,12 +70,16 @@ class ExercisePlanCustomerFormBloc extends FormBloc { emitLoading(progress: 30); // Emit either Loaded or Error - exercisePlanRepository.actualPlanDetail.repeats = quantityField.valueToInt; - exercisePlanRepository.actualPlanDetail.serie = serieField.valueToInt; - exercisePlanRepository.actualPlanDetail.weightEquation = weightField.value; - - exercisePlanRepository.addToPlan(); - planBloc.add(ExercisePlanUpdate()); + exercisePlanRepository.getActualPlanDetail().repeats = quantityField.valueToInt; + exercisePlanRepository.getActualPlanDetail().serie = serieField.valueToInt; + exercisePlanRepository.getActualPlanDetail().weightEquation = weightField.value; + if ( exercisePlanRepository.exercisePlanDetails[exercisePlanRepository.getActualPlanDetail()] == null ) { + exercisePlanRepository.getActualPlanDetail().change = ExercisePlanDetailChange.add; + } else { + exercisePlanRepository.getActualPlanDetail().change = ExercisePlanDetailChange.update; + } + exercisePlanRepository.addDetailToPlan(); + planBloc.add(ExercisePlanAddExercise(exercisePlanDetail: exercisePlanRepository.getActualPlanDetail())); emitSuccess(canSubmitAgain: false); } on Exception catch (ex) { diff --git a/lib/bloc/menu/menu_bloc.dart b/lib/bloc/menu/menu_bloc.dart index fe2144a..320a52f 100644 --- a/lib/bloc/menu/menu_bloc.dart +++ b/lib/bloc/menu/menu_bloc.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/workout_tree_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; diff --git a/lib/bloc/menu/menu_state.dart b/lib/bloc/menu/menu_state.dart index 8408f41..0184d80 100644 --- a/lib/bloc/menu/menu_state.dart +++ b/lib/bloc/menu/menu_state.dart @@ -17,7 +17,7 @@ class MenuLoading extends MenuState { } class MenuReady extends MenuState { - final WorkoutTree workoutTree; + final WorkoutMenuTree workoutTree; const MenuReady({this.workoutTree}); diff --git a/lib/main.dart b/lib/main.dart index 63a7173..4904497 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,12 +15,13 @@ import 'package:aitrainer_app/view/exercise_control_page.dart'; import 'package:aitrainer_app/view/exercise_execute_by_plan_page.dart'; import 'package:aitrainer_app/view/exercise_log_page.dart'; import 'package:aitrainer_app/view/exercise_plan_custom_page.dart'; -import 'package:aitrainer_app/view/exercise_plan_detail_add_page.dart'; +import 'package:aitrainer_app/view/exercise_plan_custom_detail_add_page.dart'; import 'package:aitrainer_app/view/exercise_type_description.dart'; import 'package:aitrainer_app/view/gdpr.dart'; import 'package:aitrainer_app/view/login.dart'; import 'package:aitrainer_app/view/exercise_new_page.dart'; import 'package:aitrainer_app/view/menu_page.dart'; +import 'package:aitrainer_app/view/mydevelopment_muscle_page.dart'; import 'package:aitrainer_app/view/mydevelopment_page.dart'; import 'package:aitrainer_app/view/myexcercise_plan_page.dart'; import 'package:aitrainer_app/view/registration.dart'; @@ -34,6 +35,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:aitrainer_app/localization/app_localization.dart'; import 'package:sentry/sentry.dart'; import 'bloc/account/account_bloc.dart'; +import 'bloc/development_by_muscle/development_by_muscle_bloc.dart'; import 'bloc/exercise_by_plan/exercise_by_plan_bloc.dart'; import 'bloc/exercise_plan/exercise_plan_bloc.dart'; import 'bloc/menu/menu_bloc.dart'; @@ -108,7 +110,7 @@ Future main() async { final WorkoutTreeRepository menuTreeRepository = WorkoutTreeRepository(); runApp( MultiBlocProvider( - providers: [ + providers: [ BlocProvider( create: (BuildContext context) => SessionBloc(session: Session()), ), @@ -125,7 +127,12 @@ Future main() async { create: (BuildContext context) => ExercisePlanBloc(menuTreeRepository: menuTreeRepository), ), BlocProvider( - create: (BuildContext context) => ExerciseByPlanBloc(menuTreeRepository: menuTreeRepository, exerciseRepository: ExerciseRepository()), + create: (BuildContext context) => ExerciseByPlanBloc( + menuTreeRepository: menuTreeRepository), + ), + BlocProvider( + create: (BuildContext context) => DevelopmentByMuscleBloc( + workoutTreeRepository: menuTreeRepository), ), ], @@ -195,6 +202,7 @@ class AitrainerApp extends StatelessWidget { 'exercisePlanDetailAdd': (context) => ExercisePlanDetailAddPage(), 'exerciseByPlanPage': (context) => ExerciseByPlanPage(), 'exerciseAddByPlanPage': (context) => ExerciseAddByPlanPage(), + 'mydevelopmentMusclePage': (context) => MyDevelopmentMusclePage(), }, initialRoute: 'home', title: 'WorkoutTest', diff --git a/lib/model/cache.dart b/lib/model/cache.dart index e58ac1c..8009581 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -4,7 +4,7 @@ import 'package:aitrainer_app/model/exercise_plan.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/service/exercise_tree_service.dart'; import 'package:aitrainer_app/service/exercisetype_service.dart'; @@ -59,9 +59,10 @@ class Cache { List _exercises; ExercisePlan _myExercisePlan; - List _myExercisesPlanDetail; - LinkedHashMap _tree = LinkedHashMap(); + LinkedHashMap _myExercisesPlanDetails = LinkedHashMap(); + + LinkedHashMap _tree = LinkedHashMap(); double _percentExercises = -1; Customer _trainee; @@ -178,56 +179,63 @@ class Cache { this._exercisesTrainee = exercises; } - void setWorkoutTree( LinkedHashMap tree) { + void setWorkoutMenuTree( LinkedHashMap tree) { this._tree = tree; } - List getExerciseTypes() { - return this._exerciseTypes; + List getExerciseTypes() => this._exerciseTypes; + + List getExerciseTree() => this._exerciseTree; + + List getExercises() => this._exercises; + + List getExercisesTrainee() => this._exercisesTrainee; + + LinkedHashMap getWorkoutMenuTree() => this._tree; + + void setPercentExercises(double percent) => this._percentExercises = percent; + + double getPercentExercises() => this._percentExercises; + + void addExercise(Exercise exercise) => _exercises.add(exercise); + + void addExerciseTrainee(Exercise exercise) => _exercisesTrainee.add(exercise); + + Customer getTrainee() => this._trainee; + + void setTrainee(Customer trainee) => _trainee = trainee; + + void setTraineeExercisePlan(ExercisePlan exercisePlan) => this._traineeExercisePlan = exercisePlan; + + ExercisePlan getTraineesExercisePlan() => this._traineeExercisePlan; + + void setMyExercisePlan(ExercisePlan exercisePlan) => _myExercisePlan = exercisePlan; + + ExercisePlan getMyExercisePlan() => _myExercisePlan; + + void setMyExercisePlanDetails(LinkedHashMap listExercisePlanDetail) + => _myExercisesPlanDetails = listExercisePlanDetail; + + void addToMyExercisePlanDetails(ExercisePlanDetail detail) => _myExercisesPlanDetails[detail.exerciseTypeId] = detail; + + LinkedHashMap getMyExercisePlanDetails() => _myExercisesPlanDetails; + + void resetMyExercisePlanDetails() => _myExercisesPlanDetails = LinkedHashMap(); + + void updateMyExercisePlanDetail(ExercisePlanDetail detail) { + this.addToMyExercisePlanDetails(detail); } - List getExerciseTree() { - return this._exerciseTree; + void deleteMyExercisePlanDetail(ExercisePlanDetail detail) + => this.deleteMyExercisePlanDetailByExerciseTypeId(detail.exerciseTypeId); + + void deletedMyExercisePlanDetail(ExercisePlanDetail detail) + => this._myExercisesPlanDetails[detail.exerciseTypeId].change = ExercisePlanDetailChange.deleted; + + void deleteMyExercisePlanDetailByExerciseTypeId(int exerciseTypeId) { + this._myExercisesPlanDetails[exerciseTypeId].change = ExercisePlanDetailChange.delete; } - List getExercises() { - return this._exercises; - } - List getExercisesTrainee() { - return this._exercisesTrainee; - } - - LinkedHashMap getWorkoutTree() { - return this._tree; - } - - void setPercentExercises(double percent) { - this._percentExercises = percent; - } - - double getPercentExercises() { - return this._percentExercises; - } - - void addExercise(Exercise exercise) { - _exercises.add(exercise); - } - - void addExerciseTrainee(Exercise exercise) { - _exercisesTrainee.add(exercise); - } - - Customer getTrainee() { - return this._trainee; - } - - void setTrainee(Customer trainee) { - this._trainee = trainee; - } - - void setTraineeExercisePlan(ExercisePlan exercisePlan) { - this._traineeExercisePlan = exercisePlan; - } } \ No newline at end of file diff --git a/lib/model/exercise.dart b/lib/model/exercise.dart index e3b7dd0..8dfc562 100644 --- a/lib/model/exercise.dart +++ b/lib/model/exercise.dart @@ -1,3 +1,4 @@ +import 'package:aitrainer_app/util/common.dart'; import 'package:intl/intl.dart'; class Exercise { @@ -10,6 +11,8 @@ class Exercise { DateTime dateAdd; int exercisePlanDetailId; + String datePart; + Exercise({this.exerciseTypeId, this.customerId, this.quantity, this.dateAdd}); @@ -21,6 +24,7 @@ class Exercise { this.unit = json['unit']; this.unitQuantity = json['unitQuantity']; this.dateAdd = DateTime.parse( json['dateAdd'] ); + this.datePart = DateFormat('yyyy-MM-dd').format(this.dateAdd); } Map toJson() => @@ -33,4 +37,16 @@ class Exercise { "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd), "exercisePlanDetailId": exercisePlanDetailId, }; + + Exercise copy() { + Exercise newExercise = Exercise(); + newExercise.exerciseTypeId = this.exerciseTypeId; + newExercise.customerId = this.customerId; + newExercise.quantity = this.quantity; + newExercise.unit = this.unit; + newExercise.unitQuantity = this.unitQuantity; + newExercise.dateAdd = this.dateAdd; + newExercise.exercisePlanDetailId = this.exercisePlanDetailId; + return newExercise; + } } \ No newline at end of file diff --git a/lib/model/exercise_plan_detail.dart b/lib/model/exercise_plan_detail.dart index eac325d..11096cc 100644 --- a/lib/model/exercise_plan_detail.dart +++ b/lib/model/exercise_plan_detail.dart @@ -1,7 +1,12 @@ -import 'package:aitrainer_app/model/exercise_plan.dart'; - import 'exercise_type.dart'; +class ExercisePlanDetailChange { + static const String add = "add"; + static const String delete = "delete"; + static const String update = "update"; + static const String deleted = "deleted"; +} + class ExercisePlanDetail { int exercisePlanDetailId; int exercisePlanId; @@ -11,6 +16,7 @@ class ExercisePlanDetail { String weightEquation; ExerciseType exerciseType; + String change; // 1: update -1:delete 0: new ExercisePlanDetail(int exerciseTypeId) { this.exerciseTypeId = exerciseTypeId; diff --git a/lib/model/workout_tree.dart b/lib/model/workout_menu_tree.dart similarity index 85% rename from lib/model/workout_tree.dart rename to lib/model/workout_menu_tree.dart index dae5454..a12ac6c 100644 --- a/lib/model/workout_tree.dart +++ b/lib/model/workout_menu_tree.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'exercise_type.dart'; -class WorkoutTree { +class WorkoutMenuTree { int id; int parent; String name; @@ -20,7 +20,7 @@ class WorkoutTree { String exerciseDetail; String nameEnglish; - WorkoutTree(this.id, this.parent, this.name, this.imageName, this.color, this.fontSize, this.child, + WorkoutMenuTree(this.id, this.parent, this.name, this.imageName, this.color, this.fontSize, this.child, this.exerciseTypeId, this.exerciseType, this.base, this.is1RM, this.nameEnglish); Map toJson() { diff --git a/lib/repository/exercise_plan_repository.dart b/lib/repository/exercise_plan_repository.dart index 61df4f4..4c08c33 100644 --- a/lib/repository/exercise_plan_repository.dart +++ b/lib/repository/exercise_plan_repository.dart @@ -8,31 +8,34 @@ import 'package:aitrainer_app/service/exercise_plan_service.dart'; class ExercisePlanRepository { bool newPlan = true; - ExercisePlan _exercisePlan; + ExercisePlan exercisePlan; LinkedHashMap exercisePlanDetails = LinkedHashMap(); - LinkedHashMap _origExercisePlanDetails = - LinkedHashMap(); - int _customerId = 0; + int customerId = 0; ExercisePlanDetail actualPlanDetail; void setCustomerId( int customerId ) { - this._customerId = customerId; + this.customerId = customerId; } - int getCustomerId() { - return this._customerId; + int getCustomerId() => customerId; + + void setExercisePlan(ExercisePlan plan) { + this.exercisePlan = plan; } - void addExerciseTypeToPlan(ExerciseType exerciseType) { - setActualPlanDetail(exerciseType); - } - void addToPlan() { + ExercisePlan getExercisePlan() => exercisePlan; + + void addDetailToPlan() { + actualPlanDetail.exercisePlanId = exercisePlan.exercisePlanId; exercisePlanDetails[actualPlanDetail.exerciseTypeId] = actualPlanDetail; + Cache().addToMyExercisePlanDetails(actualPlanDetail); } - void setActualPlanDetail(ExerciseType exerciseType) { + ExercisePlanDetail getExercisePlanDetailByExerciseId(int exerciseTypeId) => exercisePlanDetails[exerciseTypeId]; + + void setActualPlanDetailByExerciseType(ExerciseType exerciseType) { ExercisePlanDetail detail = exercisePlanDetails[exerciseType.exerciseTypeId]; if ( detail != null ) { actualPlanDetail = detail; @@ -42,11 +45,14 @@ class ExercisePlanRepository { actualPlanDetail.exerciseType = exerciseType; } - int getPlanDetailId(int exerciseTypeId) { - ExercisePlanDetail detail = exercisePlanDetails[exerciseTypeId]; - return detail.exercisePlanDetailId; + ExercisePlanDetail getActualPlanDetail() => actualPlanDetail; + + void setActualPlanDetail( ExercisePlanDetail detail ) { + this.actualPlanDetail = detail; } + int getPlanDetailId(int exerciseTypeId) => exercisePlanDetails[exerciseTypeId].exercisePlanDetailId; + String getPlanDetail(int exerciseTypeId) { ExercisePlanDetail detail = exercisePlanDetails[exerciseTypeId]; String detailString = ""; @@ -70,96 +76,121 @@ class ExercisePlanRepository { exercisePlanDetail.serie = serie; exercisePlanDetail.repeats = repeat; exercisePlanDetail.weightEquation = weight; + exercisePlanDetail.change = ExercisePlanDetailChange.update; exercisePlanDetails[exerciseType.exerciseTypeId] = exercisePlanDetail; } void removeExerciseTypeFromPlan(ExerciseType exerciseType) { - exercisePlanDetails.remove(exerciseType.exerciseTypeId); + removeExerciseTypeFromPlanByExerciseTypeId(exerciseType.exerciseTypeId); + } + + void removeExerciseTypeFromPlanByExerciseTypeId(int exerciseTypeId) { + exercisePlanDetails[exerciseTypeId].change = ExercisePlanDetailChange.delete; + Cache().deleteMyExercisePlanDetailByExerciseTypeId(exerciseTypeId); } Future saveExercisePlan() async { - if ( _exercisePlan == null ) { + if ( exercisePlan == null ) { if ( Cache().userLoggedIn == null ) { throw Exception("please log in"); } String exercisePlanName; - if ( this._customerId == Cache().userLoggedIn.customerId) { + if ( this.customerId == Cache().userLoggedIn.customerId) { exercisePlanName = Cache().userLoggedIn.name + " private"; } else { exercisePlanName = Cache().getTrainee().name + " " + Cache().getTrainee().firstname + " private"; } - _exercisePlan = ExercisePlan(exercisePlanName, this._customerId); + exercisePlan = ExercisePlan(exercisePlanName, this.customerId); } if ( newPlan ) { - _exercisePlan.dateAdd = DateTime.now(); - _exercisePlan.private = true; + exercisePlan.dateAdd = DateTime.now(); + exercisePlan.private = true; ExercisePlan savedExercisePlan = - await ExercisePlanApi().saveExercisePlan(_exercisePlan); + await ExercisePlanApi().saveExercisePlan(exercisePlan); - exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { + LinkedHashMap savedExercisePlanDetails = LinkedHashMap(); + exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { exercisePlanDetail.exercisePlanId = savedExercisePlan.exercisePlanId; - await ExercisePlanApi().saveExercisePlanDetail(exercisePlanDetail); - }); + ExercisePlanDetail savedDetail = await ExercisePlanApi().saveExercisePlanDetail(exercisePlanDetail); + savedExercisePlanDetails[savedDetail.exerciseTypeId] = savedDetail; + }); + + exercisePlan = savedExercisePlan; + exercisePlanDetails = savedExercisePlanDetails; + } else { - await ExercisePlanApi().updateExercisePlan(_exercisePlan, _exercisePlan.exercisePlanId); + //await ExercisePlanApi().updateExercisePlan(exercisePlan, exercisePlan.exercisePlanId); - _origExercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { - if (exercisePlanDetails[exercisePlanDetail.exercisePlanDetailId] == null) { + exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { + if ( exercisePlanDetail.change == ExercisePlanDetailChange.delete ) { await ExercisePlanApi() .deleteExercisePlanDetail(exercisePlanDetail.exercisePlanDetailId); - } else { + exercisePlanDetail.change = ExercisePlanDetailChange.deleted; + Cache().deletedMyExercisePlanDetail(exercisePlanDetail); + + } else if ( exercisePlanDetail.change == ExercisePlanDetailChange.update ) { await ExercisePlanApi() .updateExercisePlanDetail(exercisePlanDetail, exercisePlanDetail.exercisePlanDetailId); + Cache().updateMyExercisePlanDetail(exercisePlanDetail); + } else if ( exercisePlanDetail.change == ExercisePlanDetailChange.add ) { + await ExercisePlanApi() + .saveExercisePlanDetail(exercisePlanDetail); + Cache().addToMyExercisePlanDetails(exercisePlanDetail); } }); - exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async{ - exercisePlanDetail.exercisePlanId = _exercisePlan.exercisePlanId; - if ( _origExercisePlanDetails[exercisePlanDetail.exercisePlanDetailId] == null) { - await ExercisePlanApi() - .saveExercisePlanDetail(exercisePlanDetail); - } - }); + } } Future getLastExercisePlan() async { - if ( _customerId == 0) { + if ( customerId == 0) { return null; } - _exercisePlan = await ExercisePlanApi().getLastExercisePlan(_customerId); - newPlan = (_exercisePlan == null); - print("New plan: " + newPlan.toString()); + ExercisePlan myExercisePlan = Cache().getMyExercisePlan(); + if ( myExercisePlan != null ) { + exercisePlan = myExercisePlan; + return myExercisePlan; + } - return _exercisePlan; + exercisePlan = await ExercisePlanApi().getLastExercisePlan(customerId); + newPlan = (exercisePlan == null); + print("New plan: " + newPlan.toString()); + Cache().setMyExercisePlan(exercisePlan); + return exercisePlan; } Future getExercisePlanDetails() async { - if (_exercisePlan == null) { + if (exercisePlan == null) { ExercisePlan exercisePlan = await this.getLastExercisePlan(); if ( exercisePlan == null ) { exercisePlanDetails = LinkedHashMap(); - _origExercisePlanDetails = LinkedHashMap(); return; } } - exercisePlanDetails = LinkedHashMap(); - _origExercisePlanDetails = LinkedHashMap(); - List list = - await ExercisePlanApi().getExercisePlanDetail(_exercisePlan.exercisePlanId); + List list = List(); + LinkedHashMap listCache = Cache().getMyExercisePlanDetails(); + if ( listCache.length > 0) { + exercisePlanDetails = listCache; + return; + } else { + list = await ExercisePlanApi().getExercisePlanDetail(exercisePlan.exercisePlanId); + } + + exercisePlanDetails = LinkedHashMap(); list.forEach((element) { newPlan = false; ExercisePlanDetail detail = element; - _origExercisePlanDetails[detail.exerciseTypeId] = detail; exercisePlanDetails[detail.exerciseTypeId] = detail; }); + Cache().setMyExercisePlanDetails(exercisePlanDetails); return; } diff --git a/lib/repository/exercise_repository.dart b/lib/repository/exercise_repository.dart index 5cb4b7e..a421798 100644 --- a/lib/repository/exercise_repository.dart +++ b/lib/repository/exercise_repository.dart @@ -4,7 +4,7 @@ import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/customer.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/service/exercise_service.dart'; class ExerciseRepository { @@ -120,14 +120,14 @@ class ExerciseRepository { List baseTreeItem = List(); List checkedBaseTreeItem = List(); int count1RMExercises = 0; - LinkedHashMap tree = Cache().getWorkoutTree(); + LinkedHashMap tree = Cache().getWorkoutMenuTree(); if ( tree == null ) { return; } tree.forEach((key, value) { - WorkoutTree treeItem = value; + WorkoutMenuTree treeItem = value; if (treeItem.exerciseType != null && treeItem.exerciseType.base == true && !baseTreeItem.contains(treeItem.parent)) { @@ -148,7 +148,7 @@ class ExerciseRepository { if ( !checkedExerciseTypeId.contains(exercise.exerciseTypeId )) { checkedExerciseTypeId.add(exercise.exerciseTypeId); tree.forEach((key, value) { - WorkoutTree treeItem = value; + WorkoutMenuTree treeItem = value; if (treeItem.exerciseType != null && treeItem.exerciseType.base == true && exercise.exerciseTypeId == treeItem.exerciseType.exerciseTypeId diff --git a/lib/repository/workout_tree_repository.dart b/lib/repository/workout_tree_repository.dart index 4aa5c41..e1cb397 100644 --- a/lib/repository/workout_tree_repository.dart +++ b/lib/repository/workout_tree_repository.dart @@ -3,7 +3,7 @@ import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/service/exercise_tree_service.dart'; import 'package:aitrainer_app/service/exercisetype_service.dart'; @@ -29,8 +29,8 @@ class Antagonist { } class WorkoutTreeRepository { - final LinkedHashMap tree = LinkedHashMap(); - SplayTreeMap sortedTree = SplayTreeMap>(); + final LinkedHashMap tree = LinkedHashMap(); + SplayTreeMap sortedTree = SplayTreeMap>(); bool isEnglish; final Map _antagonist = { @@ -61,7 +61,7 @@ class WorkoutTreeRepository { if ( is1RM == false && treeItem.parentId != 0) { is1RM = isParent1RM(treeItem.parentId); } - this.tree[treeItem.name] = WorkoutTree( + this.tree[treeItem.name] = WorkoutMenuTree( treeItem.treeId, treeItem.parentId, treeName, @@ -87,7 +87,7 @@ class WorkoutTreeRepository { String assetImage = 'asset/menu/' + exerciseType.imageUrl.substring(7); bool is1RM = this.isParent1RM(exerciseType.treeId); exerciseType.is1RM = is1RM; - this.tree[exerciseType.name] = WorkoutTree( + this.tree[exerciseType.name] = WorkoutMenuTree( exerciseType.exerciseTypeId, exerciseType.treeId, exerciseTypeName, @@ -103,7 +103,7 @@ class WorkoutTreeRepository { ); }); - Cache().setWorkoutTree(tree); + Cache().setWorkoutMenuTree(tree); ExerciseRepository exerciseRepository = ExerciseRepository(); exerciseRepository.getBaseExerciseFinishedPercent(); } @@ -112,7 +112,7 @@ class WorkoutTreeRepository { bool isTreeItem1RM = false; this.tree.forEach((key, value) { - WorkoutTree treeItem = value as WorkoutTree; + WorkoutMenuTree treeItem = value as WorkoutMenuTree; if ( treeItem.id == treeId ) { isTreeItem1RM = treeItem.is1RM; //print (treeItem.name + " 1RM " + treeItem.is1RM.toString() ); @@ -124,9 +124,9 @@ class WorkoutTreeRepository { } LinkedHashMap getBranch(int parent) { - LinkedHashMap branch = LinkedHashMap(); + LinkedHashMap branch = LinkedHashMap(); tree.forEach((key, value) { - WorkoutTree workoutTree = value as WorkoutTree; + WorkoutMenuTree workoutTree = value as WorkoutMenuTree; if ( parent == workoutTree.parent) { branch[key] = value; } @@ -134,10 +134,10 @@ class WorkoutTreeRepository { return branch; } - List getBranchList(int parent) { - List branch = List(); + List getBranchList(int parent) { + List branch = List(); tree.forEach((key, value) { - WorkoutTree workoutTree = value as WorkoutTree; + WorkoutMenuTree workoutTree = value as WorkoutMenuTree; if ( parent == workoutTree.parent) { branch.add(workoutTree); } @@ -146,9 +146,9 @@ class WorkoutTreeRepository { } void sortByMuscleType() { - sortedTree = SplayTreeMap>(); + sortedTree = SplayTreeMap>(); tree.forEach((key, value) { - WorkoutTree workoutTree = value as WorkoutTree; + WorkoutMenuTree workoutTree = value as WorkoutMenuTree; //print("treeitem: " + workoutTree.toJson().toString()); /*if ( workoutTree.exerciseType != null) { print("treeItem exerciseTye " + workoutTree.exerciseType.toJson().toString()); diff --git a/lib/service/api.dart b/lib/service/api.dart index 3c750cc..ed4abac 100644 --- a/lib/service/api.dart +++ b/lib/service/api.dart @@ -21,12 +21,13 @@ class APIClient with Common { 'Content-Type': 'application/json', 'Authorization' : "Bearer " + authToken } ); + print(" ------------get response code: " + response.statusCode.toString()); if(response.statusCode == 200) { return utf8.decode(response.bodyBytes); } else if(response.statusCode == 404 ) { throw NotFoundException(message: "Not Found"); } else { - throw Exception("Unable to perform HTTP request!"); + throw Exception("Network Error, please try again later"); } } @@ -49,8 +50,9 @@ class APIClient with Common { }, body: body, ); - String decodedResponse = utf8convert(response.body); - print(" ------------ response: " + decodedResponse); + print(" ------------post response code: " + response.statusCode.toString()); + final String decodedResponse = utf8convert(response.body); + print(" ------------ response: $decodedResponse"); return decodedResponse; } @@ -59,6 +61,7 @@ class APIClient with Common { try { final body = '{"username":"$email", "password":"$password"}'; + print("authentication with $email"); final response = await http.post( uri, headers: { diff --git a/lib/service/exercise_plan_service.dart b/lib/service/exercise_plan_service.dart index 6d61036..35db27e 100644 --- a/lib/service/exercise_plan_service.dart +++ b/lib/service/exercise_plan_service.dart @@ -1,4 +1,3 @@ -import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_plan.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/util/not_found_exception.dart'; @@ -17,7 +16,7 @@ class ExercisePlanApi { "exercise_plan", body); savedExercisePlan = ExercisePlan.fromJson(jsonDecode(responseBody)); - Cache().setTraineeExercisePlan(savedExercisePlan); + } on Exception catch(e) { throw new Exception(e.toString()); } @@ -28,14 +27,14 @@ class ExercisePlanApi { ExercisePlan exercisePlan, int exercisePlanId) async { String body = JsonEncoder().convert(exercisePlan.toJson()); - print(" ===== saving exercisePlan $exercisePlan"); + print(" ===== update exercisePlan $exercisePlan"); ExercisePlan updatedExercisePlan; try { final String responseBody = await _client.post( "exercise_plan/" + exercisePlanId.toString(), body); updatedExercisePlan = ExercisePlan.fromJson(jsonDecode(responseBody)); - Cache().setTraineeExercisePlan(updatedExercisePlan); + } on Exception catch(e) { throw new Exception(e.toString()); } @@ -44,7 +43,7 @@ class ExercisePlanApi { Future saveExercisePlanDetail(ExercisePlanDetail exercisePlanDetail) async { String body = JsonEncoder().convert(exercisePlanDetail.toJson()); - print(" ===== update exercisePlanDetail $exercisePlanDetail"); + print(" ===== save exercisePlanDetail $exercisePlanDetail"); ExercisePlanDetail savedExercisePlanDetail; try { final String responseBody = await _client.post( diff --git a/lib/treeview/tree_view.dart b/lib/treeview/tree_view.dart new file mode 100644 index 0000000..4775138 --- /dev/null +++ b/lib/treeview/tree_view.dart @@ -0,0 +1,135 @@ +/// Tree view widget library +library tree_view; + +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +class TreeView extends InheritedWidget { + final List children; + final bool startExpanded; + + TreeView({ + Key key, + @required List children, + bool startExpanded = false, + }) : this.children = children, + this.startExpanded = startExpanded, + super( + key: key, + child: _TreeViewData( + children: children, + ), + ); + + static TreeView of(BuildContext context) { + //return context.inheritFromWidgetOfExactType(TreeView); + return context.dependOnInheritedWidgetOfExactType(aspect: TreeView); + } + + @override + bool updateShouldNotify(TreeView oldWidget) { + if (oldWidget.children == this.children && + oldWidget.startExpanded == this.startExpanded) { + return false; + } + return true; + } +} + +class _TreeViewData extends StatelessWidget { + final List children; + + const _TreeViewData({ + this.children, + }); + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: children.length, + itemBuilder: (context, index) { + return children.elementAt(index); + }, + ); + } +} + +class TreeViewChild extends StatefulWidget { + final bool startExpanded; + final Widget parent; + final List children; + final VoidCallback onTap; + + TreeViewChild({ + @required this.parent, + @required this.children, + this.startExpanded, + this.onTap, + Key key, + }) : super(key: key) { + assert(parent != null); + assert(children != null); + } + + @override + TreeViewChildState createState() => TreeViewChildState(); + + TreeViewChild copyWith( + TreeViewChild source, { + bool startExpanded, + Widget parent, + List children, + VoidCallback onTap, + }) { + return TreeViewChild( + parent: parent ?? source.parent, + children: children ?? source.children, + startExpanded: startExpanded ?? source.startExpanded, + onTap: onTap ?? source.onTap, + ); + } +} + +class TreeViewChildState extends State { + bool isExpanded; + + @override + void initState() { + super.initState(); + isExpanded = widget.startExpanded; + } + + @override + void didChangeDependencies() { + isExpanded = widget.startExpanded ?? TreeView.of(context).startExpanded; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: widget.parent, + onTap: widget.onTap ?? () => toggleExpanded(), + ), + AnimatedContainer( + duration: Duration(milliseconds: 400), + child: isExpanded + ? Column( + mainAxisSize: MainAxisSize.min, + children: widget.children, + ) + : Offstage(), + ), + ], + ); + } + + void toggleExpanded() { + setState(() { + this.isExpanded = !this.isExpanded; + }); + } +} diff --git a/lib/util/calculate.dart b/lib/util/calculate.dart new file mode 100644 index 0000000..b430b58 --- /dev/null +++ b/lib/util/calculate.dart @@ -0,0 +1,15 @@ +mixin Calculate { + + double calculate1RM(double quantity, double unitQuantity) { + double weight = unitQuantity; + double repeat = quantity; + if ( weight == 0 || repeat == 0) { + return 0; + } + double rmWendler = weight * repeat * 0.0333 + weight; + double rmOconner = weight * (1 + repeat / 40); + double average = (rmWendler + rmOconner)/2; + + return average; + } +} \ No newline at end of file diff --git a/lib/util/common.dart b/lib/util/common.dart index 6d3b664..c4778a5 100644 --- a/lib/util/common.dart +++ b/lib/util/common.dart @@ -73,4 +73,10 @@ mixin Common { return _passwordRegExp.hasMatch(password); } + /// Calculates week number from a date as per https://en.wikipedia.org/wiki/ISO_week_date#Calculation + int weekNumber(DateTime date) { + int dayOfYear = int.parse(DateFormat("D").format(date)); + return ((dayOfYear - date.weekday + 10) / 7).floor(); + } + } \ No newline at end of file diff --git a/lib/view/exercise_add_by_plan_page.dart b/lib/view/exercise_add_by_plan_page.dart index 90ca6b9..3f927a9 100644 --- a/lib/view/exercise_add_by_plan_page.dart +++ b/lib/view/exercise_add_by_plan_page.dart @@ -3,7 +3,7 @@ import 'dart:collection'; import 'package:aitrainer_app/bloc/exercise_add_by_plan_bloc.dart'; import 'package:aitrainer_app/bloc/exercise_by_plan/exercise_by_plan_bloc.dart'; import 'package:aitrainer_app/localization/app_language.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/splash.dart'; @@ -25,7 +25,7 @@ class _ExerciseAddByPlanPage extends State with Trans { // ignore: close_sinks final ExerciseByPlanBloc bloc = arguments['blocExerciseByPlan']; final int customerId = arguments['customerId']; - final WorkoutTree workoutTree = arguments['workoutTree']; + final WorkoutMenuTree workoutTree = arguments['workoutTree']; final ExerciseRepository exerciseRepository = ExerciseRepository(); setContext(context); @@ -136,38 +136,15 @@ class _ExerciseAddByPlanPage extends State with Trans { crossAxisAlignment: CrossAxisAlignment.start, children: [ Divider(color: Colors.transparent,), - Text("Execute the " + (i+1).toString() + ". set!", + Text(t("Execute the") + " " + (i+1).toString() + t(". set!"), style: TextStyle(),), - TextFieldBlocBuilder( - readOnly: exerciseBloc.step != i+1, - textFieldBloc: exerciseBloc.quantity1Field, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: Colors.deepOrange, - fontWeight: FontWeight.bold), - inputFormatters: [ - WhitelistingTextInputFormatter(RegExp(r"[\d.]")) - ], - decoration: InputDecoration( - fillColor: Colors.white, - filled: false, - hintStyle: TextStyle( - fontSize: 12, color: Colors.black54, fontWeight: FontWeight.w100), - hintText: t("The number of the exercise"), - labelStyle: TextStyle(fontSize: 12, color: Colors.deepOrange, fontWeight: FontWeight.normal), - labelText: t("Please repeat with ") + exerciseBloc.unitQuantity1Field.value + " " + - exerciseBloc.exerciseRepository.exerciseType.unitQuantityUnit + " " + - exerciseBloc.exercisePlanRepository.actualPlanDetail.repeats.toString() + " times!", - ), - ), TextFieldBlocBuilder( readOnly: exerciseBloc.step != i+1, textFieldBloc: exerciseBloc.unitQuantity1Field, textAlign: TextAlign.center, style: TextStyle( - fontSize: 12, + fontSize: 18, color: Colors.black54, fontWeight: FontWeight.bold), inputFormatters: [ @@ -178,11 +155,35 @@ class _ExerciseAddByPlanPage extends State with Trans { fillColor: Colors.white, filled: false, hintStyle: TextStyle( - fontSize: 12, color: Colors.black54, fontWeight: FontWeight.w100), - labelStyle: TextStyle(fontSize: 12, color: Colors.deepOrange, fontWeight: FontWeight.normal), + fontSize: 14, color: Colors.black54, fontWeight: FontWeight.w100), + labelStyle: TextStyle(fontSize: 14, color: Colors.deepOrange, fontWeight: FontWeight.normal), labelText: exerciseBloc.exerciseRepository.exerciseType.unitQuantityUnit, ), ), + TextFieldBlocBuilder( + readOnly: exerciseBloc.step != i+1, + textFieldBloc: exerciseBloc.quantity1Field, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Colors.deepOrange, + fontWeight: FontWeight.bold), + inputFormatters: [ + WhitelistingTextInputFormatter(RegExp(r"[\d.]")) + ], + + decoration: InputDecoration( + fillColor: Colors.white, + filled: false, + hintStyle: TextStyle( + fontSize: 14, color: Colors.black54, fontWeight: FontWeight.w100), + hintText: t("The number of the exercise"), + labelStyle: TextStyle(fontSize: 14, color: Colors.deepOrange, fontWeight: FontWeight.normal), + labelText: t("Please repeat with") + " "+ exerciseBloc.unitQuantity1Field.value + " " + + exerciseBloc.exerciseRepository.exerciseType.unitQuantityUnit + " " + + exerciseBloc.exercisePlanRepository.getActualPlanDetail().repeats.toString() + " " + t("times!"), + ), + ), RaisedButton( padding: EdgeInsets.all(0), diff --git a/lib/view/exercise_execute_by_plan_page.dart b/lib/view/exercise_execute_by_plan_page.dart index d900587..983d86b 100644 --- a/lib/view/exercise_execute_by_plan_page.dart +++ b/lib/view/exercise_execute_by_plan_page.dart @@ -1,23 +1,25 @@ import 'dart:collection'; - import 'package:aitrainer_app/bloc/exercise_by_plan/exercise_by_plan_bloc.dart'; import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/model/cache.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; +import 'package:aitrainer_app/treeview/tree_view.dart'; +import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_common.dart'; +import 'package:aitrainer_app/widgets/bottom_nav.dart'; +import 'package:aitrainer_app/widgets/exercise_type_widget.dart'; import 'package:aitrainer_app/widgets/splash.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_treeview/tree_view.dart'; class ExerciseByPlanPage extends StatefulWidget { @override _ExerciseByPlanPage createState() => _ExerciseByPlanPage(); } -class _ExerciseByPlanPage extends State { +class _ExerciseByPlanPage extends State with Trans { final GlobalKey _scaffoldKey = new GlobalKey(); // ignore: close_sinks ExerciseByPlanBloc bloc; @@ -38,21 +40,24 @@ class _ExerciseByPlanPage extends State { final int customerId = arguments['customerId']; bloc = BlocProvider.of(context); bloc.customerId = customerId; + setContext(context); return Scaffold( - key: _scaffoldKey, - appBar: AppBarCommonNav(), - body: Container( - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - image: DecorationImage( - image: customerId == Cache().userLoggedIn.customerId ? AssetImage('asset/image/WT_light_background.png'): - AssetImage('asset/image/WT_menu_dark.png'), - fit: BoxFit.cover, - alignment: Alignment.center, - ), + key: _scaffoldKey, + appBar: AppBarCommonNav(), + body: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: customerId == Cache().userLoggedIn.customerId + ? AssetImage('asset/image/WT_light_background.png') + : AssetImage('asset/image/WT_menu_dark.png'), + fit: BoxFit.cover, + alignment: Alignment.center, ), - child: BlocConsumer(listener: (context, state) { + ), + child: BlocConsumer( + listener: (context, state) { if (state is ExerciseByPlanError) { Scaffold.of(context).showSnackBar(SnackBar( content: Text( @@ -64,124 +69,147 @@ class _ExerciseByPlanPage extends State { LoadingDialog(); } }, - // ignore: missing_return - builder: (context, state) { + + builder: (context, state) { if (state is ExerciseByPlanStateInitial || state is ExerciseByPlanLoading) { return Container(); } else if (state is ExerciseByPlanReady) { return exerciseWidget(bloc); - } else if (state is ExerciseByPlanError) { + } else { return exerciseWidget(bloc); } - }))); + })), + bottomNavigationBar: BottomNavigator(bottomNavIndex: 2), + ); } Widget exerciseWidget(ExerciseByPlanBloc bloc) { - final LinkedHashMap args = LinkedHashMap(); - TreeViewController _treeViewController = TreeViewController(children: nodeExercisePlan(bloc)); - - TreeViewTheme _treeViewTheme = TreeViewTheme( - expanderTheme: ExpanderThemeData( - type: ExpanderType.plusMinus, - modifier: ExpanderModifier.circleOutlined, - position: ExpanderPosition.start, - color: Colors.black26, - size: 10, - ), - labelStyle: TextStyle(fontSize: 14, letterSpacing: 0, color: Colors.blue.shade800), - parentLabelStyle: TextStyle( - fontSize: 14, - letterSpacing: 0.3, - fontWeight: FontWeight.w800, - color: Colors.orange.shade600, - ), - iconTheme: IconThemeData( - size: 20, - color: Colors.blue.shade800, - ), - colorScheme: bloc.customerId == Cache().userLoggedIn.customerId ? ColorScheme.light(background: Colors.transparent) : ColorScheme.dark(background: Colors.transparent), - ); - - return Scaffold( - backgroundColor: Colors.transparent, - body: TreeView( - controller: _treeViewController, - allowParentSelect: false, - supportParentDoubleTap: false, - //onExpansionChanged: _expandNodeHandler, - onNodeTap: (key) { - /* Node node = _treeViewController.getNode(key); - WorkoutTree workoutTree = node.data as WorkoutTree; - bloc.exercisePlanRepository.setActualPlanDetail(workoutTree.exerciseType); - print("change node " + node.label + " key " + key); - bloc.add(ExercisePlanUpdate(workoutTree: workoutTree)); - Navigator.of(context).pushNamed("exercisePlanDetailAdd", arguments: bloc); */ - - Node node = _treeViewController.getNode(key); - WorkoutTree workoutTree = node.data as WorkoutTree; - args['blocExerciseByPlan'] = bloc; - args['customerId'] = bloc.customerId; - args['workoutTree'] = workoutTree; - Navigator.of(context).pushNamed("exerciseAddByPlanPage", arguments: args); - }, - - theme: _treeViewTheme, - ), - //bottomNavigationBar: BottomNavigator(bottomNavIndex: 2), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + return TreeView( + startExpanded: false, + children: nodeExercisePlan(bloc), ); } - List nodeExercisePlan(ExerciseByPlanBloc bloc) { - List nodes = List(); - Node actualNode; + List nodeExercisePlan(ExerciseByPlanBloc bloc) { + List exerciseTypes = List(); bool isEnglish = AppLanguage().appLocal == Locale("en"); + Card explanation = Card( + color: Colors.white38, + child: Container( + padding: EdgeInsets.only(left: 10, right: 5, top: 12, bottom: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + Icon( + Icons.info, + color: Colors.orangeAccent, + ), + Text(" "), + Flexible( + child: + Text( + t("Execute your active Exercise Plan!"), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + + ], + ), + Divider( + color: Colors.transparent, + ), + Text( + t("Select the muscle type and tap on the exercise. One the next page enter the weight and repeat."), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal), + ), + ], + ))); + + exerciseTypes.add(explanation); + bloc.menuTreeRepository.sortedTree.forEach((name, list) { - List listWorkoutItem = list; - List listExerciseTypePerMuscle = List(); - NodeIcon icon; - listWorkoutItem.forEach((element) { - - WorkoutTree treeItem = element; - if ( treeItem.selected ) { - icon = - treeItem.executed == false ? NodeIcon(codePoint: Icons.bubble_chart.codePoint, color: "blueAccent") : - NodeIcon(codePoint: Icons.check_box.codePoint, color: "green"); - - String exerciseLabel = isEnglish - ? treeItem.name - : treeItem.exerciseType == null ? treeItem.name : treeItem.exerciseType.nameTranslation; - - List> planDetailList = List>(); - String planDetail = bloc.exercisePlanRepository.getPlanDetail(treeItem.exerciseTypeId); - - if (planDetail.length > 0) { - exerciseLabel += " (" + planDetail + ")"; - } - - actualNode = Node( - label: exerciseLabel, - key: treeItem.id.toString(), - data: treeItem, - expanded: planDetailList.length > 0 ? true : false, - children: [], - icon: icon); - listExerciseTypePerMuscle.add(actualNode); - } - }); - - if (name != null) { - actualNode = Node( - label: name, - key: name, - expanded: true, - children: listExerciseTypePerMuscle, - icon: NodeIcon(codePoint: Icons.perm_identity.codePoint, color: "orange")); - nodes.add(actualNode); - } + exerciseTypes.add( + Container( + margin: const EdgeInsets.only(left: 4.0), + child: TreeViewChild( + startExpanded: true, + parent: ExerciseTypeWidget(exerciseTypeName: name), //_getExerciseWidget(exerciseTypeName: name), + children: _getChildList(list, bloc), + ))); }); - return nodes; + return exerciseTypes; + } + + List _getChildList(List listWorkoutTree, ExerciseByPlanBloc bloc) { + List list = List(); + listWorkoutTree.forEach((element) { + + if ( element.selected) { + list.add( + TreeViewChild( + startExpanded: false, + parent: + Card( + margin: EdgeInsets.only(left: 10, top: 5), + color: Colors.white54, + child: Container( + padding: const EdgeInsets.only(left: 5, top: 0, right: 5, bottom: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + icon: element.executed ? Icon(Icons.check_box, color: Colors.green): + Icon(Icons.indeterminate_check_box, color: Colors.blue.shade800,), + onPressed: () => { + addExerciseByPlanEvent(bloc, element) + }, + ), + SizedBox(width: 20), + Flexible( + child: + InkWell( + child: + Text( + element.name, + textAlign: TextAlign.start, + style: TextStyle(fontSize: 12, color: Colors.black), + ), + onTap: () => { + addExerciseByPlanEvent(bloc, element) + }, + ), + + ), + IconButton( + + padding: EdgeInsets.all(0), + icon: Icon(Icons.description, color: Colors.black12,), + onPressed: () { + + }, + ), + + ]), + ) + ), + children: [ + ] + ) + ); + } + }); + return list; + } + + void addExerciseByPlanEvent(ExerciseByPlanBloc bloc, WorkoutMenuTree workoutTree) { + LinkedHashMap args = LinkedHashMap(); + args['blocExerciseByPlan'] = bloc; + args['customerId'] = bloc.customerId; + args['workoutTree'] = workoutTree; + Navigator.of(context).pushNamed("exerciseAddByPlanPage", arguments: args); } } diff --git a/lib/view/exercise_log_page.dart b/lib/view/exercise_log_page.dart index b2c9c81..816884e 100644 --- a/lib/view/exercise_log_page.dart +++ b/lib/view/exercise_log_page.dart @@ -4,7 +4,6 @@ import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; -import 'package:aitrainer_app/repository/customer_repository.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_common.dart'; @@ -21,7 +20,6 @@ class _ExerciseLogPage extends State with Trans { Widget build(BuildContext context) { LinkedHashMap arguments = ModalRoute.of(context).settings.arguments; final ExerciseRepository exerciseRepository = arguments['exerciseRepository']; - final CustomerRepository customerRepository = arguments['customerRepository']; final int customerId = arguments['customerId']; setContext(context); diff --git a/lib/view/exercise_plan_detail_add_page.dart b/lib/view/exercise_plan_custom_detail_add_page.dart similarity index 94% rename from lib/view/exercise_plan_detail_add_page.dart rename to lib/view/exercise_plan_custom_detail_add_page.dart index d17668c..80f7104 100644 --- a/lib/view/exercise_plan_detail_add_page.dart +++ b/lib/view/exercise_plan_custom_detail_add_page.dart @@ -2,6 +2,7 @@ import 'package:aitrainer_app/bloc/exercise_plan/exercise_plan_bloc.dart'; import 'package:aitrainer_app/bloc/exercise_plan_custom_form.dart'; import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/localization/app_localization.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_common.dart'; @@ -34,8 +35,8 @@ class _ExercisePlanDetailAddPage extends State with T String exerciseName = ""; if (bloc != null) { exerciseName = AppLanguage().appLocal == Locale("en") - ? bloc.exercisePlanRepository.actualPlanDetail.exerciseType.name - : bloc.exercisePlanRepository.actualPlanDetail.exerciseType.nameTranslation; + ? bloc.exercisePlanRepository.getActualPlanDetail().exerciseType.name + : bloc.exercisePlanRepository.getActualPlanDetail().exerciseType.nameTranslation; } return Form( @@ -118,9 +119,10 @@ class _ExercisePlanDetailAddPage extends State with T color: Colors.red.shade300, focusColor: Colors.white, onPressed: () => { - print("Remove " + bloc.exercisePlanRepository.actualPlanDetail.exerciseType.name), + print("Remove " + bloc.exercisePlanRepository.getActualPlanDetail().exerciseType.name), + bloc.exercisePlanRepository.getActualPlanDetail().change = ExercisePlanDetailChange.delete, planBloc.add(ExercisePlanRemoveExercise( - exercisePlanDetail: bloc.exercisePlanRepository.actualPlanDetail)), + exercisePlanDetail: bloc.exercisePlanRepository.getActualPlanDetail() )), Navigator.of(context).pop(), }, child: Text(t( diff --git a/lib/view/exercise_plan_custom_page.dart b/lib/view/exercise_plan_custom_page.dart index 751bbb5..e94e1ce 100644 --- a/lib/view/exercise_plan_custom_page.dart +++ b/lib/view/exercise_plan_custom_page.dart @@ -1,24 +1,26 @@ import 'dart:collection'; import 'package:aitrainer_app/bloc/exercise_plan/exercise_plan_bloc.dart'; -import 'package:aitrainer_app/localization/app_language.dart'; + import 'package:aitrainer_app/model/cache.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; -import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; +import 'package:aitrainer_app/treeview/tree_view.dart'; +import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_common.dart'; +import 'package:aitrainer_app/widgets/bottom_nav.dart'; +import 'package:aitrainer_app/widgets/exercise_type_widget.dart'; import 'package:aitrainer_app/widgets/splash.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_treeview/tree_view.dart'; class ExercisePlanCustomPage extends StatefulWidget { @override _ExercisePlanCustomPage createState() => _ExercisePlanCustomPage(); } -class _ExercisePlanCustomPage extends State { +class _ExercisePlanCustomPage extends State with Trans { final GlobalKey _scaffoldKey = new GlobalKey(); // ignore: close_sinks ExercisePlanBloc bloc; @@ -36,10 +38,10 @@ class _ExercisePlanCustomPage extends State { @override Widget build(BuildContext context) { LinkedHashMap arguments = ModalRoute.of(context).settings.arguments; - final ExerciseRepository exerciseRepository = arguments['exerciseRepository']; final int customerId = arguments['customerId']; bloc = BlocProvider.of(context); bloc.customerId = customerId; + setContext(context); return Scaffold( key: _scaffoldKey, @@ -54,135 +56,158 @@ class _ExercisePlanCustomPage extends State { alignment: Alignment.center, ), ), - child: BlocConsumer(listener: (context, state) { - if (state is ExercisePlanError) { - Scaffold.of(context).showSnackBar(SnackBar( - content: Text( - state.message, - ), - backgroundColor: Colors.orange, - )); - } else if (state is ExercisePlanLoading) { - LoadingDialog(); - } - }, + child: BlocConsumer( + listener: (context, state) { + if (state is ExercisePlanError) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + state.message, + ), + backgroundColor: Colors.orange, + )); + } else if (state is ExercisePlanLoading) { + LoadingDialog(); + } + }, // ignore: missing_return - builder: (context, state) { - if (state is ExercisePlanInitial) { + builder: (context, state) { + if (state is ExercisePlanReady) { + return exerciseWidget(bloc); + } return Container(); - } else if (state is ExercisePlanReady) { - return exerciseWidget(bloc); - } else if (state is ExercisePlanError) { - return exerciseWidget(bloc); - } else if (state is ExercisePlanLoading) { - return Container(); - } - }))); + + } + ) + ), + bottomNavigationBar: BottomNavigator(bottomNavIndex: 2), + ); } Widget exerciseWidget(ExercisePlanBloc bloc) { - TreeViewController _treeViewController = TreeViewController(children: nodeExercisePlan(bloc)); - - TreeViewTheme _treeViewTheme = TreeViewTheme( - expanderTheme: ExpanderThemeData( - type: ExpanderType.plusMinus, - modifier: ExpanderModifier.circleOutlined, - position: ExpanderPosition.start, - color: Colors.black26, - size: 10, - ), - labelStyle: TextStyle(fontSize: 14, letterSpacing: 0, color: Colors.blue.shade800), - parentLabelStyle: TextStyle( - fontSize: 14, - letterSpacing: 0.3, - fontWeight: FontWeight.w800, - color: Colors.orange.shade600, - ), - iconTheme: IconThemeData( - size: 20, - color: Colors.blue.shade800, - ), - colorScheme: bloc.customerId == Cache().userLoggedIn.customerId ? ColorScheme.light(background: Colors.transparent) : ColorScheme.dark(background: Colors.transparent), - ); - - return Scaffold( - backgroundColor: Colors.transparent, - body: TreeView( - controller: _treeViewController, - allowParentSelect: true, - supportParentDoubleTap: false, - //onExpansionChanged: _expandNodeHandler, - onNodeTap: (key) { - Node node = _treeViewController.getNode(key); - WorkoutTree workoutTree = node.data as WorkoutTree; - bloc.exercisePlanRepository.setActualPlanDetail(workoutTree.exerciseType); - print("change node " + node.label + " key " + key); - bloc.add(ExercisePlanUpdate(workoutTree: workoutTree)); - Navigator.of(context).pushNamed("exercisePlanDetailAdd", arguments: bloc); - }, - theme: _treeViewTheme, - ), - floatingActionButton: FloatingActionButton( - backgroundColor: Colors.blueAccent, - child: Icon(Icons.save_alt), - onPressed: () => { - bloc.add(ExercisePlanSave()), - if (bloc.exercisePlanRepository.getExercisePlanDetailSize() > 0) { - Navigator.of(context).pop() - } - } - ), - //bottomNavigationBar: BottomNavigator(bottomNavIndex: 2), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + return TreeView( + startExpanded: false, + children: _getTreeChildren(bloc), ); } - List nodeExercisePlan(ExercisePlanBloc bloc) { - List nodes = List(); - Node actualNode; - bool isEnglish = AppLanguage().appLocal == Locale("en"); + List _getTreeChildren(ExercisePlanBloc bloc) { + List exerciseTypes = List(); - bloc.menuTreeRepository.sortedTree.forEach((name, list) { - List listWorkoutItem = list; - List listExerciseTypePerMuscle = List(); - NodeIcon icon; - listWorkoutItem.forEach((element) { - WorkoutTree treeItem = element; - icon = - treeItem.selected == true ? NodeIcon(codePoint: Icons.bubble_chart.codePoint, color: "blueAccent") : null; + Card explanation = Card( + color: Colors.white60, + child: Container( + padding: EdgeInsets.only(left: 10, right: 5, top: 12, bottom: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + Icon( + Icons.info, + color: Colors.orangeAccent, + ), + Text(" "), + Text( + t("Custom Exercise Plan"), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ], + ), + Divider( + color: Colors.transparent, + ), + Text( + t("Select manually the exercises what you would like to have in your plan. At the end don't forget to save."), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal), + ), - String exerciseLabel = isEnglish - ? treeItem.name - : treeItem.exerciseType == null ? treeItem.name : treeItem.exerciseType.nameTranslation; + ], + ) + ) + ); + exerciseTypes.add(explanation); - List> planDetailList = List>(); - String planDetail = bloc.exercisePlanRepository.getPlanDetail(treeItem.exerciseTypeId); - - if (planDetail.length > 0) { - exerciseLabel += " (" + planDetail +")"; - } - - actualNode = Node( - label: exerciseLabel, - key: treeItem.id.toString(), - data: treeItem, - expanded: planDetailList.length > 0 ? true : false, - children: [], - icon: icon); - listExerciseTypePerMuscle.add(actualNode); - }); - //print ("Node name " + name); - if (name != null) { - actualNode = Node( - label: name, // AppLocalizations.of(context).translate(name), - key: name, - expanded: true, - children: listExerciseTypePerMuscle, - icon: NodeIcon(codePoint: Icons.perm_identity.codePoint, color: "orange")); - nodes.add(actualNode); - } + bloc.menuTreeRepository.sortedTree.forEach((name, list) { + exerciseTypes.add(Container( + margin: const EdgeInsets.only(left: 4.0), + child: TreeViewChild( + startExpanded: true, + parent: ExerciseTypeWidget(exerciseTypeName: name), + children: _getChildList(list, bloc), + ))); }); - return nodes; + return exerciseTypes; + } + + List _getChildList(List listWorkoutTree, ExercisePlanBloc bloc) { + List list = List(); + listWorkoutTree.forEach((element) { + + list.add( + TreeViewChild( + startExpanded: false, + parent: + Card( + margin: EdgeInsets.only(left: 10, top: 5), + color: Colors.white54, + child: Container( + padding: const EdgeInsets.only(left: 5, top: 0, right: 5, bottom: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + icon: element.selected ? Icon(Icons.check, color: Colors.greenAccent.shade700,) : Icon(Icons.add, color: Colors.blue.shade400,), + onPressed: () => { + bloc.add(ExercisePlanUpdateUI(workoutTree: element)), + Navigator.of(context).pushNamed("exercisePlanDetailAdd", arguments: bloc), + }, + ), + SizedBox(width: 20), + Flexible( + child: + InkWell( + child: + Text( + element.name, + textAlign: TextAlign.start, + style: TextStyle(fontSize: 12, color: Colors.black), + ), + onTap: () => { + bloc.add(ExercisePlanUpdateUI(workoutTree: element)), + Navigator.of(context).pushNamed("exercisePlanDetailAdd", arguments: bloc), + }, + ), + ), + InkWell( + child: + element.selected && bloc.exercisePlanRepository.exercisePlanDetails[element.exerciseTypeId].change != null ? + Text(bloc.exercisePlanRepository.exercisePlanDetails[element.exerciseTypeId].repeats.toString() + + " x " + bloc.exercisePlanRepository.exercisePlanDetails[element.exerciseTypeId].weightEquation + + " " + element.exerciseType.unitQuantityUnit, style: TextStyle(fontSize: 9, color: Colors.green),) : Text(""), + onTap: () => { + bloc.add(ExercisePlanUpdateUI(workoutTree: element)), + Navigator.of(context).pushNamed("exercisePlanDetailAdd", arguments: bloc), + }, + ), + IconButton( + + padding: EdgeInsets.all(0), + icon: Icon(Icons.description, color: Colors.black12,), + onPressed: () { + + }, + ), + + ]), + ) + ), + children: [ + ] + ) + ); + + }); + return list; } } diff --git a/lib/view/mydevelopment_muscle_page.dart b/lib/view/mydevelopment_muscle_page.dart new file mode 100644 index 0000000..80268ef --- /dev/null +++ b/lib/view/mydevelopment_muscle_page.dart @@ -0,0 +1,322 @@ +import 'dart:collection'; +import 'package:aitrainer_app/util/trans.dart'; +import 'package:aitrainer_app/widgets/exercise_type_widget.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:aitrainer_app/util/common.dart'; +import 'package:aitrainer_app/bloc/development_by_muscle/development_by_muscle_bloc.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; +import 'package:aitrainer_app/treeview/tree_view.dart'; +import 'package:aitrainer_app/widgets/app_bar_common.dart'; +import 'package:aitrainer_app/widgets/bottom_nav.dart'; +import 'package:aitrainer_app/widgets/splash.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; + +class MyDevelopmentMusclePage extends StatefulWidget { + @override + _MyDevelopmentMuscleState createState() => _MyDevelopmentMuscleState(); +} + +class _MyDevelopmentMuscleState extends State with Common, Trans { + // ignore: close_sinks + DevelopmentByMuscleBloc bloc; + double cWidth; + + @override + void initState() { + super.initState(); + + /// We require the initializers to run after the loading screen is rendered + SchedulerBinding.instance.addPostFrameCallback((_) { + BlocProvider.of(context).add(DevelopmentByMuscleLoad()); + }); + } + + @override + Widget build(BuildContext context) { + bloc = BlocProvider.of(context); + cWidth = mediaSizeWidth(context); + setContext(context); + + return Scaffold( + appBar: AppBarCommonNav(), + body: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_menu_dark.png'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: BlocConsumer( + listener: (context, state) { + if (state is DevelopmentByMuscleErrorState) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + state.message, + ), + backgroundColor: Colors.orange, + )); + } else if (state is DevelopmentByMuscleLoadingState) { + LoadingDialog(); + } + }, + builder: (context, state) { + if (state is DevelopmentByMuscleStateInitial) { + return Container(); + } else { + return TreeView( + startExpanded: false, + children: _getTreeChildren(bloc.workoutTreeRepository.sortedTree, bloc), + ); + } + ; + }, + ), + ), + bottomNavigationBar: BottomNavigator(bottomNavIndex: 1), + ); + } + + List _getTreeChildren(SplayTreeMap tree, DevelopmentByMuscleBloc bloc) { + List exerciseTypes = List(); + + Card explanation = Card( + color: Colors.white60, + child: Container( + padding: EdgeInsets.only(left: 10, right: 5, top: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + Icon( + Icons.info, + color: Colors.orangeAccent, + ), + Text(" "), + Text( + t("My Development By Muscle"), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ], + ), + Divider( + color: Colors.transparent, + ), + Text( + t("Here you see you development in the last period."), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ChoiceChip( + avatar: Icon(Icons.bubble_chart,), + label: Text(t('Sum Of Mass')), + + labelStyle: TextStyle(fontSize: 9,color: Colors.black), + selectedColor: Colors.lightBlueAccent, + selected: bloc.diagramType == DiagramType.sumMass, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDiagramTypeChange(diagramType: DiagramType.sumMass)) + }, + ), + ChoiceChip( + avatar: Icon(Icons.accessibility_new), + label: Text(t('One Max Rep')), + labelStyle: TextStyle(fontSize: 9,color: Colors.black), + selectedColor: Colors.lightBlueAccent, + selected: bloc.diagramType == DiagramType.oneRepMax, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDiagramTypeChange(diagramType: DiagramType.oneRepMax)) + }, + ), + ChoiceChip( + avatar: Icon(Icons.perm_device_information), + label: Text(t('Percent')), + labelStyle: TextStyle(fontSize: 9,color: Colors.black), + selectedColor: Colors.lightBlueAccent, + selected: bloc.diagramType == DiagramType.percent, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDiagramTypeChange(diagramType: DiagramType.percent)) + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ChoiceChip( + avatar: Icon(Icons.timer), + label: Text(t('Detailed')), + labelStyle: TextStyle(fontSize: 9, color: Colors.black), + disabledColor: Colors.black26, + selectedColor: Colors.greenAccent, + selected: bloc.dateRate == DateRate.daily, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDateRateChange(dateRate: DateRate.daily)) + }, + + ), + ChoiceChip( + avatar: Icon(Icons.timer), + label: Text(t('Weekly')), + labelStyle: TextStyle(fontSize: 9, color: Colors.black), + selectedColor: Colors.greenAccent, + disabledColor: Colors.white12, + tooltip: "Heti bontás", + selected: bloc.dateRate == DateRate.weekly, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDateRateChange(dateRate: DateRate.weekly)) + }, + + ), + ChoiceChip( + avatar: Icon(Icons.timer), + label: Text(t('Monthly')), + labelStyle: TextStyle(fontSize: 9, color: Colors.black), + selectedColor: Colors.greenAccent, + disabledColor: Colors.black26, + selected: bloc.dateRate == DateRate.monthly, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDateRateChange(dateRate: DateRate.monthly)) + }, + + ), + ChoiceChip( + avatar: Icon(Icons.timer), + label: Text(t('Yearly')), + labelStyle: TextStyle(fontSize: 9, color: Colors.black), + selectedColor: Colors.greenAccent, + disabledColor: Colors.white70, + selected: bloc.dateRate == DateRate.yearly, + onSelected: (value) => { + bloc.add(DevelopmentByMuscleDateRateChange(dateRate: DateRate.yearly)) + }, + ), + ], + ) + ], + ) + ) + ); + exerciseTypes.add(explanation); + + tree.forEach((name, list) { + List listWorkoutMenuTree = list; + exerciseTypes.add(Container( + margin: const EdgeInsets.only(left: 4.0), + child: TreeViewChild( + startExpanded: true, + parent: _getExerciseWidget(exerciseTypeName: name), + children: _getChildList(list, bloc), + ))); + }); + + return exerciseTypes; + } + + Widget _getExerciseWidget({@required String exerciseTypeName, List list}) { + return ExerciseTypeWidget(exerciseTypeName: exerciseTypeName); + } + + List _getChildList(List listWorkoutTree, DevelopmentByMuscleBloc bloc) { + List list = List(); + listWorkoutTree.forEach((element) { + bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null); + String unit = " kg"; + if ( bloc.diagramType == DiagramType.percent) { + unit = " %"; + } + list.add(SizedBox( + width: cWidth * 0.85, + height: hasNoData ? 0 : 200, + child: Container( + padding: const EdgeInsets.only(left:5, top:5, right:5, bottom: 5), + color: Colors.white70, + child: Column(mainAxisSize: MainAxisSize.min, children: [ + hasNoData + ? Container() + : Text( + element.exerciseType.nameTranslation, + style: TextStyle(color: Colors.deepOrange), + ), + hasNoData + ? Container( + //child: Text("no data for " + element.exerciseType.nameTranslation), + ) + : Expanded( + //fit: FlexFit.loose, + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: Colors.white70, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + return BarTooltipItem( + rod.y.toStringAsFixed(0) + unit, + TextStyle(color: Colors.black54, fontSize: 12, fontWeight: FontWeight.bold), + ); + }), + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: SideTitles( + showTitles: true, + textStyle: TextStyle(fontSize: 8, color: Colors.blueGrey), + getTitles: (double value) { + var date = new DateTime.fromMillisecondsSinceEpoch(value.toInt()); + //String strDate = DateFormat('MM.dd.', AppLanguage().appLocal.toString()).format(date); + String strDate = bloc.getDatePart(date); + return strDate; + }, + ), + leftTitles: SideTitles( + showTitles: true, + textStyle: TextStyle(fontSize: 8, color: Colors.blueGrey), + interval: bloc.listChartData[element.exerciseTypeId] == null + ? 100 + : bloc.listChartData[element.exerciseTypeId].interval, + margin: 10, + getTitles: (double value) { + return value.toStringAsFixed(0) + unit; + })), + borderData: FlBorderData( + show: false, + ), + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => + value % bloc.listChartData[element.exerciseTypeId].gridInterval == 0, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.black26, + strokeWidth: 0.5, + ); + }, + ), + groupsSpace: 2, + barGroups: bloc.listChartData[element.exerciseTypeId] == null + ? [] + : bloc.listChartData[element.exerciseTypeId].data, + ), + swapAnimationDuration: Duration(milliseconds: 1200), + ), + ) + ]), + ), + )); + }); + + return list; + } +} + + diff --git a/lib/view/mydevelopment_page.dart b/lib/view/mydevelopment_page.dart index 08f20ef..df425d8 100644 --- a/lib/view/mydevelopment_page.dart +++ b/lib/view/mydevelopment_page.dart @@ -8,6 +8,7 @@ import 'package:aitrainer_app/widgets/app_bar_common.dart'; import 'package:aitrainer_app/widgets/bottom_nav.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class MyDevelopmentPage extends StatefulWidget { @override @@ -84,10 +85,10 @@ class _MyDevelopmentPage extends State with Trans { fit: StackFit.passthrough, overflow: Overflow.clip, children: [ - Image.asset('asset/image/lock.png', + /*Image.asset('asset/image/lock.png', height: 40, width: 40, - ), + ),*/ FlatButton( padding: EdgeInsets.all(20), textColor: Colors.white, @@ -95,6 +96,8 @@ class _MyDevelopmentPage extends State with Trans { focusColor: Colors.blueAccent, onPressed: () => { + Navigator.of(context).pushNamed('mydevelopmentMusclePage', + arguments: args) }, child: Text(t("Development Of Muscles"), style: TextStyle(fontSize: 18),) diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index 9f17deb..c4e3c60 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -151,7 +151,7 @@ class _AppBarNav extends State with SingleTickerProviderStateMixin { lineHeight: 14.0, percent: percent, center: Text( - (percent * 100).toStringAsFixed(0) + "% finished", + (percent * 100).toStringAsFixed(0) + "% " + AppLocalizations.of(context).translate("finished"), style: new TextStyle(fontSize: 12.0), ), trailing: Icon(percent > 0.6 ? Icons.mood : Icons.mood_bad, color: colorAnim.value,), diff --git a/lib/widgets/app_bar_common.dart b/lib/widgets/app_bar_common.dart index b769db1..0f791a8 100644 --- a/lib/widgets/app_bar_common.dart +++ b/lib/widgets/app_bar_common.dart @@ -75,7 +75,7 @@ class _AppBarCommonNav extends State with SingleTickerProvider icon: Icon(Icons.arrow_back, color: Colors.white), onPressed: () => { - Navigator.of(context).pop() + Navigator.of(context).pushNamed('home') }, ) ); @@ -119,7 +119,7 @@ class _AppBarCommonNav extends State with SingleTickerProvider lineHeight: 14.0, percent: percent, center: Text( - (percent * 100).toStringAsFixed(0) + "% finished", + (percent * 100).toStringAsFixed(0) + "% " + AppLocalizations.of(context).translate("finished"), style: new TextStyle(fontSize: 12.0), ), trailing: Icon(percent > 0.6 ? Icons.mood : Icons.mood_bad, color: colorAnim.value,), diff --git a/lib/widgets/bottom_nav.dart b/lib/widgets/bottom_nav.dart index 50fad6b..8092b7c 100644 --- a/lib/widgets/bottom_nav.dart +++ b/lib/widgets/bottom_nav.dart @@ -91,12 +91,12 @@ class _NawDrawerWidget extends State { break; case 1: - //Navigator.of(context).pop(); + Navigator.of(context).pop(); Navigator.of(context).pushNamed('myDevelopment'); break; case 2: - //Navigator.of(context).pop(); + Navigator.of(context).pop(); Navigator.of(context).pushNamed('myExercisePlan'); break; diff --git a/lib/widgets/exercise_type_widget.dart b/lib/widgets/exercise_type_widget.dart new file mode 100644 index 0000000..24251b7 --- /dev/null +++ b/lib/widgets/exercise_type_widget.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class ExerciseTypeWidget extends StatelessWidget { + final String exerciseTypeName; + //final DateTime lastModified; + + ExerciseTypeWidget({@required this.exerciseTypeName}); + + @override + Widget build(BuildContext context) { + Widget exerciseTypeNameWidget = Text( + this.exerciseTypeName, + style: TextStyle(fontWeight: FontWeight.w800, color: Colors.blueAccent, backgroundColor: Colors.transparent), + ); + + Icon icon = Icon(Icons.person); + + return Card( + color: Colors.white38, + shadowColor: Colors.black54, + elevation: 0.0, + child: ListTile( + leading: icon, + title: exerciseTypeNameWidget, + //subtitle: lastModifiedWidget, + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 9ee7002..5504093 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -45,9 +45,6 @@ class _HomePageState extends State { SettingsBloc settingsBloc = BlocProvider.of(context); settingsBloc.context = context; sessionBloc.add(SessionStart(settingsBloc: settingsBloc)); - //String lang = AppLanguage().appLocal.languageCode; - //print (" -- Loading delayed lang $lang"); - //settingsBloc.add(SettingsChangeLanguage(language: lang)); } } }); diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index f8819a4..b5319a2 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; import 'package:aitrainer_app/localization/app_localization.dart'; import 'package:aitrainer_app/model/cache.dart'; -import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; @@ -31,7 +31,7 @@ class MenuPageWidget extends StatelessWidget { menuBloc.menuTreeRepository .getBranch(menuBloc.parent) .forEach((treeName, value) { - WorkoutTree workoutTree = value as WorkoutTree; + WorkoutMenuTree workoutTree = value as WorkoutMenuTree; _columnChildren.add(Container( padding: EdgeInsets.only(top: 16.0), child: Center( @@ -83,7 +83,7 @@ class MenuPageWidget extends StatelessWidget { } void menuClick( - WorkoutTree workoutTree, MenuBloc menuBloc, BuildContext context) { + WorkoutMenuTree workoutTree, MenuBloc menuBloc, BuildContext context) { print("Hi!, Menu clicked " + workoutTree.id.toString()); if (workoutTree.child == false) { menuBloc.add(MenuTreeDown(parent: workoutTree.id)); @@ -107,7 +107,7 @@ class MenuPageWidget extends StatelessWidget { } } - dynamic getShape(WorkoutTree workoutTree) { + dynamic getShape(WorkoutMenuTree workoutTree) { bool base = workoutTree.base; dynamic returnCode = (base == true) ? RoundedRectangleBorder( @@ -117,7 +117,7 @@ class MenuPageWidget extends StatelessWidget { return returnCode; } - dynamic _getButtonImage(WorkoutTree workoutTree) { + dynamic _getButtonImage(WorkoutMenuTree workoutTree) { dynamic image; try { image = Image.asset( diff --git a/pubspec.lock b/pubspec.lock index 7b7c8aa..06df6bd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,6 +225,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" file: dependency: transitive description: @@ -239,6 +246,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.10.11" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.1" flutter: dependency: "direct main" description: flutter @@ -333,14 +347,14 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "0.11.6" + version: "0.12.1" freezed_annotation: dependency: transitive description: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "0.11.0+1" + version: "0.12.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -401,7 +415,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.14" + version: "2.1.18" intl: dependency: "direct dev" description: @@ -429,7 +443,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0" json_rpc_2: dependency: transitive description: @@ -514,6 +528,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" path_provider_linux: dependency: transitive description: @@ -528,6 +556,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+1" pedantic: dependency: transitive description: @@ -541,7 +576,7 @@ packages: name: percent_indicator url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7+3" petitparser: dependency: transitive description: @@ -639,7 +674,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.10" + version: "0.5.12" shared_preferences_linux: dependency: transitive description: @@ -668,6 +703,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2+7" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+1" shelf: dependency: transitive description: @@ -707,7 +749,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.6" + version: "0.9.7+1" source_map_stack_trace: dependency: transitive description: @@ -876,6 +918,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.3" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.3" xdg_directories: dependency: transitive description: @@ -889,7 +938,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.5.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 68c1139..59ca81e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.0+21 +version: 1.1.0+22 environment: sdk: ">=2.7.0 <3.0.0" @@ -34,13 +34,14 @@ dependencies: flutter_facebook_login: ^3.0.0 flutter_bloc: ^6.0.5 equatable: ^1.2.5 - freezed: ^0.11.6 + freezed: ^0.12.1 flutter_form_bloc: ^0.19.0 spider_chart: ^0.1.5 rainbow_color: ^0.1.1 - percent_indicator: ^2.1.6 + percent_indicator: ^2.1.7+3 gradient_bottom_navigation_bar: ^1.0.0+4 flutter_treeview: ^0.6.0+1 + fl_chart: ^0.11.1 mockito: ^4.1.1 @@ -58,7 +59,7 @@ dev_dependencies: http: 0.12.1 intl: 0.16.1 - shared_preferences: ^0.5.10 + shared_preferences: ^0.5.12 flutter_launcher_icons: ^0.8.0 diff --git a/test/exercise_plan_bloc.dart b/test/exercise_plan_bloc.dart new file mode 100644 index 0000000..6ce2be1 --- /dev/null +++ b/test/exercise_plan_bloc.dart @@ -0,0 +1,193 @@ +import 'package:aitrainer_app/bloc/exercise_plan/exercise_plan_bloc.dart'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise_plan.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +import 'package:aitrainer_app/repository/workout_tree_repository.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +main() { + SimExercisePlanRepository _exercisePlanRepository; + ExercisePlanBloc bloc; + + int _customerId; + + Future setUpPlan() async { + final String planName2 = "Test Plan2"; + ExercisePlan plan2 = ExercisePlan(planName2, 101); + _exercisePlanRepository.setCustomerId(101); + _exercisePlanRepository.setExercisePlan(plan2); + + ExercisePlanDetail detail3 = ExercisePlanDetail(3); + detail3.repeats = 23; + detail3.weightEquation = "60"; + detail3.serie = 4; + + _exercisePlanRepository.setActualPlanDetail(detail3); + _exercisePlanRepository.addDetailToPlan(); + + ExercisePlanDetail detail4 = ExercisePlanDetail(4); + detail4.repeats = 12; + detail4.weightEquation = "95"; + detail4.serie = 3; + + _exercisePlanRepository.setActualPlanDetail(detail4); + _exercisePlanRepository.addDetailToPlan(); + await _exercisePlanRepository.saveExercisePlan(); + } + + setUp(() async { + _exercisePlanRepository = SimExercisePlanRepository(); + WorkoutTreeRepository menuTreeRepository = WorkoutTreeRepository(); + bloc = ExercisePlanBloc(menuTreeRepository: menuTreeRepository); + bloc.setExercisePlanRepository(_exercisePlanRepository); + _customerId = 62; + await setUpPlan(); + }); + +/* + group('New Plan', () { + test('Get Data', () async { + }); + + test('Add bloc', () async { + + }); + test('Update bloc', () async { + + }); + test('Delete bloc', () async { + + }); + + });*/ + + group('Existing Plan', () { + test('Get Data', () async { + bloc.customerId = 101; + bloc.add(ExercisePlanLoad()); + + final expectedResponse = [ + ExercisePlanLoading(), + ExercisePlanReady() + ]; + + expectLater( + bloc, + emitsInOrder(expectedResponse), + ).then((_) { + expect(bloc.exercisePlanRepository.newPlan, false); + expect(bloc.exercisePlanRepository.exercisePlan.name, "Test Plan2" ); + expect(bloc.exercisePlanRepository.exercisePlanDetails[4].weightEquation, "95"); + expect(Cache().getMyExercisePlan().name, "Test Plan2"); + expect(Cache().getMyExercisePlanDetails()[4].weightEquation, "95"); + }); + }); + + test('Add bloc', () async { + bloc.customerId = 101; + + bloc.exercisePlanRepository.getLastExercisePlan(); + bloc.exercisePlanRepository.getExercisePlanDetails(); + + ExercisePlanDetail detail4 = ExercisePlanDetail(5); + detail4.repeats = 20; + detail4.weightEquation = "55"; + detail4.serie = 3; + + + + bloc.add(ExercisePlanAddExercise(exercisePlanDetail: detail4)); + + final expectedResponse2 = [ + ExercisePlanLoading(), + ExercisePlanReady() + ]; + + expectLater( + bloc, + emitsInOrder(expectedResponse2), + ).then((_) { + expect(bloc.exercisePlanRepository.newPlan, false); + expect(bloc.exercisePlanRepository.exercisePlan.customerId, 101); + expect(bloc.exercisePlanRepository.exercisePlanDetails.length, 3); + expect(bloc.exercisePlanRepository.exercisePlanDetails[5].repeats, 20); + expect(bloc.exercisePlanRepository.exercisePlanDetails[5].change, ExercisePlanDetailChange.add); + expect(Cache() + .getMyExercisePlan() + .name, "Test Plan2"); + expect(Cache().getMyExercisePlanDetails()[5].weightEquation, "55"); + expect(Cache().getMyExercisePlanDetails()[5].repeats, 20); + }); + }); + test('Update bloc', () async { + bloc.customerId = 101; + + bloc.exercisePlanRepository.getLastExercisePlan(); + bloc.exercisePlanRepository.getExercisePlanDetails(); + bloc.exercisePlanRepository.exercisePlanDetails[3].repeats = 25; + + bloc.add(ExercisePlanAddExercise(exercisePlanDetail: bloc.exercisePlanRepository.exercisePlanDetails[3])); + + final expectedResponse2 = [ + ExercisePlanLoading(), + ExercisePlanReady() + ]; + + expectLater( + bloc, + emitsInOrder(expectedResponse2), + ).then((_) { + expect(bloc.exercisePlanRepository.newPlan, false); + expect(bloc.exercisePlanRepository.exercisePlanDetails[3].repeats, 25); + expect(Cache().getMyExercisePlanDetails()[3].repeats, 25); + }); + + }); + test('Delete bloc', () async { + bloc.customerId = 101; + bloc.exercisePlanRepository.getLastExercisePlan(); + bloc.exercisePlanRepository.getExercisePlanDetails(); + bloc.add(ExercisePlanRemoveExercise(exercisePlanDetail: bloc.exercisePlanRepository.exercisePlanDetails[3])); + + final expectedResponse2 = [ + ExercisePlanLoading(), + ExercisePlanReady() + ]; + + expectLater( + bloc, + emitsInOrder(expectedResponse2), + ).then((_) { + expect(bloc.exercisePlanRepository.newPlan, false); + expect(bloc.exercisePlanRepository.exercisePlanDetails.length, 2); + expect(bloc.exercisePlanRepository.exercisePlanDetails[3], isNull); + expect(Cache().getMyExercisePlanDetails()[3], isNull); + }); + }); + + test('Test Trainee', () async { + /*bloc.customerId = 102; + bloc.exercisePlanRepository.customerId = 102; + + bloc.add(ExercisePlanLoad()); + + final expectedResponse = [ + ExercisePlanLoading(), + ExercisePlanReady() + ]; + + + expectLater( + bloc, + emitsInOrder(expectedResponse), + ).then((_) { + expect(bloc.exercisePlanRepository.newPlan, true); + expect(bloc.exercisePlanRepository.exercisePlanDetails.length, 0); + });*/ + }); + +}); + +} \ No newline at end of file diff --git a/test/exercise_plan_repository_test.dart b/test/exercise_plan_repository_test.dart new file mode 100644 index 0000000..617c37e --- /dev/null +++ b/test/exercise_plan_repository_test.dart @@ -0,0 +1,158 @@ +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise_plan.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; +import 'mocks.dart'; + + +main() { + SimExercisePlanRepository _exercisePlanRepository; + int _customerId; + + Future setUpPlan() async { + + final String planName2 = "Test Plan2"; + ExercisePlan plan2 = ExercisePlan(planName2, 101); + _exercisePlanRepository.setCustomerId(101); + _exercisePlanRepository.setExercisePlan(plan2); + + ExercisePlanDetail detail3 = ExercisePlanDetail(3); + detail3.repeats = 23; + detail3.weightEquation = "60"; + detail3.serie = 4; + + _exercisePlanRepository.setActualPlanDetail(detail3); + _exercisePlanRepository.addDetailToPlan(); + + ExercisePlanDetail detail4 = ExercisePlanDetail(4); + detail4.repeats = 12; + detail4.weightEquation = "95"; + detail4.serie = 3; + + _exercisePlanRepository.setActualPlanDetail(detail4); + _exercisePlanRepository.addDetailToPlan(); + await _exercisePlanRepository.saveExercisePlan(); + } + + setUp(() async { + _exercisePlanRepository = SimExercisePlanRepository(); + _customerId = 62; + await setUpPlan(); + }); + + + + group('New Plan', () { + test('add new plan and plan details and save', () async { + final String planName = "Boss Test Plan"; + ExercisePlan plan = ExercisePlan(planName, _customerId); + _exercisePlanRepository.setExercisePlan(plan); + + ExercisePlanDetail detail1 = ExercisePlanDetail(37); + detail1.repeats = 12; + detail1.weightEquation = "80"; + detail1.serie = 4; + + _exercisePlanRepository.setActualPlanDetail(detail1); + _exercisePlanRepository.addDetailToPlan(); + + ExercisePlanDetail detail2 = ExercisePlanDetail(55); + detail2.repeats = 13; + detail2.weightEquation = "55"; + detail2.serie = 3; + + _exercisePlanRepository.setActualPlanDetail(detail2); + _exercisePlanRepository.addDetailToPlan(); + + await _exercisePlanRepository.saveExercisePlan(); + + expect(_exercisePlanRepository.getExercisePlan().name, planName); + expect(_exercisePlanRepository.getExercisePlan().exercisePlanId > 0, true); + + ExercisePlanDetail detail = + _exercisePlanRepository.getExercisePlanDetailByExerciseId(55); + expect(detail.exercisePlanId, _exercisePlanRepository.getExercisePlan().exercisePlanId); + + + }); + test('save new plan and plan details second', () async { + int customerId = 100; + final String planName = "Boss2 Test Plan"; + ExercisePlan plan = ExercisePlan(planName, customerId); + _exercisePlanRepository.setExercisePlan(plan); + + ExercisePlanDetail detail1 = ExercisePlanDetail(12); + detail1.repeats = 10; + detail1.weightEquation = "40"; + detail1.serie = 5; + + _exercisePlanRepository.setActualPlanDetail(detail1); + _exercisePlanRepository.addDetailToPlan(); + + ExercisePlanDetail detail2 = ExercisePlanDetail(13); + detail2.repeats = 33; + detail2.weightEquation = "15"; + detail2.serie = 3; + _exercisePlanRepository.setActualPlanDetail(detail2); + _exercisePlanRepository.addDetailToPlan(); + + await _exercisePlanRepository.saveExercisePlan(); + + expect(_exercisePlanRepository.getExercisePlan().name, planName); + expectLater(_exercisePlanRepository.getExercisePlan().exercisePlanId > 0, true); + + ExercisePlanDetail detail = _exercisePlanRepository.getExercisePlanDetailByExerciseId(13); + expect(detail.exercisePlanId, _exercisePlanRepository.getExercisePlan().exercisePlanId); + expect(detail.repeats, 33); + + }); + + }); + + group('Existing Plan', () { + test('Get Last Plan and Details from DB', () async { + ExercisePlan exercisePlan = await _exercisePlanRepository.getLastExercisePlan(); + expect(exercisePlan.customerId,101); + + await _exercisePlanRepository.getExercisePlanDetails(); + expect(_exercisePlanRepository.exercisePlanDetails[3].repeats,23 ); + expect(_exercisePlanRepository.exercisePlanDetails[4].weightEquation,"95" ); + + //Test Cache + expect(Cache().getMyExercisePlan().name,"Test Plan2"); + expect(Cache().getMyExercisePlanDetails()[3].weightEquation,"60"); + }); + + test('Add new PlanDetail', () async { + _exercisePlanRepository.newPlan = false; + ExercisePlanDetail detail4 = ExercisePlanDetail(5); + detail4.repeats = 6; + detail4.weightEquation = "105"; + detail4.serie = 3; + + _exercisePlanRepository.setActualPlanDetail(detail4); + _exercisePlanRepository.addDetailToPlan(); + + await _exercisePlanRepository.saveExercisePlan(); + + expect(_exercisePlanRepository.getExercisePlan().name, "Test Plan2"); + expect(Cache().getMyExercisePlanDetails()[5].weightEquation, "105"); + expect(Cache().getMyExercisePlanDetails()[5].repeats, 6); + + }); + + test('Delete from PlanDetails', () async { + _exercisePlanRepository.removeExerciseTypeFromPlanByExerciseTypeId(4); + _exercisePlanRepository.saveExercisePlan(); + + expect(_exercisePlanRepository.exercisePlanDetails[4].change, ExercisePlanDetailChange.delete); + expect(Cache().getMyExercisePlanDetails()[4], isNull); + }); + + }); + + + + +} diff --git a/test/mocks.dart b/test/mocks.dart new file mode 100644 index 0000000..c462f97 --- /dev/null +++ b/test/mocks.dart @@ -0,0 +1,181 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise_plan.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; +import 'package:aitrainer_app/service/exercise_plan_service.dart'; +import 'package:mockito/mockito.dart'; + +class MockExercisePlanApi extends Mock implements ExercisePlanApi { + static final MockExercisePlanApi _singleton = MockExercisePlanApi._internal(); + factory MockExercisePlanApi() { + return _singleton; + } + + MockExercisePlanApi._internal() {} + + final List memoryExercisePlan = List(); + final List memoryExercisePlanDetail = List(); + + Future saveExercisePlan(ExercisePlan plan) async { + memoryExercisePlan.add(plan); + plan.exercisePlanId = memoryExercisePlan.length; + return plan; + } + + Future saveExercisePlanDetail(ExercisePlanDetail detail) async { + memoryExercisePlanDetail.add(detail); + detail.exercisePlanDetailId = memoryExercisePlanDetail.length; + return detail; + } + + Future updateExercisePlanDetail(ExercisePlanDetail detail, int planDetailId) async { + ExercisePlanDetail updated; + memoryExercisePlanDetail.forEach((element) { + if ( element.exercisePlanDetailId == planDetailId ) { + element = detail; + } + }); + + return updated; + } + + Future deleteExercisePlanDetail(int exercisePlanId) async { + int index = -1; + int countIndex = 0; + memoryExercisePlanDetail.forEach((element) { + if ( element.exercisePlanId == exercisePlanId ) { + index = countIndex; + } + countIndex++; + }); + if ( index > -1) { + memoryExercisePlanDetail.removeAt(index); + } + } + + Future> getExercisePlanDetail(int exercisePlanId) async { + List foundList = List(); + memoryExercisePlanDetail.forEach((element) { + if ( element.exercisePlanId == exercisePlanId ) { + foundList.add(element); + } + }); + return foundList; + } + + Future getLastExercisePlan(int customerId) async { + ExercisePlan found; + memoryExercisePlan.forEach((element) { + if ( element.customerId == customerId ) { + found = element; + } + }); + return found; + } +} + +class SimExercisePlanRepository with ExercisePlanRepository { + + Future getExercisePlanDetails() async { + if (exercisePlan == null) { + ExercisePlan exercisePlan = await this.getLastExercisePlan(); + if ( exercisePlan == null ) { + exercisePlanDetails = LinkedHashMap(); + return; + } + } + + List list = List(); + LinkedHashMap listCache = Cache().getMyExercisePlanDetails(); + if ( listCache.length > 0) { + exercisePlanDetails = listCache; + return; + } else { + list = await MockExercisePlanApi().getExercisePlanDetail(exercisePlan.exercisePlanId); + } + + exercisePlanDetails = LinkedHashMap(); + + list.forEach((element) { + newPlan = false; + ExercisePlanDetail detail = element; + exercisePlanDetails[detail.exerciseTypeId] = detail; + }); + Cache().setMyExercisePlanDetails(exercisePlanDetails); + + return; + } + + Future getLastExercisePlan() async { + if ( customerId == 0) { + return null; + } + ExercisePlan myExercisePlan = Cache().getMyExercisePlan(); + if ( myExercisePlan != null ) { + newPlan = false; + return myExercisePlan; + } + + exercisePlan = await MockExercisePlanApi().getLastExercisePlan(customerId); + newPlan = (exercisePlan == null); + Cache().setMyExercisePlan(exercisePlan); + return exercisePlan; + } + + Future saveExercisePlan() async { + if ( exercisePlan == null ) { + if ( Cache().userLoggedIn == null ) { + throw Exception("please log in"); + } + + String exercisePlanName; + if ( this.customerId == Cache().userLoggedIn.customerId) { + exercisePlanName = Cache().userLoggedIn.name + " private"; + } else { + exercisePlanName = Cache().getTrainee().name + " " + Cache().getTrainee().firstname + " private"; + } + + exercisePlan = ExercisePlan(exercisePlanName, this.customerId); + } + if ( newPlan ) { + exercisePlan.dateAdd = DateTime.now(); + exercisePlan.private = true; + + ExercisePlan savedExercisePlan + = await MockExercisePlanApi().saveExercisePlan(exercisePlan); + + + LinkedHashMap savedExercisePlanDetails = LinkedHashMap(); + exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { + exercisePlanDetail.exercisePlanId = savedExercisePlan.exercisePlanId; + + ExercisePlanDetail savedDetail = await MockExercisePlanApi().saveExercisePlanDetail(exercisePlanDetail); + + savedExercisePlanDetails[savedDetail.exerciseTypeId] = savedDetail; + }); + + exercisePlan = savedExercisePlan; + } else { + + await MockExercisePlanApi().updateExercisePlan(exercisePlan, exercisePlan.exercisePlanId); + + exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { + if ( exercisePlanDetail.change == ExercisePlanDetailChange.delete ) { + await MockExercisePlanApi() + .deleteExercisePlanDetail(exercisePlanDetail.exercisePlanDetailId); + Cache().deleteMyExercisePlanDetail(exercisePlanDetail); + } else if ( exercisePlanDetail.change == ExercisePlanDetailChange.update ) { + await MockExercisePlanApi() + .updateExercisePlanDetail(exercisePlanDetail, exercisePlanDetail.exercisePlanDetailId); + Cache().updateMyExercisePlanDetail(exercisePlanDetail); + } else if ( exercisePlanDetail.change == ExercisePlanDetailChange.add ) { + await MockExercisePlanApi() + .saveExercisePlanDetail(exercisePlanDetail); + Cache().addToMyExercisePlanDetails(exercisePlanDetail); + } + }); + } + } +} \ No newline at end of file