diff --git a/asset/image/lock.png b/asset/image/lock.png new file mode 100644 index 0000000..2e8c552 Binary files /dev/null and b/asset/image/lock.png differ diff --git a/i18n/en.json b/i18n/en.json index 244ce60..c1300a9 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -52,6 +52,7 @@ "Sizes": "Sizes", "Save Exercise": "Save Exercise of:", "Save": "Save", + "Delete": "Delete", "Name": "Name", "Exercise": "Exercise", @@ -117,5 +118,21 @@ "3rd Control Exercise:": "3rd Control Exercise:", "My Development":"My Development", - "My Training Plan":"My Training Plan" + "My Training Plan":"My Training Plan", + + "Please add an exercise plan": "Please add an exercise plan", + "Serie": "Serie", + "Repeats": "Repeats", + "Save The Exercise To The Exercise Plan": "Save The Exercise To The Exercise Plan", + "The number of the serie done with":"The number of the serie done with", + "The number of the repeats of one serie":"The number of the repeats of one serie", + + "1. Chest": "1. Chest", + "2. Biceps": "2. Biceps", + "3. Triceps": "3. Triceps", + "4. Back": "4. Back", + "5. Shoulders": "5. Shoulders", + "6. Core": "6. Core", + "7. Thigh": "7. Thigh", + "8. Calf": "8. Calf" } \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index f553264..5489c48 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -52,6 +52,7 @@ "Sizes": "Méretek", "Save Exercise": "Gyakorlat mentése:", "Save": "Mentés", + "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", @@ -116,5 +117,22 @@ "3rd Control Exercise:": "3. kontrollgyakorlat:", "My Development":"Fejlődésem", - "My Training Plan":"Edzéstervem" + "My Training Plan":"Edzéstervem", + + "Please add an exercise plan": "Kérlek add meg az edzéstervet a gyakorlathoz", + + "Serie": "Széria", + "Repeats": "Ismétlés", + "Save The Exercise To The Exercise Plan": "Gyakorlat mentése az edzéstervhez", + "The number of the serie done with":"Mennyi szériát csinálsz", + "The number of the repeats of one serie":"Hány ismétlést csinálsz egy gyakorlaton belül", + + "1. Chest": "1. Mell", + "2. Biceps": "2. Bicepsz", + "3. Triceps": "3. Tricepsz", + "4. Back": "4. Hát", + "5. Shoulders": "5. Váll", + "6. Core": "6. Has", + "7. Thigh": "7. Comb", + "8. Calf": "8. Vádli" } \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index 2e3779a..f0a05b5 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> <device id="retina6_1" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> @@ -16,10 +16,10 @@ <viewControllerLayoutGuide type="bottom" id="wCe-IQ-FAo"/> </layoutGuides> <view key="view" autoresizesSubviews="NO" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="Ze5-6b-2t3"> - <rect key="frame" x="0.0" y="0.0" width="414" height="878"/> + <rect key="frame" x="0.0" y="0.0" width="414" height="825"/> <subviews> <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFit" ambiguous="YES" image="LaunchImage" adjustsImageSizeForAccessibilityContentSizeCategory="YES" id="YRO-k0-Ey4"> - <rect key="frame" x="-1" y="0.0" width="414" height="878"/> + <rect key="frame" x="-26" y="-92" width="505" height="1009"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="default"/> </imageView> diff --git a/lib/bloc/account/account_bloc.dart b/lib/bloc/account/account_bloc.dart index 03779a8..0e33c7b 100644 --- a/lib/bloc/account/account_bloc.dart +++ b/lib/bloc/account/account_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/customer.dart'; import 'package:aitrainer_app/repository/customer_repository.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; @@ -13,6 +14,7 @@ part 'account_state.dart'; class AccountBloc extends Bloc<AccountEvent, AccountState> { final CustomerRepository customerRepository; bool loggedIn = false; + int traineeId = 0; AccountBloc({this.customerRepository}) : super(AccountInitial()) { if ( Cache().userLoggedIn != null ) { customerRepository.customer = Cache().userLoggedIn; @@ -36,8 +38,21 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> { } else if (event is AccountLogout) { await Cache().logout(); customerRepository.customer = null; + customerRepository.emptyTrainees(); loggedIn = false; yield AccountLoggedOut(); + } else if ( event is AccountGetTrainees) { + yield AccountLoading(); + await customerRepository.getTrainees(); + yield AccountReady(); + } else if ( event is AccountSelectTrainee ) { + yield AccountLoading(); + customerRepository.setTrainee(event.traineeId); + Cache().setTrainee(customerRepository.getTraineeById(event.traineeId)); + ExerciseRepository exerciseRepository = ExerciseRepository(); + await exerciseRepository.getExercisesByCustomer(event.traineeId); + this.traineeId = event.traineeId; + yield AccountReady(); } } on Exception catch(e) { yield AccountError(message: e.toString()); diff --git a/lib/bloc/account/account_event.dart b/lib/bloc/account/account_event.dart index a433b05..5259cfd 100644 --- a/lib/bloc/account/account_event.dart +++ b/lib/bloc/account/account_event.dart @@ -43,4 +43,16 @@ class AccountLogInFinished extends AccountEvent { @override List<Object> get props => [customer]; +} + +class AccountGetTrainees extends AccountEvent { + const AccountGetTrainees(); +} + +class AccountSelectTrainee extends AccountEvent { + final int traineeId; + const AccountSelectTrainee({this.traineeId}); + + @override + List<Object> get props => [traineeId]; } \ No newline at end of file diff --git a/lib/bloc/exercise_add_by_plan_bloc.dart b/lib/bloc/exercise_add_by_plan_bloc.dart new file mode 100644 index 0000000..3d72ae1 --- /dev/null +++ b/lib/bloc/exercise_add_by_plan_bloc.dart @@ -0,0 +1,81 @@ +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/repository/exercise_plan_repository.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; + +class ExerciseAddByPlanFormBloc extends FormBloc<String, String> { + final ExerciseRepository exerciseRepository; + final ExercisePlanRepository exercisePlanRepository; + final WorkoutTree workoutTree; + final customerId; + Customer customer; + int step = 1; + int countSteps = 1; + + final quantity1Field = TextFieldBloc( + ); + + final unitQuantity1Field = TextFieldBloc( + ); + + ExerciseAddByPlanFormBloc({this.exerciseRepository, this.exercisePlanRepository, this.customerId, this.workoutTree}) { + addFieldBlocs(fieldBlocs: [ + quantity1Field, + unitQuantity1Field, + ]); + + quantity1Field.onValueChanges(onData: (previous, current) async* { + exerciseRepository.setQuantity(current.valueToDouble); + }); + + unitQuantity1Field.onValueChanges(onData: (previous, current) async* { + exerciseRepository.setUnitQuantity(unitQuantity1Field.valueToDouble); + }); + + exerciseRepository.exerciseType = workoutTree.exerciseType; + if ( Cache().userLoggedIn.customerId == customerId) { + customer = Cache().userLoggedIn; + } else if ( Cache().getTrainee().customerId == customerId) { + customer = Cache().getTrainee(); + } + exercisePlanRepository.setActualPlanDetail(workoutTree.exerciseType); + exerciseRepository.customer = customer; + countSteps = exercisePlanRepository.actualPlanDetail.serie; + + unitQuantity1Field.updateInitialValue(exercisePlanRepository.actualPlanDetail.weightEquation); + quantity1Field.updateInitialValue(exercisePlanRepository.actualPlanDetail.repeats.toString()); + + } + + @override + void onSubmitting() async { + + try { + emitLoading(progress: 30); + step++; + + exerciseRepository.setUnitQuantity(unitQuantity1Field.valueToDouble); + exerciseRepository.setQuantity(quantity1Field.valueToDouble); + exerciseRepository.exercise.exercisePlanDetailId = + exercisePlanRepository.actualPlanDetail.exercisePlanDetailId; + exerciseRepository.exercise.unit = workoutTree.exerciseType.unit; + workoutTree.executed = true; + print("On Submitting Add Exercise By Plan " + exerciseRepository.exercise.toJson().toString()); + await exerciseRepository.addExercise(); + + emitSuccess(canSubmitAgain: true); + } on Exception catch (ex) { + emitFailure(failureResponse: ex.toString()); + } + } + + //@override + Future<void> close() { + quantity1Field.close(); + unitQuantity1Field.close(); + + return super.close(); + } +} diff --git a/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart b/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart new file mode 100644 index 0000000..c852831 --- /dev/null +++ b/lib/bloc/exercise_by_plan/exercise_by_plan_bloc.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'package:aitrainer_app/model/exercise_type.dart'; +import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:aitrainer_app/repository/menu_tree_repository.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +part 'exercise_by_plan_event.dart'; + +part 'exercise_by_plan_state.dart'; + +class ExerciseByPlanBloc extends Bloc<ExerciseByPlanEvent, ExerciseByPlanState> { + final ExerciseRepository exerciseRepository; + final MenuTreeRepository menuTreeRepository; + final ExercisePlanRepository exercisePlanRepository = ExercisePlanRepository(); + int customerId; + @override + ExerciseByPlanBloc({this.exerciseRepository, this.menuTreeRepository}) : super(ExerciseByPlanStateInitial()); + + Future<void> getData() async { + exercisePlanRepository.setCustomerId(customerId); + await exercisePlanRepository.getLastExercisePlan(); + await exercisePlanRepository.getExercisePlanDetails(); + menuTreeRepository.sortedTree = null; + menuTreeRepository.sortByMuscleType(); + + menuTreeRepository.sortedTree.forEach((key, value) { + List<WorkoutTree> listWorkoutTree = value; + listWorkoutTree.forEach((workoutTree) { + workoutTree.selected = false; + if (exercisePlanRepository.exercisePlanDetails.length > 0) { + if (exercisePlanRepository.exercisePlanDetails[workoutTree.exerciseTypeId] != null) { + workoutTree.selected = true; + } + } + }); + }); + } + + @override + Stream<ExerciseByPlanState> mapEventToState(ExerciseByPlanEvent event) async* { + try { + if (event is ExerciseByPlanLoad) { + yield ExerciseByPlanLoading(); + await this.getData(); + yield ExerciseByPlanReady(); + } else if (event is AddExerciseByPlanEvent) { + yield ExerciseByPlanLoading(); + yield ExerciseByPlanReady(); + } + } on Exception catch (e) { + yield ExerciseByPlanError(message: e.toString()); + } + } +} diff --git a/lib/bloc/exercise_by_plan/exercise_by_plan_event.dart b/lib/bloc/exercise_by_plan/exercise_by_plan_event.dart new file mode 100644 index 0000000..97c1c14 --- /dev/null +++ b/lib/bloc/exercise_by_plan/exercise_by_plan_event.dart @@ -0,0 +1,21 @@ +part of 'exercise_by_plan_bloc.dart'; + +@immutable +abstract class ExerciseByPlanEvent extends Equatable { + const ExerciseByPlanEvent(); + + @override + List<Object> get props => []; +} + +class AddExerciseByPlanEvent extends ExerciseByPlanEvent { + final ExerciseType exerciseType; + const AddExerciseByPlanEvent({this.exerciseType}); + + @override + List<Object> get props => [exerciseType]; +} + +class ExerciseByPlanLoad extends ExerciseByPlanEvent { + const ExerciseByPlanLoad(); +} diff --git a/lib/bloc/exercise_by_plan/exercise_by_plan_state.dart b/lib/bloc/exercise_by_plan/exercise_by_plan_state.dart new file mode 100644 index 0000000..f9c89c7 --- /dev/null +++ b/lib/bloc/exercise_by_plan/exercise_by_plan_state.dart @@ -0,0 +1,31 @@ +part of 'exercise_by_plan_bloc.dart'; + +@immutable +abstract class ExerciseByPlanState extends Equatable { + const ExerciseByPlanState(); + + @override + List<Object> get props => []; +} + +class ExerciseByPlanStateInitial extends ExerciseByPlanState { + const ExerciseByPlanStateInitial(); +} + +class ExerciseByPlanLoading extends ExerciseByPlanState { + const ExerciseByPlanLoading(); +} + +// updated screen +class ExerciseByPlanReady extends ExerciseByPlanState { + const ExerciseByPlanReady(); +} + +// error splash screen +class ExerciseByPlanError extends ExerciseByPlanState { + final String message; + const ExerciseByPlanError({this.message}); + + @override + List<Object> get props => [message]; +} \ No newline at end of file diff --git a/lib/bloc/exercise_plan/exercise_plan_bloc.dart b/lib/bloc/exercise_plan/exercise_plan_bloc.dart new file mode 100644 index 0000000..5c88210 --- /dev/null +++ b/lib/bloc/exercise_plan/exercise_plan_bloc.dart @@ -0,0 +1,89 @@ +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/repository/exercise_plan_repository.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:aitrainer_app/repository/menu_tree_repository.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +part 'exercise_plan_event.dart'; + +part 'exercise_plan_state.dart'; + +class ExercisePlanBloc extends Bloc<ExercisePlanEvent, ExercisePlanState> { + final MenuTreeRepository menuTreeRepository; + final ExerciseRepository exerciseRepository; + final ExercisePlanRepository exercisePlanRepository = ExercisePlanRepository(); + int customerId; + + ExercisePlanBloc({this.exerciseRepository, this.menuTreeRepository}) : super(ExercisePlanInitial()); + + Future<void> getData() async { + exercisePlanRepository.setCustomerId(customerId); + await exercisePlanRepository.getLastExercisePlan(); + await exercisePlanRepository.getExercisePlanDetails(); + menuTreeRepository.sortedTree = null; + menuTreeRepository.sortByMuscleType(); + + menuTreeRepository.sortedTree.forEach((key, value) { + List<WorkoutTree> listWorkoutTree = value; + listWorkoutTree.forEach((workoutTree) { + workoutTree.selected = false; + if (exercisePlanRepository.exercisePlanDetails.length > 0) { + if (exercisePlanRepository.exercisePlanDetails[workoutTree.exerciseTypeId] != null) { + //print("bingo"); + workoutTree.selected = true; + } + } + }); + }); + } + + @override + Stream<ExercisePlanState> mapEventToState(ExercisePlanEvent event) async* { + try { + if (event is ExercisePlanLoad) { + yield ExercisePlanLoading(); + await this.getData(); + yield ExercisePlanReady(); + } + if (event is ExercisePlanUpdate) { + yield ExercisePlanLoading(); + WorkoutTree workoutTree = event.workoutTree; + if (workoutTree != null) { + exercisePlanRepository.addExerciseTypeToPlan(workoutTree.exerciseType); + workoutTree.selected = true; + } + yield ExercisePlanReady(); + } else if (event is ExercisePlanRemoveExercise) { + yield ExercisePlanLoading(); + ExercisePlanDetail planDetail = event.exercisePlanDetail; + exercisePlanRepository.removeExerciseTypeFromPlan(planDetail.exerciseType); + this.menuTreeRepository.sortedTree.forEach((key, value) { + List<WorkoutTree> 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(); + exercisePlanRepository.saveExercisePlan(); + 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 new file mode 100644 index 0000000..0faf92a --- /dev/null +++ b/lib/bloc/exercise_plan/exercise_plan_event.dart @@ -0,0 +1,49 @@ +part of 'exercise_plan_bloc.dart'; + +@immutable +abstract class ExercisePlanEvent extends Equatable { + const ExercisePlanEvent(); + @override + List<Object> get props => []; +} + +class ExercisePlanLoad extends ExercisePlanEvent { + const ExercisePlanLoad(); +} + +class ExercisePlanUpdate extends ExercisePlanEvent { + final WorkoutTree workoutTree; + const ExercisePlanUpdate({this.workoutTree}); + + @override + List<Object> get props => [workoutTree]; +} + +class ExercisePlanAddExercise extends ExercisePlanEvent { + final ExerciseType exerciseType; + const ExercisePlanAddExercise({this.exerciseType}); + + @override + List<Object> get props => [exerciseType]; +} + +class ExercisePlanRemoveExercise extends ExercisePlanEvent { + final ExercisePlanDetail exercisePlanDetail; + const ExercisePlanRemoveExercise({this.exercisePlanDetail}); + + @override + List<Object> get props => [exercisePlanDetail]; +} + +class ExercisePlanUpdateExercise extends ExercisePlanEvent { + final ExercisePlanDetail exercisePlanDetail; + const ExercisePlanUpdateExercise({this.exercisePlanDetail}); + + @override + List<Object> get props => [exercisePlanDetail]; +} + +class ExercisePlanSave extends ExercisePlanEvent { + const ExercisePlanSave(); +} + diff --git a/lib/bloc/exercise_plan/exercise_plan_state.dart b/lib/bloc/exercise_plan/exercise_plan_state.dart new file mode 100644 index 0000000..131a0e6 --- /dev/null +++ b/lib/bloc/exercise_plan/exercise_plan_state.dart @@ -0,0 +1,37 @@ +part of 'exercise_plan_bloc.dart'; + +@immutable +abstract class ExercisePlanState extends Equatable{ + const ExercisePlanState(); + + @override + List<Object> get props => []; +} + +// Display the last saved exercise plan +class ExercisePlanInitial extends ExercisePlanState { + const ExercisePlanInitial(); +} + +// Loading screen +class ExercisePlanLoading extends ExercisePlanState { + const ExercisePlanLoading(); +} + +// updated screen +class ExercisePlanReady extends ExercisePlanState { + const ExercisePlanReady(); +} + +// error splash screen +class ExercisePlanError extends ExercisePlanState { + final String message; + const ExercisePlanError({this.message}); + + @override + List<Object> get props => [message]; +} + + + + diff --git a/lib/bloc/exercise_plan_custom_form.dart b/lib/bloc/exercise_plan_custom_form.dart new file mode 100644 index 0000000..43e714e --- /dev/null +++ b/lib/bloc/exercise_plan_custom_form.dart @@ -0,0 +1,94 @@ +import 'package:aitrainer_app/bloc/exercise_plan/exercise_plan_bloc.dart'; +import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; + +class ExercisePlanCustomerFormBloc extends FormBloc<String, String> { + final ExercisePlanRepository exercisePlanRepository; + final ExercisePlanBloc planBloc; + final serieField = TextFieldBloc( + validators: [ + FieldBlocValidators.required, + ], + ); + + final quantityField = TextFieldBloc( + validators: [ + FieldBlocValidators.required, + ], + ); + + final weightField = TextFieldBloc( + validators: [ + FieldBlocValidators.required, + ], + ); + + final exerciseTypeField = TextFieldBloc( + validators: [ + FieldBlocValidators.required, + ], + ); + + ExercisePlanCustomerFormBloc({this.exercisePlanRepository, this.planBloc}) { + addFieldBlocs(fieldBlocs: [ + quantityField, + serieField, + weightField + ]); + + String repeatsInitial = exercisePlanRepository.actualPlanDetail != null && + exercisePlanRepository.actualPlanDetail.repeats != null ? + exercisePlanRepository.actualPlanDetail.repeats.toString() : "12"; + quantityField.updateInitialValue(repeatsInitial); + + String serieInitial = exercisePlanRepository.actualPlanDetail != null && + exercisePlanRepository.actualPlanDetail.serie != null ? + exercisePlanRepository.actualPlanDetail.serie.toString() : "3"; + serieField.updateInitialValue(serieInitial); + + String weightInitial = exercisePlanRepository.actualPlanDetail != null && + exercisePlanRepository.actualPlanDetail.weightEquation != null ? + exercisePlanRepository.actualPlanDetail.weightEquation : "30"; + weightField.updateInitialValue(weightInitial); + + quantityField.onValueChanges(onData: (previous, current) async* { + exercisePlanRepository.actualPlanDetail.repeats = current.valueToInt; + }); + serieField.onValueChanges(onData: (previous, current) async* { + exercisePlanRepository.actualPlanDetail.serie = current.valueToInt; + }); + weightField.onValueChanges(onData: (previous, current) async* { + exercisePlanRepository.actualPlanDetail.weightEquation = current.value; + }); + } + + @override + void onSubmitting() async { + print("On Submitting Custom Plan form"); + try { + 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()); + + emitSuccess(canSubmitAgain: false); + } on Exception catch (ex) { + emitFailure(failureResponse: ex.toString()); + } + } + + + //@override + Future<void> close() { + quantityField.close(); + serieField.close(); + weightField.close(); + exerciseTypeField.close(); + return super.close(); + } +} diff --git a/lib/bloc/settings/settings_bloc.dart b/lib/bloc/settings/settings_bloc.dart index af2d4b5..fb6db71 100644 --- a/lib/bloc/settings/settings_bloc.dart +++ b/lib/bloc/settings/settings_bloc.dart @@ -34,6 +34,10 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> { yield SettingsLoading(); await _changeLang( event.language); yield SettingsReady(_locale); + } else if ( event is SettingsGetLanguage) { + await AppLanguage().fetchLocale(); + _locale = AppLanguage().appLocal; + yield SettingsReady(_locale); } } @@ -50,12 +54,13 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> { break; } this.language = lang; + final AppLanguage appLanguage = AppLanguage(); + appLanguage.changeLanguage(_locale); await loadLang(); } Future<void> loadLang() async{ - final AppLanguage appLanguage = AppLanguage(); - appLanguage.changeLanguage(_locale); + print (" -- Loading lang $_locale"); if ( context != null ) { AppLocalizations.of(context).setLocale(_locale); await AppLocalizations.of(context).load(); diff --git a/lib/bloc/settings/settings_event.dart b/lib/bloc/settings/settings_event.dart index 31a8279..c0f741b 100644 --- a/lib/bloc/settings/settings_event.dart +++ b/lib/bloc/settings/settings_event.dart @@ -11,3 +11,7 @@ class SettingsChangeLanguage extends SettingsEvent { final String language; const SettingsChangeLanguage({this.language}); } + +class SettingsGetLanguage extends SettingsEvent { + const SettingsGetLanguage(); +} diff --git a/lib/localization/app_language.dart b/lib/localization/app_language.dart index 11fad94..49bf4f8 100644 --- a/lib/localization/app_language.dart +++ b/lib/localization/app_language.dart @@ -20,19 +20,24 @@ class AppLanguage{ Future<void> fetchLocale() async { var prefs = await SharedPreferences.getInstance(); - if (prefs.getString('language_code') == null) { + String langCode = prefs.getString('language_code'); + print(" ---- lang code $langCode"); + if ( langCode == null) { _appLocale = Locale('en'); } else { - _appLocale = Locale(prefs.getString('language_code')); + _appLocale = Locale(langCode); } print(" ---- Fetched lang: " + _appLocale.toString()); } getLocale(SharedPreferences prefs) { - if (prefs.getString('language_code') == null) { + String langCode = prefs.getString('language_code'); + if ( langCode == null) { _appLocale = Locale('en'); } - _appLocale = Locale(prefs.getString('language_code')); + _appLocale = Locale(langCode); + print(" ---- Get lang: " + _appLocale.toString() + " lang code $langCode"); + } diff --git a/lib/localization/app_localization.dart b/lib/localization/app_localization.dart index 181ba08..ab032e3 100644 --- a/lib/localization/app_localization.dart +++ b/lib/localization/app_localization.dart @@ -28,6 +28,7 @@ class AppLocalizations { Future<bool> load() async { // Load the language JSON file from the "lang" folder + print(" -- load language pieces " + locale.languageCode); String jsonString = await rootBundle.loadString('i18n/${locale.languageCode}.json'); Map<String, dynamic> jsonMap = json.decode(jsonString); diff --git a/lib/main.dart b/lib/main.dart index 213155a..f82cbc5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:aitrainer_app/repository/customer_repository.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/repository/menu_tree_repository.dart'; import 'package:aitrainer_app/util/session.dart'; import 'package:aitrainer_app/view/account.dart'; @@ -9,13 +10,19 @@ import 'package:aitrainer_app/view/customer_fitness_page.dart'; import 'package:aitrainer_app/view/customer_goal_page.dart'; import 'package:aitrainer_app/view/customer_modify_page.dart'; import 'package:aitrainer_app/view/customer_welcome_page.dart'; +import 'package:aitrainer_app/view/exercise_add_by_plan_page.dart'; 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_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_page.dart'; +import 'package:aitrainer_app/view/myexcercise_plan_page.dart'; import 'package:aitrainer_app/view/registration.dart'; import 'package:aitrainer_app/view/settings.dart'; import 'package:aitrainer_app/widgets/home.dart'; @@ -27,6 +34,8 @@ 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/exercise_by_plan/exercise_by_plan_bloc.dart'; +import 'bloc/exercise_plan/exercise_plan_bloc.dart'; import 'bloc/menu/menu_bloc.dart'; import 'bloc/session/session_bloc.dart'; import 'bloc/settings/settings_bloc.dart'; @@ -96,14 +105,15 @@ Future<Null> main() async { // - https://api.dartlang.org/stable/1.24.2/dart-async/Zone-class.html // - https://www.dartlang.org/articles/libraries/zones runZonedGuarded<Future<Null>>(() async { + final MenuTreeRepository menuTreeRepository = MenuTreeRepository(); runApp( MultiBlocProvider( - providers: [ + providers: [ BlocProvider<SessionBloc>( create: (BuildContext context) => SessionBloc(session: Session()), ), BlocProvider<MenuBloc>( - create: (BuildContext context) => MenuBloc( menuTreeRepository: MenuTreeRepository()), + create: (BuildContext context) => MenuBloc( menuTreeRepository: menuTreeRepository), ), BlocProvider<SettingsBloc>( create: (BuildContext context) => SettingsBloc(), @@ -111,6 +121,12 @@ Future<Null> main() async { BlocProvider<AccountBloc>( create: (BuildContext context) => AccountBloc(customerRepository: CustomerRepository()), ), + BlocProvider<ExercisePlanBloc>( + create: (BuildContext context) => ExercisePlanBloc(menuTreeRepository: menuTreeRepository), + ), + BlocProvider<ExerciseByPlanBloc>( + create: (BuildContext context) => ExerciseByPlanBloc(menuTreeRepository: menuTreeRepository, exerciseRepository: ExerciseRepository()), + ), ], child: AitrainerApp(), @@ -172,10 +188,16 @@ class AitrainerApp extends StatelessWidget { 'account': (context) => AccountPage(), 'settings': (context) => SettingsPage(), 'exerciseTypeDescription': (context) => ExerciseTypeDescription(), - 'mydevelopment': (context) => MyDevelopmentPage(), + 'myDevelopment': (context) => MyDevelopmentPage(), + 'myExercisePlan': (context) => MyExercisePlanPage(), + 'exerciseLogPage': (context) => ExerciseLogPage(), + 'exercisePlanCustomPage': (context) => ExercisePlanCustomPage(), + 'exercisePlanDetailAdd': (context) => ExercisePlanDetailAddPage(), + 'exerciseByPlanPage': (context) => ExerciseByPlanPage(), + 'exerciseAddByPlanPage': (context) => ExerciseAddByPlanPage(), }, initialRoute: 'home', - title: 'Aitrainer', + title: 'WorkoutTest', theme: ThemeData( brightness: Brightness.light, //primarySwatch: Colors.transparent, diff --git a/lib/model/cache.dart b/lib/model/cache.dart index af29450..e58ac1c 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -1,6 +1,7 @@ import 'dart:collection'; - import 'package:aitrainer_app/model/customer.dart'; +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'; @@ -55,10 +56,19 @@ class Cache { List<ExerciseType> _exerciseTypes; List<ExerciseTree> _exerciseTree; + List<Exercise> _exercises; + ExercisePlan _myExercisePlan; + List<ExercisePlanDetail> _myExercisesPlanDetail; + LinkedHashMap _tree = LinkedHashMap<String, WorkoutTree>(); double _percentExercises = -1; + Customer _trainee; + List<Exercise> _exercisesTrainee; + ExercisePlan _traineeExercisePlan; + List<ExercisePlanDetail> _traineeExercisesPlanDetail; + List deviceLanguages; String startPage; @@ -73,6 +83,10 @@ class Cache { } } + void setTestBaseUrl() { + baseUrl = 'http://aitrainer.app:8899/api/'; + } + String getAuthToken() { return this.authToken; } @@ -106,6 +120,12 @@ class Cache { logout() async { userLoggedIn = null; authToken = ""; + _trainee = null; + _percentExercises = -1; + _exercisesTrainee = null; + _traineeExercisePlan = null; + _exercises = List(); + print("Trainees is null? " + (_trainee == null).toString() ); //firstLoad = true; Future<SharedPreferences> prefs = SharedPreferences.getInstance(); await setPreferences(prefs, SharePrefsChange.logout, 0); @@ -154,6 +174,10 @@ class Cache { this._exercises = exercises; } + void setExercisesTrainee( List<Exercise> exercises ) { + this._exercisesTrainee = exercises; + } + void setWorkoutTree( LinkedHashMap<String, WorkoutTree> tree) { this._tree = tree; } @@ -170,6 +194,10 @@ class Cache { return this._exercises; } + List<Exercise> getExercisesTrainee() { + return this._exercisesTrainee; + } + LinkedHashMap<String, WorkoutTree> getWorkoutTree() { return this._tree; } @@ -186,4 +214,20 @@ class Cache { _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/customer.dart b/lib/model/customer.dart index 6063fb9..23444a6 100644 --- a/lib/model/customer.dart +++ b/lib/model/customer.dart @@ -13,6 +13,7 @@ class Customer { String fitnessLevel; String bodyType; int admin; + int trainer; int dataPolicyAllowed; @@ -30,6 +31,7 @@ class Customer { this.goal, this.weight, this.admin, + this.trainer, this.dataPolicyAllowed }); @@ -47,6 +49,7 @@ class Customer { this.goal = json['goal']; this.weight = json['weight']; this.admin = json['admin']; + this.trainer = json['trainer']; } Map<String, dynamic> toJson() => @@ -64,6 +67,7 @@ class Customer { "goal": goal, "weight": weight, "admin": admin, + "trainer": trainer, "dataPolicyAllowed": dataPolicyAllowed, }; } diff --git a/lib/model/exercise.dart b/lib/model/exercise.dart index 3e16abb..e3b7dd0 100644 --- a/lib/model/exercise.dart +++ b/lib/model/exercise.dart @@ -8,6 +8,7 @@ class Exercise { String unit; double unitQuantity; DateTime dateAdd; + int exercisePlanDetailId; @@ -30,5 +31,6 @@ class Exercise { "unit": unit, "unitQuantity": unitQuantity, "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd), + "exercisePlanDetailId": exercisePlanDetailId, }; } \ No newline at end of file diff --git a/lib/model/exercise_plan.dart b/lib/model/exercise_plan.dart new file mode 100644 index 0000000..b2b77d8 --- /dev/null +++ b/lib/model/exercise_plan.dart @@ -0,0 +1,58 @@ +import 'package:intl/intl.dart'; + +class ExercisePlan { + int exercisePlanId; + int customerId; + String name; + String description; + bool private; + DateTime dateAdd; + DateTime dateUpd; + + ExercisePlan(String name, int customerId) { + this.customerId = customerId; + this.name = name; + this.dateUpd = DateTime.now(); + } + + ExercisePlan.fromJson(Map json) { + this.exercisePlanId = json['exercisePlanId']; + this.customerId = json['customerId']; + this.name = json['name']; + this.private = json['private']; + this.description = json['description']; + this.dateAdd = json['dateAdd'] == null ? null : DateTime.parse( json['dateAdd'] ); + this.dateUpd = json['dateUpd'] == null ? null : DateTime.parse( json['dateUpd'] ); + } + + Map<String, dynamic> toJson() { + String formattedDateAdd; + if ( dateAdd != null) { + formattedDateAdd = DateFormat('yyyy-MM-dd HH:mm').format(dateAdd); + } + String formattedDateUpd = DateFormat('yyyy-MM-dd HH:mm').format(dateUpd); + + print("DateAdd $formattedDateAdd"); + if ( exercisePlanId == null ) { + return { + "customerId": customerId, + "name": name, + "description": description, + "private": private, + "dateAdd": formattedDateAdd, + "dateUpd": formattedDateUpd, + }; + } else { + return { + "exercisePlanId": exercisePlanId, + "customerId": customerId, + "name": name, + "description": description, + "private": private, + "dateAdd": formattedDateAdd, + "dateUpd": formattedDateUpd, + }; + } + } + +} \ No newline at end of file diff --git a/lib/model/exercise_plan_detail.dart b/lib/model/exercise_plan_detail.dart new file mode 100644 index 0000000..eac325d --- /dev/null +++ b/lib/model/exercise_plan_detail.dart @@ -0,0 +1,36 @@ +import 'package:aitrainer_app/model/exercise_plan.dart'; + +import 'exercise_type.dart'; + +class ExercisePlanDetail { + int exercisePlanDetailId; + int exercisePlanId; + int exerciseTypeId; + int serie; + int repeats; + String weightEquation; + + ExerciseType exerciseType; + + ExercisePlanDetail(int exerciseTypeId) { + this.exerciseTypeId = exerciseTypeId; + } + + ExercisePlanDetail.fromJson(Map json) { + this.exercisePlanDetailId = json['exercisePlanDetailId']; + this.exercisePlanId = json['exercisePlanId']; + this.exerciseTypeId = json['exerciseTypeId']; + this.serie = json['serie']; + this.repeats = json['repeats']; + this.weightEquation = json['weightEquation']; + } + + Map<String, dynamic> toJson() => + { + "exercisePlanId": exercisePlanId, + "exerciseTypeId": exerciseTypeId, + "serie": serie, + "repeats": repeats, + "weightEquation": weightEquation + }; +} \ No newline at end of file diff --git a/lib/model/workout_tree.dart b/lib/model/workout_tree.dart index d07bb92..dae5454 100644 --- a/lib/model/workout_tree.dart +++ b/lib/model/workout_tree.dart @@ -5,7 +5,7 @@ import 'exercise_type.dart'; class WorkoutTree { int id; int parent; - String name; // is also the key + String name; String imageName; Color color; double fontSize; @@ -15,7 +15,26 @@ class WorkoutTree { bool base; bool is1RM; + bool selected = false; + bool executed = false; + String exerciseDetail; + String nameEnglish; - WorkoutTree(this.id, this.parent, this.name, this.imageName, this.color, this.fontSize, this.child, this.exerciseTypeId, this.exerciseType, this.base, this.is1RM); + WorkoutTree(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<String, dynamic> toJson() { + return { + "id": id, + "parent": parent, + "name": name, + "imageName": imageName, + "color": color.toString(), + "fontSize": fontSize.toString(), + "child": child.toString(), + "exerciseTypeId": exerciseTypeId.toString(), + "base": base.toString(), + "is1RM": is1RM.toString(), + }; + } } diff --git a/lib/repository/customer_repository.dart b/lib/repository/customer_repository.dart index 4226a2c..4c0b520 100644 --- a/lib/repository/customer_repository.dart +++ b/lib/repository/customer_repository.dart @@ -1,3 +1,4 @@ +import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/customer.dart'; import 'package:aitrainer_app/service/customer_service.dart'; @@ -9,6 +10,9 @@ class GenderItem { class CustomerRepository { Customer customer; + Customer _trainee; + List<Customer> _trainees; + //List<CustomerRepository> customerList = List<CustomerRepository>(); bool visibleDetails = false; List<GenderItem> genders; @@ -131,13 +135,53 @@ class CustomerRepository { await CustomerApi().saveCustomer(modelCustomer); } - /* Future<List<CustomerRepository>> getCustomers() async { - final results = await CustomerApi().getRealCustomers(""); - this.customerList = results.map((item) => CustomerRepository(customer: item)).toList(); - return this.customerList; + Future<Customer> getTraineeAsCustomer() async { + this._trainee = await CustomerApi().getTrainee( + Cache().userLoggedIn.customerId + ); + return _trainee; } - addNewCustomerToList(CustomerRepository customerViewModel) { - customerList.add(customerViewModel); - }*/ + Future<List<Customer>> getTrainees() async { + int trainerId = Cache().userLoggedIn.customerId; + final results = await CustomerApi().getTrainees(trainerId); + this._trainees = results; + return results; + } + + List<Customer> getTraineesList() { + return _trainees; + } + + void setTrainee(int traineeId ) { + if ( _trainees == null ) { + return; + } + _trainees.forEach((element) { + if ( traineeId == element.customerId) { + this._trainee = element; + } + }); + } + + void emptyTrainees() { + _trainees = null; + _trainee = null; + } + + Customer getTrainee() { + return this._trainee; + } + + Customer getTraineeById(int customerId) { + if (_trainees == null) { + return null; + } + _trainees.forEach((element) { + if ( customerId == element.customerId) { + this._trainee = element; + } + }); + return _trainee; + } } diff --git a/lib/repository/exercise_plan_repository.dart b/lib/repository/exercise_plan_repository.dart new file mode 100644 index 0000000..61df4f4 --- /dev/null +++ b/lib/repository/exercise_plan_repository.dart @@ -0,0 +1,167 @@ +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/model/exercise_type.dart'; +import 'package:aitrainer_app/service/exercise_plan_service.dart'; + +class ExercisePlanRepository { + bool newPlan = true; + ExercisePlan _exercisePlan; + LinkedHashMap<int, ExercisePlanDetail> exercisePlanDetails = + LinkedHashMap<int, ExercisePlanDetail>(); + LinkedHashMap<int, ExercisePlanDetail> _origExercisePlanDetails = + LinkedHashMap<int, ExercisePlanDetail>(); + int _customerId = 0; + ExercisePlanDetail actualPlanDetail; + + void setCustomerId( int customerId ) { + this._customerId = customerId; + } + + int getCustomerId() { + return this._customerId; + } + + void addExerciseTypeToPlan(ExerciseType exerciseType) { + setActualPlanDetail(exerciseType); + } + + void addToPlan() { + exercisePlanDetails[actualPlanDetail.exerciseTypeId] = actualPlanDetail; + } + + void setActualPlanDetail(ExerciseType exerciseType) { + ExercisePlanDetail detail = exercisePlanDetails[exerciseType.exerciseTypeId]; + if ( detail != null ) { + actualPlanDetail = detail; + } else { + actualPlanDetail = ExercisePlanDetail(exerciseType.exerciseTypeId); + } + actualPlanDetail.exerciseType = exerciseType; + } + + int getPlanDetailId(int exerciseTypeId) { + ExercisePlanDetail detail = exercisePlanDetails[exerciseTypeId]; + return detail.exercisePlanDetailId; + } + + String getPlanDetail(int exerciseTypeId) { + ExercisePlanDetail detail = exercisePlanDetails[exerciseTypeId]; + String detailString = ""; + if ( detail != null) { + detailString = + detail.serie.toString() + "x" + detail.repeats.toString() + " " + + detail.weightEquation + "kg"; + } + return detailString; + } + + int getExercisePlanDetailSize() { + return exercisePlanDetails.length; + } + + void updateExercisePlanDetail(ExerciseType exerciseType, int serie, int repeat, String weight) { + if ( exercisePlanDetails[exerciseType.exerciseTypeId] == null) { + return; + } + ExercisePlanDetail exercisePlanDetail = exercisePlanDetails[exerciseType.exerciseTypeId]; + exercisePlanDetail.serie = serie; + exercisePlanDetail.repeats = repeat; + exercisePlanDetail.weightEquation = weight; + + exercisePlanDetails[exerciseType.exerciseTypeId] = exercisePlanDetail; + } + + void removeExerciseTypeFromPlan(ExerciseType exerciseType) { + exercisePlanDetails.remove(exerciseType.exerciseTypeId); + } + + Future<void> 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 ExercisePlanApi().saveExercisePlan(_exercisePlan); + + exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { + exercisePlanDetail.exercisePlanId = savedExercisePlan.exercisePlanId; + await ExercisePlanApi().saveExercisePlanDetail(exercisePlanDetail); + }); + } else { + + await ExercisePlanApi().updateExercisePlan(_exercisePlan, _exercisePlan.exercisePlanId); + + _origExercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async { + if (exercisePlanDetails[exercisePlanDetail.exercisePlanDetailId] == null) { + await ExercisePlanApi() + .deleteExercisePlanDetail(exercisePlanDetail.exercisePlanDetailId); + } else { + await ExercisePlanApi() + .updateExercisePlanDetail(exercisePlanDetail, exercisePlanDetail.exercisePlanDetailId); + } + }); + + exercisePlanDetails.forEach((exerciseTypeId, exercisePlanDetail) async{ + exercisePlanDetail.exercisePlanId = _exercisePlan.exercisePlanId; + if ( _origExercisePlanDetails[exercisePlanDetail.exercisePlanDetailId] == null) { + await ExercisePlanApi() + .saveExercisePlanDetail(exercisePlanDetail); + } + }); + } + } + + Future<ExercisePlan> getLastExercisePlan() async { + if ( _customerId == 0) { + return null; + } + _exercisePlan = await ExercisePlanApi().getLastExercisePlan(_customerId); + newPlan = (_exercisePlan == null); + print("New plan: " + newPlan.toString()); + + return _exercisePlan; + } + + Future<void> getExercisePlanDetails() async { + if (_exercisePlan == null) { + ExercisePlan exercisePlan = await this.getLastExercisePlan(); + if ( exercisePlan == null ) { + exercisePlanDetails = LinkedHashMap<int, ExercisePlanDetail>(); + _origExercisePlanDetails = LinkedHashMap<int, ExercisePlanDetail>(); + return; + } + } + exercisePlanDetails = LinkedHashMap<int, ExercisePlanDetail>(); + _origExercisePlanDetails = LinkedHashMap<int, ExercisePlanDetail>(); + + List<ExercisePlanDetail> list = + await ExercisePlanApi().getExercisePlanDetail(_exercisePlan.exercisePlanId); + + list.forEach((element) { + newPlan = false; + ExercisePlanDetail detail = element; + _origExercisePlanDetails[detail.exerciseTypeId] = detail; + exercisePlanDetails[detail.exerciseTypeId] = detail; + }); + + return; + } + +} \ No newline at end of file diff --git a/lib/repository/exercise_repository.dart b/lib/repository/exercise_repository.dart index 885305f..244d614 100644 --- a/lib/repository/exercise_repository.dart +++ b/lib/repository/exercise_repository.dart @@ -72,8 +72,12 @@ class ExerciseRepository { final Exercise modelExercise = this.exercise; modelExercise.customerId = this.customer.customerId; modelExercise.exerciseTypeId = this.exerciseType.exerciseTypeId; - await ExerciseApi().addExercise(modelExercise); - Cache().addExercise(exercise); + Exercise savedExercise = await ExerciseApi().addExercise(modelExercise); + if ( customer.customerId == Cache().userLoggedIn.customerId) { + Cache().addExercise(savedExercise); + } else if ( Cache().getTrainee() != null && customer.customerId == Cache().getTrainee().customerId ) { + Cache().addExerciseTrainee(savedExercise); + } } @@ -89,15 +93,26 @@ class ExerciseRepository { Future<List<Exercise>> getExercisesByCustomer( int customerId ) async { final results = await ExerciseApi().getExercisesByCustomer(customerId); this.exerciseList = results; - Cache().setExercises(exerciseList); + if ( customerId == Cache().userLoggedIn.customerId) { + Cache().setExercises(exerciseList); + } else if ( Cache().getTrainee() != null && customerId == Cache().getTrainee().customerId ) { + Cache().setExercisesTrainee(exerciseList); + } return this.exerciseList; } List<Exercise> getExerciseList() { - if ( this.exerciseList == null || this.exerciseList.length == 0 ) { - this.exerciseList = Cache().getExercises(); - } - return this.exerciseList; + //if ( this.exerciseList == null || this.exerciseList.length == 0 ) { + return this.exerciseList = Cache().getExercises(); + //} + //return this.exerciseList; + } + + List<Exercise> getExerciseListTrainee() { + //if ( this.exerciseList == null || this.exerciseList.length == 0 ) { + return this.exerciseList = Cache().getExercisesTrainee(); + //} + //return this.exerciseList; } void getBaseExerciseFinishedPercent() { diff --git a/lib/repository/menu_tree_repository.dart b/lib/repository/menu_tree_repository.dart index 0526e0c..1e0d584 100644 --- a/lib/repository/menu_tree_repository.dart +++ b/lib/repository/menu_tree_repository.dart @@ -9,12 +9,44 @@ import 'package:aitrainer_app/service/exercise_tree_service.dart'; import 'package:aitrainer_app/service/exercisetype_service.dart'; import 'package:flutter/material.dart'; +class Antagonist { + static String chest = "Chest"; + static int chestNr = 1; + static String biceps = "Biceps"; + static int bicepsNr = 2; + static String triceps = "Triceps"; + static int tricepsNr =3; + static String back = "Back"; + static int backNr = 4; + static String shoulder = "Shoulders"; + static int shoulderNr = 5; + static String core = "Core"; + static int coreNr = 6; + static String thigh = "Thigh"; + static int thighNr = 7; + static String calf = "Calf"; + static int calfNr = 8; +} + class MenuTreeRepository { final LinkedHashMap tree = LinkedHashMap<String, WorkoutTree>(); + SplayTreeMap sortedTree = SplayTreeMap<String, List<WorkoutTree>>(); + bool isEnglish; + + final Map<String, int> _antagonist = { + Antagonist.chest: Antagonist.chestNr, + Antagonist.biceps: Antagonist.bicepsNr, + Antagonist.triceps: Antagonist.tricepsNr, + Antagonist.back: Antagonist.backNr, + Antagonist.shoulder: Antagonist.shoulderNr, + Antagonist.core: Antagonist.coreNr, + Antagonist.thigh: Antagonist.thighNr, + Antagonist.calf: Antagonist.calfNr + }; Future<void> createTree() async { final AppLanguage appLanguage = AppLanguage(); - bool isEnglish = appLanguage.appLocal == Locale('en'); + isEnglish = appLanguage.appLocal == Locale('en'); print("** Start creating tree on lang: " + appLanguage.appLocal.toString()); List<ExerciseTree> exerciseTree = Cache().getExerciseTree(); @@ -25,7 +57,7 @@ class MenuTreeRepository { exerciseTree.forEach( (treeItem) async { String treeName = isEnglish ? treeItem.name : treeItem.nameTranslation; String assetImage = 'asset/menu/' + treeItem.imageUrl.substring(7); - bool is1RM = treeItem.name == '1RM' ? true : false; + bool is1RM = treeItem.name == 'One Rep Max' ? true : false; if ( is1RM == false && treeItem.parentId != 0) { is1RM = isParent1RM(treeItem.parentId); } @@ -34,12 +66,13 @@ class MenuTreeRepository { treeItem.parentId, treeName, assetImage, Colors.white, - 32, + 24, false, 0, null, false, - is1RM + is1RM, + treeItem.name, ); }); @@ -65,7 +98,8 @@ class MenuTreeRepository { exerciseType.exerciseTypeId, exerciseType, exerciseType.base, - is1RM + is1RM, + exerciseType.name ); }); @@ -81,7 +115,7 @@ class MenuTreeRepository { WorkoutTree treeItem = value as WorkoutTree; if ( treeItem.id == treeId ) { isTreeItem1RM = treeItem.is1RM; - //print (treeItem.name + " 1RM " + treeItem.is1RM.toString() ); + print (treeItem.name + " 1RM " + treeItem.is1RM.toString() ); } }); @@ -99,4 +133,34 @@ class MenuTreeRepository { }); return branch; } + + List<WorkoutTree> getBranchList(int parent) { + List branch = List<WorkoutTree>(); + tree.forEach((key, value) { + WorkoutTree workoutTree = value as WorkoutTree; + if ( parent == workoutTree.parent) { + branch.add(workoutTree); + } + }); + return branch; + } + + void sortByMuscleType() { + sortedTree = SplayTreeMap<String, List<WorkoutTree>>(); + tree.forEach((key, value) { + WorkoutTree workoutTree = value as WorkoutTree; + //print("treeitem: " + workoutTree.toJson().toString()); + /*if ( workoutTree.exerciseType != null) { + print("treeItem exerciseTye " + workoutTree.exerciseType.toJson().toString()); + } else { + print("treeItem exerciseType null " + workoutTree.toJson().toString()); + }*/ + if ( workoutTree.nameEnglish != 'One Rep Max' && workoutTree.is1RM && workoutTree.exerciseTypeId == 0) { + String treeName = _antagonist[workoutTree.nameEnglish].toString() + ". " + workoutTree.name; + sortedTree[treeName] = this.getBranchList(workoutTree.id); + } + }); + + return; + } } \ No newline at end of file diff --git a/lib/service/api.dart b/lib/service/api.dart index e172e96..3c750cc 100644 --- a/lib/service/api.dart +++ b/lib/service/api.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:aitrainer_app/util/common.dart'; +import 'package:aitrainer_app/util/not_found_exception.dart'; import 'package:http/http.dart' as http; import 'package:aitrainer_app/model/cache.dart'; @@ -22,6 +23,8 @@ class APIClient with Common { ); 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!"); } diff --git a/lib/service/customer_service.dart b/lib/service/customer_service.dart index 39f04b6..69ad1b4 100644 --- a/lib/service/customer_service.dart +++ b/lib/service/customer_service.dart @@ -5,64 +5,65 @@ import 'package:aitrainer_app/service/api.dart'; import 'package:aitrainer_app/model/cache.dart'; class CustomerApi { - final APIClient _client=new APIClient(); + final APIClient _client = new APIClient(); Future<List<Customer>> getRealCustomers(String param) async { final body = await _client.get("customers/", param); final Iterable json = jsonDecode(body); - final List<Customer> customers = json.map( (customer) => Customer.fromJson(customer) ).toList(); + final List<Customer> customers = json.map((customer) => + Customer.fromJson(customer)).toList(); return customers; } Future<void> saveCustomer(Customer customer) async { String body = JsonEncoder().convert(customer.toJson()); - print(" ===== saving customer id: " + customer.customerId.toString() + ":" + body ); + print(" ===== saving customer id: " + customer.customerId.toString() + ":" + + body); await _client.post( - "customers/"+customer.customerId.toString(), - body); + "customers/" + customer.customerId.toString(), + body); } Future<void> addCustomer(Customer customer) async { String body = JsonEncoder().convert(customer.toJson()); - print(" ===== add new customer: " + body ); + print(" ===== add new customer: " + body); await _client.post( - "customers", - body); + "customers", + body); } Future<void> addUser(User user) async { String body = JsonEncoder().convert(user.toJson()); - print(" ===== register new user: " + body ); + print(" ===== register new user: " + body); final String responseBody = await _client.post( - "registration", - body); + "registration", + body); Customer customer; try { int status = jsonDecode(responseBody)['status']; - if ( status != null ) { + if (status != null) { throw new Exception(jsonDecode(responseBody)['error']); } else { customer = Customer.fromJson(jsonDecode(responseBody)); Cache().afterRegistration(customer); } - } on FormatException catch(exception) { + } on FormatException catch (exception) { throw new Exception(responseBody); } - } Future<void> getUser(User user) async { String body = JsonEncoder().convert(user.toJson()); - print(" ===== login the user: " + body ); + print(" ===== login the user: " + body); final String responseBody = await _client.post( - "login", - body); + "login", + body); Customer customer; try { customer = Customer.fromJson(jsonDecode(responseBody)); await Cache().afterLogin(customer); - } on FormatException catch(exception) { + } on FormatException catch (exception) { throw new Exception(responseBody); } } @@ -70,18 +71,54 @@ class CustomerApi { Future<void> getCustomer(int customerId) async { String body = ""; - print(" ===== get the customer by id: " + customerId.toString() ); + print(" ===== get the customer by id: " + customerId.toString()); try { final String responseBody = await _client.get( - "customers/"+customerId.toString(), - body); + "customers/" + customerId.toString(), + body); Customer customer = Customer.fromJson(jsonDecode(responseBody)); + print(" --- Customer: " + customer.toJson().toString()); Cache().afterRegistration(customer); } catch (exception) { - print ("Exception: " + exception.toString()); - print (" === go to registration "); + print("Exception: " + exception.toString()); + print(" === go to registration "); Cache().logout(); Cache().startPage = "registration"; } } + + Future<Customer> getTrainee(int customerId) async { + String body = ""; + Customer customer; + print(" ===== get Trainee customer by id: " + customerId.toString()); + try { + final String responseBody = await _client.get( + "customers/" + customerId.toString(), + body); + customer = Customer.fromJson(jsonDecode(responseBody)); + print(" --- Trainee: " + customer.toJson().toString()); + } catch (exception) { + print("Exception: " + exception.toString()); + throw Exception(exception); + } + return customer; + } + + Future<List<Customer>> getTrainees(int trainerId) async { + List<Customer> trainees = List<Customer>(); + print("Get trainees list"); + try { + String body = ""; + final String responseBody = await _client.get( + "customers/trainees/" + trainerId.toString(), + body + ); + final Iterable json = jsonDecode(responseBody); + trainees = json.map((customer) => Customer.fromJson(customer)).toList(); + } catch (exception) { + print("Exception: " + exception.toString()); + throw Exception(exception); + } + return trainees; + } } \ No newline at end of file diff --git a/lib/service/exercise_plan_service.dart b/lib/service/exercise_plan_service.dart new file mode 100644 index 0000000..6d61036 --- /dev/null +++ b/lib/service/exercise_plan_service.dart @@ -0,0 +1,145 @@ +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'; +import 'dart:convert'; +import 'api.dart'; + +class ExercisePlanApi { + final APIClient _client = new APIClient(); + + Future<ExercisePlan> saveExercisePlan(ExercisePlan exercisePlan) async { + String body = JsonEncoder().convert(exercisePlan.toJson()); + print(" ===== saving exercisePlan $exercisePlan"); + ExercisePlan savedExercisePlan; + try { + final String responseBody = await _client.post( + "exercise_plan", + body); + savedExercisePlan = ExercisePlan.fromJson(jsonDecode(responseBody)); + Cache().setTraineeExercisePlan(savedExercisePlan); + } on Exception catch(e) { + throw new Exception(e.toString()); + } + return savedExercisePlan; + } + + Future<ExercisePlan> updateExercisePlan( + ExercisePlan exercisePlan, + int exercisePlanId) async { + String body = JsonEncoder().convert(exercisePlan.toJson()); + print(" ===== saving 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()); + } + return updatedExercisePlan; + } + + Future<ExercisePlanDetail> saveExercisePlanDetail(ExercisePlanDetail exercisePlanDetail) async { + String body = JsonEncoder().convert(exercisePlanDetail.toJson()); + print(" ===== update exercisePlanDetail $exercisePlanDetail"); + ExercisePlanDetail savedExercisePlanDetail; + try { + final String responseBody = await _client.post( + "exercise_plan_detail", + body); + savedExercisePlanDetail = ExercisePlanDetail.fromJson(jsonDecode(responseBody)); + } on Exception catch(e) { + throw new Exception(e.toString()); + } + return savedExercisePlanDetail; + } + + Future<ExercisePlanDetail> updateExercisePlanDetail(ExercisePlanDetail exercisePlanDetail, + int exercisePlanDetailId) async { + String body = JsonEncoder().convert(exercisePlanDetail.toJson()); + print(" ===== update exercisePlanDetail $exercisePlanDetail"); + ExercisePlanDetail savedExercisePlanDetail; + try { + final String responseBody = await _client.post( + "exercise_plan_detail/" + exercisePlanDetailId.toString(), + body); + savedExercisePlanDetail = ExercisePlanDetail.fromJson(jsonDecode(responseBody)); + } on Exception catch(e) { + throw new Exception(e.toString()); + } + return savedExercisePlanDetail; + } + + Future<void> deleteExercisePlanDetail(int exercisePlanDetailId) async { + print(" ===== delete exercisePlanDetail $exercisePlanDetailId"); + String body = ""; + try { + await _client.post( + "exercise_plan_detail/delete/" + exercisePlanDetailId.toString(), + body); + + } on Exception catch(e) { + throw new Exception(e.toString()); + } + return; + } + + Future<void> deleteExercisePlan(int exercisePlanId) async { + String body = ""; + print(" ===== delete exercisePlan $exercisePlanId"); + + try { + await _client.post( + "exercise_plan/delete/" + exercisePlanId.toString(), + body); + + } on Exception catch(e) { + throw new Exception(e.toString()); + } + return; + } + + Future<ExercisePlan> getLastExercisePlan(int customerId) async { + String body = ""; + print(" ===== get last exercisePlan $customerId"); + ExercisePlan exercisePlan; + try { + final String responseBody = await _client.get( + "exercise_plan/last/" + customerId.toString(), + body); + exercisePlan = ExercisePlan.fromJson(jsonDecode(responseBody)); + } on Exception catch(e) { + if ( e is NotFoundException) { + print("ExercisePlan not found for " + customerId.toString()); + return exercisePlan; + } else { + throw new Exception(e.toString()); + } + } + return exercisePlan; + } + + Future<List<ExercisePlanDetail>> getExercisePlanDetail(int exercisePlanId) async { + String body = ""; + print(" ===== get exercisePlanDetail $exercisePlanId"); + List<ExercisePlanDetail> listExercisePlanDetail; + try { + final String responseBody = await _client.get( + "exercise_plan_detail/" + exercisePlanId.toString(), + body); + print("response body:" + responseBody); + final Iterable json = jsonDecode(responseBody); + listExercisePlanDetail = json.map( (planDetail) => ExercisePlanDetail.fromJson(planDetail) ) .toList(); + } on Exception catch(e) { + throw new Exception(e.toString()); + } + return listExercisePlanDetail; + } + + + + +} \ No newline at end of file diff --git a/lib/service/exercise_service.dart b/lib/service/exercise_service.dart index f08831e..6648f99 100644 --- a/lib/service/exercise_service.dart +++ b/lib/service/exercise_service.dart @@ -22,12 +22,14 @@ class ExerciseApi { body); } - Future<void> addExercise(Exercise exercise) async { + Future<Exercise> addExercise(Exercise exercise) async { String body = JsonEncoder().convert(exercise.toJson()); print(" ===== add new exercise: " + body ); - await _client.post( + final String response = await _client.post( "exercises", body); + final Exercise savedExercise = Exercise.fromJson(jsonDecode(response)); + return savedExercise; } Future<List<Exercise>> getExercisesByCustomer(int customerId ) async { diff --git a/lib/util/not_found_exception.dart b/lib/util/not_found_exception.dart new file mode 100644 index 0000000..ab56186 --- /dev/null +++ b/lib/util/not_found_exception.dart @@ -0,0 +1,4 @@ +class NotFoundException implements Exception { + final String message; + const NotFoundException({this.message}); +} \ No newline at end of file diff --git a/lib/util/session.dart b/lib/util/session.dart index bcbe973..683610e 100644 --- a/lib/util/session.dart +++ b/lib/util/session.dart @@ -27,7 +27,7 @@ class Session { if ( Cache().firstLoad ) { print (" -- Session: fetch locale.."); - await appLanguage.fetchLocale(); + await appLanguage.getLocale(_sharedPreferences); await AppLocalizations.delegate.load(appLanguage.appLocal); print (" -- Session: fetch token.."); await _fetchToken(_sharedPreferences); @@ -95,7 +95,6 @@ class Session { //Navigator.of(context).pushNamed('login'); Cache().startPage = "login"; } else { - print("************** Store SharedPreferences"); // get API customer customerId = prefs.getInt(Cache.customerIdKey); await CustomerApi().getCustomer(customerId); diff --git a/lib/view/account.dart b/lib/view/account.dart index 485d53d..e70df1e 100644 --- a/lib/view/account.dart +++ b/lib/view/account.dart @@ -1,5 +1,7 @@ import 'package:aitrainer_app/bloc/account/account_bloc.dart'; +import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/localization/app_localization.dart'; +import 'package:aitrainer_app/model/customer.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:aitrainer_app/widgets/bottom_nav.dart'; import 'package:flutter/material.dart'; @@ -7,6 +9,7 @@ import 'package:flutter/cupertino.dart'; // ignore: must_be_immutable class AccountPage extends StatelessWidget { + // ignore: close_sinks AccountBloc accountBloc; @override @@ -93,7 +96,7 @@ class AccountPage extends StatelessWidget { ), ), loginOut( context, accountBloc ), - //exercises(exerciseChangingViewModel), + getMyTrainees(context, accountBloc), ]); } @@ -137,93 +140,72 @@ class AccountPage extends StatelessWidget { return element; } - /* ListTile exercises( ExerciseChangingViewModel model ) { - ListTile element = ListTile(); - if ( Auth().userLoggedIn == null ) { - return element; + Widget getMyTrainees( BuildContext context, AccountBloc accountBloc ) { + if ( accountBloc.customerRepository.customer == null ) { + return Container(); } - - element = ListTile( - title: Text(AppLocalizations.of(context).translate("Exercises")), - subtitle: Column( - children: [ - FutureBuilder<List<ExerciseViewModel>>( - future: _exercises, - builder: (context, snapshot) { - if (snapshot.hasData) { - return getExercises( model );//CustomerListWidget(customers: _exerciseViewModel.exerciseList); - } else if (snapshot.hasError) { - return Text("${snapshot.error}"); - } - - // By default, show a loading spinner. - return CircularProgressIndicator(); - } - ),] - )); - - return element; - } -*/ - /* - Widget getExercises( ExerciseChangingViewModel model ) { - List<ExerciseViewModel> exercises = model.exerciseList; - - Column element = Column(); - if (exercises.length > 0) { - List<Column> rows = List(); - - exercises.forEach((exercise) { - String exerciseName = AppLocalizations.of(context).translate( - Common.getExerciseType(exercise.getExercise().exerciseTypeId).name); - - String quantity = exercise.getExercise().quantity.toString() + " " + - AppLocalizations.of(context).translate(exercise.getExercise().unit); - - String unitQuantity = ""; - String unitQuantityUnit = ""; - String date = Common.getDateLocale(exercise.getExercise().dateAdd, false); - if (exercise.getExercise().unitQuantity != null) { - unitQuantity = exercise.getExercise().unitQuantity.toString(); - unitQuantityUnit = AppLocalizations.of(context).translate( - Common.getExerciseType(exercise.getExercise().exerciseTypeId).unitQuantityUnit); - } - - TableRow row = TableRow( - children: [ - Text(date), - Text(exerciseName), - Text(quantity), - - Text(unitQuantity + " " + unitQuantityUnit), - ] - ); - - Table table = Table( - defaultColumnWidth: FractionColumnWidth(0.28), - children: [row], - ); - - Column col = Column( - children: [ - table, - Row( - children: [ - Text(" "), - ] - ) - ], - ); - rows.add(col); - }); - - - element = Column( - children: rows, + if ( accountBloc.customerRepository.customer.trainer == 0 ) { + return ListTile( + title: Container(), ); } - return element; + if (accountBloc.customerRepository.getTraineesList() == null ) { + return ListTile( + leading: Icon(Icons.people), + title: RaisedButton( + color: Colors.white70, + onPressed: () => accountBloc.add(AccountGetTrainees()), + child: Text("See my trainees"), + ), + ); + + } + + List<Widget> elements = List<Widget>(); + accountBloc.customerRepository.getTraineesList().forEach((element) { + Customer trainee = element; + String name = trainee.name; + String firstName = trainee.firstname; + String nodeName = AppLanguage().appLocal == Locale("en") ? + firstName + " " + name : name + " " + firstName; + + bool selected = accountBloc.traineeId == trainee.customerId; + + Widget widget = FlatButton( + padding: EdgeInsets.all(10), + shape:RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + side: BorderSide(width: 2, color: selected ? Colors.blue : Colors.black26 ), + ), + onPressed: () { + accountBloc.add(AccountSelectTrainee(traineeId: trainee.customerId)); + //Navigator.of(context).pushNamed('login'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(nodeName, style: + TextStyle( + color: selected ? Colors.blue : Colors.black54, + fontWeight: selected ? FontWeight.bold : FontWeight.normal + ), + ), + Icon(Icons.arrow_forward_ios), + ]), + ); + elements.add(widget); + }); + + return ListTile( + leading: Icon(Icons.people), + subtitle: Text("My Trainees"), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: elements, + ) + ); + } - } */ } diff --git a/lib/view/exercise_add_by_plan_page.dart b/lib/view/exercise_add_by_plan_page.dart new file mode 100644 index 0000000..91ebc3d --- /dev/null +++ b/lib/view/exercise_add_by_plan_page.dart @@ -0,0 +1,241 @@ +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/localization/app_localization.dart'; +import 'package:aitrainer_app/model/workout_tree.dart'; +import 'package:aitrainer_app/repository/exercise_plan_repository.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:aitrainer_app/widgets/splash.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; + +class ExerciseAddByPlanPage extends StatefulWidget{ + _ExerciseAddByPlanPage createState() => _ExerciseAddByPlanPage(); +} + +class _ExerciseAddByPlanPage extends State<ExerciseAddByPlanPage> { + + @override + Widget build(BuildContext context) { + LinkedHashMap arguments = ModalRoute.of(context).settings.arguments; + // ignore: close_sinks + final ExerciseByPlanBloc bloc = arguments['blocExerciseByPlan']; + final int customerId = arguments['customerId']; + final WorkoutTree workoutTree = arguments['workoutTree']; + final ExerciseRepository exerciseRepository = ExerciseRepository(); + + return BlocProvider( + create: (context) => + ExerciseAddByPlanFormBloc( + exerciseRepository: exerciseRepository, + exercisePlanRepository: bloc.exercisePlanRepository, + customerId: customerId, + workoutTree: workoutTree), + child: BlocBuilder<ExerciseAddByPlanFormBloc, FormBlocState>( + builder: (context, state) { + // ignore: close_sinks + final exerciseBloc = BlocProvider.of<ExerciseAddByPlanFormBloc>(context); + if ( state is FormBlocLoading ) { + return LoadingDialog(); + } else if ( state is FormBlocSuccess) { + return getControlForm(exerciseBloc); + } else { + return getControlForm(exerciseBloc); + } + } + )); + } + + Form getControlForm( ExerciseAddByPlanFormBloc exerciseBloc) { + String exerciseName = AppLanguage().appLocal == Locale("en") ? + exerciseBloc.exerciseRepository.exerciseType.name : + exerciseBloc.exerciseRepository.exerciseType.nameTranslation; + + return Form( + autovalidate: true, + child: Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + backgroundColor: Colors.black, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + Text("Add Exercise"), + Image.asset( + 'asset/image/WT_long_logo.png', + fit: BoxFit.cover, + height: 65.0, + ), + ], + ), + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: Container( + width: MediaQuery + .of(context) + .size + .width, + height: MediaQuery + .of(context) + .size + .height, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_light_background.png'), + fit: BoxFit.fill, + alignment: Alignment.center, + ), + ), + child: Container( + padding: const EdgeInsets.only (top: 25, left: 25, right: 25), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: <Widget>[ + Text(exerciseName, + style: TextStyle(fontWeight: FontWeight.bold, + fontSize: 18, + color: Colors.deepOrange), + overflow: TextOverflow.fade, + maxLines: 1, + softWrap: true, + ), + Divider(color: Colors.transparent,), + + Divider(), + Column( + children: repeatExercises(exerciseBloc), + + ), + Divider(), + + + + ]), + ) + ) + ), + ), + ); + } + + List<Column> repeatExercises(ExerciseAddByPlanFormBloc exerciseBloc) { + List<Column> listColumns = List<Column>(); + for ( int i = 0; i < exerciseBloc.countSteps; i++) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Divider(color: Colors.transparent,), + Text("Execute the " + (i+1).toString() + ". 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: AppLocalizations.of(context) + .translate("The number of the exercise"), + labelStyle: TextStyle(fontSize: 12, color: Colors.deepOrange, fontWeight: FontWeight.normal), + labelText: "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, + color: Colors.black54, + 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), + labelStyle: TextStyle(fontSize: 12, color: Colors.deepOrange, fontWeight: FontWeight.normal), + labelText: exerciseBloc.exerciseRepository.exerciseType.unitQuantityUnit, + ), + ), + RaisedButton( + + padding: EdgeInsets.all(0), + textColor: Colors.white, + color: exerciseBloc.step == i+1 ? Colors.blue : Colors.black26, + focusColor: Colors.blueAccent, + onPressed: () => + { + print ("Submit step " + exerciseBloc.step.toString() + " (i) " + i.toString()), + if ( exerciseBloc.step == i+1 ) { + exerciseBloc.submit() + }, + if ( i+1 == exerciseBloc.countSteps) { + Navigator.of(context).pop() + } + }, + child: Text( + AppLocalizations.of(context).translate("Check"), + style: TextStyle(fontSize: 12),) + ), + Divider(color: Colors.transparent,), + ], + ); + listColumns.add(col); + } + return listColumns; + } + + String validateNumberInput(input) { + String error = AppLocalizations.of(context).translate( + "Please type the right quantity 0-10000"); + dynamic rc = (input != null && input.length > 0); + if (!rc) { + return null; + } + + Pattern pattern = r'^\d+(?:\.\d+)?$'; + RegExp regex = new RegExp(pattern); + if (!regex.hasMatch(input)) { + return error; + } + + rc = double.tryParse(input); + if (rc == null) { + return error; + } + + + if (!(double.parse(input) < 10000 && double.parse(input) > 0)) { + return error; + } + + return null; + } +} diff --git a/lib/view/exercise_execute_by_plan_page.dart b/lib/view/exercise_execute_by_plan_page.dart new file mode 100644 index 0000000..d900587 --- /dev/null +++ b/lib/view/exercise_execute_by_plan_page.dart @@ -0,0 +1,187 @@ +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/widgets/app_bar_common.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<ExerciseByPlanPage> { + final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); + // ignore: close_sinks + ExerciseByPlanBloc bloc; + + @override + void initState() { + super.initState(); + + /// We require the initializers to run after the loading screen is rendered + SchedulerBinding.instance.addPostFrameCallback((_) { + BlocProvider.of<ExerciseByPlanBloc>(context).add(ExerciseByPlanLoad()); + }); + } + + @override + Widget build(BuildContext context) { + LinkedHashMap arguments = ModalRoute.of(context).settings.arguments; + final int customerId = arguments['customerId']; + bloc = BlocProvider.of<ExerciseByPlanBloc>(context); + bloc.customerId = customerId; + + 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, + ), + ), + child: BlocConsumer<ExerciseByPlanBloc, ExerciseByPlanState>(listener: (context, state) { + if (state is ExerciseByPlanError) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + state.message, + ), + backgroundColor: Colors.orange, + )); + } else if (state is ExerciseByPlanLoading) { + LoadingDialog(); + } + }, + // ignore: missing_return + 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) { + return exerciseWidget(bloc); + } + }))); + } + + 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<dynamic> 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<dynamic> 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, + ); + } + + List<Node> nodeExercisePlan(ExerciseByPlanBloc bloc) { + List<Node> nodes = List<Node>(); + Node actualNode; + bool isEnglish = AppLanguage().appLocal == Locale("en"); + + bloc.menuTreeRepository.sortedTree.forEach((name, list) { + List<WorkoutTree> listWorkoutItem = list; + List<Node> listExerciseTypePerMuscle = List<Node>(); + 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<Node<dynamic>> planDetailList = List<Node<dynamic>>(); + 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); + } + }); + + return nodes; + } +} diff --git a/lib/view/exercise_log_page.dart b/lib/view/exercise_log_page.dart new file mode 100644 index 0000000..3d1c3d6 --- /dev/null +++ b/lib/view/exercise_log_page.dart @@ -0,0 +1,148 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/localization/app_language.dart'; +import 'package:aitrainer_app/localization/app_localization.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/widgets/app_bar_common.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_treeview/tree_view.dart'; + +class ExerciseLogPage extends StatefulWidget { + @override + _ExerciseLogPage createState() => _ExerciseLogPage(); +} + +class _ExerciseLogPage extends State<ExerciseLogPage> { + @override + 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']; + + return Scaffold( + 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: exerciseWidget(exerciseRepository, customerId), + ) + ); + } + + Widget exerciseWidget(ExerciseRepository exerciseRepository, int customerId) { + TreeViewController _treeViewController = + TreeViewController(children: nodeExercises(exerciseRepository, customerId)); + + TreeViewTheme _treeViewTheme = TreeViewTheme( + expanderTheme: ExpanderThemeData( + type: ExpanderType.caret, + modifier: ExpanderModifier.none, + position: ExpanderPosition.start, + color: Colors.red.shade800, + size: 20, + ), + labelStyle: TextStyle( + fontSize: 12, + letterSpacing: 0.1, + ), + parentLabelStyle: TextStyle( + fontSize: 16, + letterSpacing: 0.1, + fontWeight: FontWeight.w800, + color: Colors.orange.shade600, + ), + iconTheme: IconThemeData( + size: 18, + color: Colors.grey.shade800, + ), + colorScheme: ColorScheme.light(background: Colors.transparent), + ); + + return TreeView( + controller: _treeViewController, + allowParentSelect: false, + supportParentDoubleTap: false, + //onExpansionChanged: _expandNodeHandler, + onNodeTap: (key) { + setState(() { + _treeViewController = _treeViewController.copyWith(selectedKey: key); + }); + }, + theme: _treeViewTheme, + ); + } + + List<Node> nodeExercises(ExerciseRepository exerciseRepository, int customerId) { + List<Node> nodes = List<Node>(); + List<Exercise> exercises; + if ( customerId == Cache().userLoggedIn.customerId ) { + exercises = exerciseRepository.getExerciseList(); + } else if ( Cache().getTrainee() != null && customerId == Cache().getTrainee().customerId ) { + exercises = exerciseRepository.getExerciseListTrainee(); + } + + + String prevDay = ""; + Node actualNode; + List<Node> listExercisesPerDay; + exercises.forEach((element) { + Exercise exercise = element; + ExerciseType exerciseType = + exerciseRepository.getExerciseTypeById(exercise.exerciseTypeId); + String actualDay = exercise.dateAdd.year.toString() + + "-" + + exercise.dateAdd.month.toString() + + "-" + + exercise.dateAdd.day.toString(); + + if (prevDay.compareTo(actualDay) != 0) { + listExercisesPerDay = List<Node>(); + actualNode = Node( + label: actualDay, + key: exercise.dateAdd.toString(), + expanded: true, + children: listExercisesPerDay, + icon: + NodeIcon(codePoint: Icons.date_range.codePoint, color: "blue")); + nodes.add(actualNode); + prevDay = actualDay; + } + + String exerciseName = AppLanguage().appLocal == Locale("en") + ? exerciseType.name + : exerciseType.nameTranslation; + String unitQuantity = exerciseType.unitQuantity == "1" + ? exercise.unitQuantity.toStringAsFixed(0) + + " " + + AppLocalizations.of(context) + .translate(exerciseType.unitQuantityUnit) + + " " + : ""; + + String labelExercise = exerciseName + + " " + + unitQuantity + + exercise.quantity.toStringAsFixed(0) + + " " + + AppLocalizations.of(context).translate(exercise.unit); + listExercisesPerDay.add(Node( + label: labelExercise, + key: exercise.exerciseId.toString(), + expanded: false, + icon: NodeIcon(codePoint: Icons.repeat.codePoint, color: "blue"))); + }); + return nodes; + } +} diff --git a/lib/view/exercise_plan_custom_page.dart b/lib/view/exercise_plan_custom_page.dart new file mode 100644 index 0000000..9beb3ef --- /dev/null +++ b/lib/view/exercise_plan_custom_page.dart @@ -0,0 +1,190 @@ +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/widgets/app_bar_common.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<ExercisePlanCustomPage> { + final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); + // ignore: close_sinks + ExercisePlanBloc bloc; + + @override + void initState() { + super.initState(); + + /// We require the initializers to run after the loading screen is rendered + SchedulerBinding.instance.addPostFrameCallback((_) { + BlocProvider.of<ExercisePlanBloc>(context).add(ExercisePlanLoad()); + }); + } + + @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<ExercisePlanBloc>(context); + bloc.customerId = customerId; + + 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, + ), + ), + child: BlocConsumer<ExercisePlanBloc, ExercisePlanState>(listener: (context, state) { + if (state is ExercisePlanError) { + //showInSnackBar(state.message); + //return exerciseWidget(bloc); + 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) { + 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(); + } + }))); + } + + 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<dynamic> 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, + ); + } + + List<Node> nodeExercisePlan(ExercisePlanBloc bloc) { + List<Node> nodes = List<Node>(); + Node actualNode; + bool isEnglish = AppLanguage().appLocal == Locale("en"); + + bloc.menuTreeRepository.sortedTree.forEach((name, list) { + List<WorkoutTree> listWorkoutItem = list; + List<Node> listExerciseTypePerMuscle = List<Node>(); + NodeIcon icon; + listWorkoutItem.forEach((element) { + WorkoutTree treeItem = element; + icon = + treeItem.selected == true ? NodeIcon(codePoint: Icons.bubble_chart.codePoint, color: "blueAccent") : null; + + String exerciseLabel = isEnglish + ? treeItem.name + : treeItem.exerciseType == null ? treeItem.name : treeItem.exerciseType.nameTranslation; + + List<Node<dynamic>> planDetailList = List<Node<dynamic>>(); + 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); + } + }); + + return nodes; + } +} diff --git a/lib/view/exercise_plan_detail_add_page.dart b/lib/view/exercise_plan_detail_add_page.dart new file mode 100644 index 0000000..1216b81 --- /dev/null +++ b/lib/view/exercise_plan_detail_add_page.dart @@ -0,0 +1,144 @@ + +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/repository/exercise_plan_repository.dart'; +import 'package:aitrainer_app/widgets/app_bar_common.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; + +class ExercisePlanDetailAddPage extends StatefulWidget { + @override + _ExercisePlanDetailAddPage createState() => _ExercisePlanDetailAddPage(); +} + +class _ExercisePlanDetailAddPage extends State<ExercisePlanDetailAddPage> { + @override + Widget build(BuildContext context) { + // ignore: close_sinks + final ExercisePlanBloc planBloc = ModalRoute.of(context).settings.arguments; + final ExercisePlanRepository exercisePlanRepository = planBloc.exercisePlanRepository; + + return BlocProvider( + create: (context) => ExercisePlanCustomerFormBloc(exercisePlanRepository: exercisePlanRepository, planBloc: planBloc), + child: Builder(builder: (context) { + // ignore: close_sinks + final bloc = BlocProvider.of<ExercisePlanCustomerFormBloc>(context); + + String exerciseName = ""; + if (bloc != null) { + exerciseName = AppLanguage().appLocal == Locale("en") + ? bloc.exercisePlanRepository.actualPlanDetail.exerciseType.name + : bloc.exercisePlanRepository.actualPlanDetail.exerciseType.nameTranslation; + } + + return Form( + autovalidate: true, + child: Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBarCommonNav(), + body: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_light_background.png'), + fit: BoxFit.fill, + alignment: Alignment.center, + ), + ), + child: Container( + padding: const EdgeInsets.only(top: 25, left: 25, right: 25), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ + Text(AppLocalizations.of(context).translate('Save The Exercise To The Exercise Plan'), + style: TextStyle(fontSize: 14, color: Colors.blueAccent)), + Text( + exerciseName, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.deepOrange), + overflow: TextOverflow.fade, + maxLines: 1, + softWrap: true, + ), + TextFieldBlocBuilder( + textFieldBloc: bloc.serieField, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, color: Colors.lightBlue, fontWeight: FontWeight.bold), + inputFormatters: [WhitelistingTextInputFormatter(RegExp(r"[\d.]"))], + decoration: InputDecoration( + fillColor: Colors.white, + filled: false, + hintStyle: TextStyle(fontSize: 16, color: Colors.black54, fontWeight: FontWeight.w100), + hintText: AppLocalizations.of(context).translate("The number of the serie done with"), + labelStyle: TextStyle(fontSize: 16, color: Colors.lightBlue), + labelText: AppLocalizations.of(context).translate("Serie"), + ), + ), + TextFieldBlocBuilder( + textFieldBloc: bloc.quantityField, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, color: Colors.lightBlue, fontWeight: FontWeight.bold), + inputFormatters: [WhitelistingTextInputFormatter(RegExp(r"[\d.]"))], + decoration: InputDecoration( + fillColor: Colors.white, + filled: false, + hintStyle: TextStyle(fontSize: 16, color: Colors.black54, fontWeight: FontWeight.w100), + hintText: AppLocalizations.of(context).translate("The number of the repeats of one serie"), + labelStyle: TextStyle(fontSize: 16, color: Colors.lightBlue), + labelText: AppLocalizations.of(context).translate("Repeats"), + ), + ), + TextFieldBlocBuilder( + textFieldBloc: bloc.weightField, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, color: Colors.lightBlue, fontWeight: FontWeight.bold), + inputFormatters: [WhitelistingTextInputFormatter(RegExp(r"[\d.]"))], + decoration: InputDecoration( + fillColor: Colors.white, + filled: false, + hintStyle: TextStyle(fontSize: 16, color: Colors.black54, fontWeight: FontWeight.w100), + hintText: AppLocalizations.of(context).translate("The weight"), + labelStyle: TextStyle(fontSize: 16, color: Colors.lightBlue), + labelText: AppLocalizations.of(context).translate("Weight"), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + RaisedButton( + textColor: Colors.white, + color: Colors.red.shade300, + focusColor: Colors.white, + onPressed: () => { + print("Remove " + bloc.exercisePlanRepository.actualPlanDetail.exerciseType.name), + planBloc.add(ExercisePlanRemoveExercise(exercisePlanDetail: bloc.exercisePlanRepository.actualPlanDetail)), + Navigator.of(context).pop(), + }, + child: Text("Delete"), //Text(AppLocalizations.of(context).translate("Delete"), style: TextStyle(fontSize: 16),) + ), + RaisedButton( + textColor: Colors.white, + color: Colors.blueAccent, + focusColor: Colors.white, + onPressed: () => { + bloc.submit(), + Navigator.of(context).pop(), + }, + child: Text( + AppLocalizations.of(context).translate("Save"), + style: TextStyle(fontSize: 16), + )), + ], + ), + ]), + ))), + ), + ); + })); + } +} diff --git a/lib/view/mydevelopment_page.dart b/lib/view/mydevelopment_page.dart deleted file mode 100644 index b6324e1..0000000 --- a/lib/view/mydevelopment_page.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:aitrainer_app/localization/app_language.dart'; -import 'package:aitrainer_app/localization/app_localization.dart'; -import 'package:aitrainer_app/model/exercise.dart'; -import 'package:aitrainer_app/model/exercise_type.dart'; -import 'package:aitrainer_app/repository/exercise_repository.dart'; -import 'package:aitrainer_app/widgets/app_bar.dart'; -import 'package:aitrainer_app/widgets/bottom_nav.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_treeview/tree_view.dart'; - -class MyDevelopmentPage extends StatefulWidget { - @override - _MyDevelopmentPage createState() => _MyDevelopmentPage(); -} - -class _MyDevelopmentPage extends State<MyDevelopmentPage> { - @override - Widget build(BuildContext context) { - final ExerciseRepository exerciseRepository = ExerciseRepository(); - - return Scaffold( - appBar: AppBarNav(), - body: Container( - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('asset/image/WT_light_background.png'), - fit: BoxFit.cover, - alignment: Alignment.center, - ), - ), - child: Container( - padding: EdgeInsets.all(10), - child: - exerciseWidget(exerciseRepository), - - ) - ), - bottomNavigationBar: BottomNavigator(bottomNavIndex: 1)); - } - - Widget exerciseWidget(ExerciseRepository exerciseRepository) { - TreeViewController _treeViewController = TreeViewController(children: nodeExercises(exerciseRepository) ); - - TreeViewTheme _treeViewTheme = TreeViewTheme( - expanderTheme: ExpanderThemeData( - type: ExpanderType.caret, - modifier: ExpanderModifier.none, - position: ExpanderPosition.start, - color: Colors.red.shade800, - size: 20, - ), - labelStyle: TextStyle( - fontSize: 12, - letterSpacing: 0.1, - ), - parentLabelStyle: TextStyle( - fontSize: 16, - letterSpacing: 0.1, - fontWeight: FontWeight.w800, - color: Colors.orange.shade600, - ), - iconTheme: IconThemeData( - size: 18, - color: Colors.grey.shade800, - ), - colorScheme: ColorScheme.light( - background: Colors.transparent - ), - ); - - return TreeView( - controller: _treeViewController, - allowParentSelect: false, - supportParentDoubleTap: false, - //onExpansionChanged: _expandNodeHandler, - onNodeTap: (key) { - setState(() { - _treeViewController = _treeViewController.copyWith(selectedKey: key); - }); - }, - theme: _treeViewTheme, - ); - } - - List<Node> nodeExercises(ExerciseRepository exerciseRepository) { - List<Node> nodes = List<Node>(); - List<Exercise> exercises = exerciseRepository.getExerciseList(); - - String prevDay = ""; - Node actualNode; - List<Node> listExercisesPerDay; - exercises.forEach((element) { - Exercise exercise = element; - ExerciseType exerciseType = - exerciseRepository.getExerciseTypeById(exercise.exerciseTypeId); - String actualDay = exercise.dateAdd.year.toString()+"-"+ - exercise.dateAdd.month.toString()+"-"+ - exercise.dateAdd.day.toString(); - - if ( prevDay.compareTo(actualDay) != 0) { - listExercisesPerDay = List<Node>(); - actualNode = - Node( - label: actualDay, - key: exercise.dateAdd.toString(), - expanded: true, - children: listExercisesPerDay, - icon: NodeIcon( - codePoint: Icons.date_range.codePoint, - color: "blue" - ) - ); - nodes.add(actualNode); - prevDay = actualDay; - } - - String exerciseName = AppLanguage().appLocal == Locale("en") ? - exerciseType.name : - exerciseType.nameTranslation; - String unitQuantity = exerciseType.unitQuantity == "1" ? - exercise.unitQuantity.toStringAsFixed(0) - + " " + AppLocalizations.of(context).translate(exerciseType.unitQuantityUnit) + " " - : ""; - - String labelExercise = - exerciseName + " " + unitQuantity - + exercise.quantity.toStringAsFixed(0) + " " - + AppLocalizations.of(context).translate(exercise.unit); - listExercisesPerDay.add( - Node( - label: labelExercise, - key: exercise.exerciseId.toString(), - expanded: false, - icon: NodeIcon( - codePoint: Icons.repeat.codePoint, - color: "blue" - ) - ) - ); - - - }); - return nodes; - } -} - - diff --git a/lib/view/myexcercise_plan_page.dart b/lib/view/myexcercise_plan_page.dart new file mode 100644 index 0000000..68351d4 --- /dev/null +++ b/lib/view/myexcercise_plan_page.dart @@ -0,0 +1,196 @@ +import 'dart:collection'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +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'; + +class MyExercisePlanPage extends StatefulWidget { + @override + _MyExercisePlanPage createState() => _MyExercisePlanPage(); +} + +class _MyExercisePlanPage extends State<MyExercisePlanPage> { + @override + Widget build(BuildContext context) { + final ExerciseRepository exerciseRepository = ExerciseRepository(); + final LinkedHashMap args = LinkedHashMap(); + + return Scaffold( + appBar: AppBarCommonNav(), + body: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_light_background.png'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: CustomScrollView( + scrollDirection: Axis.vertical, + slivers: + [ + SliverGrid( + delegate: SliverChildListDelegate( + [ + FlatButton( + padding: EdgeInsets.all(10), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + args['customerId'] = Cache().userLoggedIn.customerId, + Navigator.of(context).pushNamed('exerciseByPlanPage', + arguments: args) + }, + child: Text("Execute My Selected Training Plan", + style: TextStyle(fontSize: 18),) + ), + + FlatButton( + padding: EdgeInsets.all(0), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + args['exerciseRepository'] = exerciseRepository, + args['customerId'] = Cache().userLoggedIn.customerId, + Navigator.of(context).pushNamed('exercisePlanCustomPage', + arguments: args) + }, + child: Text("Edit My Custom Plan", + style: TextStyle(fontSize: 18),) + ), + FlatButton( + padding: EdgeInsets.all(20), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + + }, + child: Text("Suggested Plan", + style: TextStyle(fontSize: 18),) + ), + Stack( + fit: StackFit.passthrough, + overflow: Overflow.clip, + alignment: Alignment.topLeft, + children: [ + Image.asset('asset/image/lock.png', + height: 40, + width: 40, + ), + FlatButton( + padding: EdgeInsets.all(20), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + + }, + child: Text("My Special Plan", + style: TextStyle(fontSize: 18),) + ), + + ], + ), + + Stack( + fit: StackFit.passthrough, + overflow: Overflow.clip, + children: [ + Image.asset('asset/image/lock.png', + height: 40, + width: 40, + ), + FlatButton( + padding: EdgeInsets.all(20), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + + }, + child: Text("My Arnold's Plan", + style: TextStyle(fontSize: 18),) + ), + + ] + ), + + hiddenPlanWidget(exerciseRepository), + hiddenTrainingWidget(), + + + ] + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 20.0, + crossAxisSpacing: 20.0, + childAspectRatio: 1.2, + ), + ) + ] + ) + ), + bottomNavigationBar: BottomNavigator(bottomNavIndex: 2)); + } + + Widget hiddenPlanWidget(ExerciseRepository exerciseRepository) { + final LinkedHashMap args = LinkedHashMap(); + if ( Cache().getTrainee() != null ) { + return FlatButton( + padding: EdgeInsets.all(20), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + args['exerciseRepository'] = exerciseRepository, + args['customerId'] = Cache().getTrainee().customerId, + Navigator.of(context).pushNamed('exercisePlanCustomPage', + arguments: args) + }, + child: Text("My Trainee's Plan", + style: TextStyle(fontSize: 18),) + ); + } else { + return Container(); + } + } + + Widget hiddenTrainingWidget() { + final LinkedHashMap args = LinkedHashMap(); + if ( Cache().getTrainee() != null ) { + print ("!!Trainee: " + Cache().getTrainee().firstname + " " + Cache().getTrainee().name); + return FlatButton( + padding: EdgeInsets.all(20), + textColor: Colors.white, + color: Colors.black12, + focusColor: Colors.blueAccent, + onPressed: () => + { + args['customerId'] = Cache().getTrainee().customerId, + Navigator.of(context).pushNamed('exerciseByPlanPage', + arguments: args) + }, + child: Text("Execute My Trainee's Training Plan", + style: TextStyle(fontSize: 18),) + ); + } else { + return Container(); + } + } + +} + + diff --git a/lib/view/settings.dart b/lib/view/settings.dart index 03bad7d..3112dcd 100644 --- a/lib/view/settings.dart +++ b/lib/view/settings.dart @@ -109,6 +109,13 @@ class SettingsPage extends StatelessWidget{ ) ), + ListTile( + leading: Icon(Icons.get_app), + title: RaisedButton( + child: Text("Check lang", style: TextStyle(fontSize: 12),), + onPressed: () => settingsBloc.add(SettingsGetLanguage()), + ) + ) ] ); } diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index d20d300..1b273d7 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -56,14 +56,15 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin { } }); */ colorAnim = RainbowColorTween([Colors.white70, - Colors.greenAccent, - Colors.lightGreen, - Colors.lightGreenAccent, - Colors.yellow, + Colors.blueGrey, + Colors.blueAccent, + Colors.lightBlue, + Colors.lightBlueAccent, Colors.yellowAccent, Colors.orange, Colors.orangeAccent, - Colors.white70]) + Colors.yellowAccent, + Color(0xffcce6ff)]) .animate(colorController) ..addListener(() { setState(() {}); }) ..addStatusListener((status) { @@ -103,7 +104,9 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin { icon: Icon(Icons.arrow_back, color: Colors.white), onPressed: () => { - menuBloc.add(MenuTreeUp(parent: 0)) + if ( menuBloc != null ) { + menuBloc.add(MenuTreeUp(parent: 0)), + } }, ) ); @@ -151,7 +154,7 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin { trailing: Icon(percent > 0.6 ? Icons.mood : Icons.mood_bad, color: colorAnim.value,), linearStrokeCap: LinearStrokeCap.roundAll, backgroundColor: colorAnim.value, - progressColor: Colors.blue, + progressColor: Color(0xff73e600), animation: true, ), ], diff --git a/lib/widgets/app_bar_common.dart b/lib/widgets/app_bar_common.dart new file mode 100644 index 0000000..f6c6c02 --- /dev/null +++ b/lib/widgets/app_bar_common.dart @@ -0,0 +1,133 @@ +import 'dart:async'; +import 'package:aitrainer_app/localization/app_localization.dart'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:rainbow_color/rainbow_color.dart'; + + +class AppBarCommonNav extends StatefulWidget implements PreferredSizeWidget { + + @override + _AppBarCommonNav createState() => _AppBarCommonNav(); + + @override + Size get preferredSize => const Size.fromHeight(60); +} + +class _AppBarCommonNav extends State<AppBarCommonNav> with SingleTickerProviderStateMixin { + Animation<Color> colorAnim; + AnimationController colorController; + + @override + void initState() { + + colorController = + AnimationController(duration: Duration(seconds: 4), vsync: this); + + colorAnim = RainbowColorTween([Colors.white70, + Colors.blueGrey, + Colors.blueAccent, + Colors.lightBlue, + Colors.lightBlueAccent, + Colors.yellowAccent, + Colors.orange, + Colors.orangeAccent, + Colors.yellowAccent, + Color(0xffcce6ff)]) + .animate(colorController) + ..addListener(() { setState(() {}); }) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + Timer(Duration(seconds: 10), () { + //colorController.reset(); + if ( mounted ) { + colorController.forward(); + } + }); + } else if (status == AnimationStatus.dismissed) { + colorController.forward(); + } + }); + colorController.forward(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: Colors.black, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + + getAnimatedWidget(), + Image.asset( + 'asset/image/WT_long_logo.png', + fit: BoxFit.cover, + height: 65.0, + ), + ], + ), + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => + { + Navigator.of(context).pop() + }, + ) + ); + } + + @override + void dispose() { + //sizeController.dispose(); + colorController.dispose(); + super.dispose(); + } + + Widget getAnimatedWidget() { + double percent = Cache().getPercentExercises(); + if ( percent == -1) { + ExerciseRepository exerciseRepository = ExerciseRepository(); + exerciseRepository.getBaseExerciseFinishedPercent(); + percent = Cache().getPercentExercises(); + } + int sizeExerciseList = Cache().getExercises() == null? 0 : Cache().getExercises().length; + if ( sizeExerciseList == 0 ) { + return Stack( + alignment: Alignment.topLeft, + children: [ + Text(AppLocalizations.of(context).translate("Make your first test"), + style: TextStyle(fontSize: 16, color: colorAnim.value, shadows: [Shadow(color: Colors.purple , blurRadius: 15)]), + + ), + //TestProgress(animation: sizeAnim), + ] + ); + } else { + + return Stack( + alignment: Alignment.topLeft, + children: [ + LinearPercentIndicator( + width: 120.0, + lineHeight: 14.0, + percent: percent, + center: Text( + (percent * 100).toStringAsFixed(0) + "% finished", + style: new TextStyle(fontSize: 12.0), + ), + trailing: Icon(percent > 0.6 ? Icons.mood : Icons.mood_bad, color: colorAnim.value,), + linearStrokeCap: LinearStrokeCap.roundAll, + backgroundColor: colorAnim.value, + progressColor: Color(0xff73e600), + animation: true, + ), + ], + ); + } + } +} diff --git a/lib/widgets/bottom_nav.dart b/lib/widgets/bottom_nav.dart index 4d9715c..50fad6b 100644 --- a/lib/widgets/bottom_nav.dart +++ b/lib/widgets/bottom_nav.dart @@ -91,12 +91,13 @@ class _NawDrawerWidget extends State<BottomNavigator> { break; case 1: - Navigator.of(context).pop(); - Navigator.of(context).pushNamed('mydevelopment'); + //Navigator.of(context).pop(); + Navigator.of(context).pushNamed('myDevelopment'); break; case 2: - //throw new StateError('This is a Dart exception on event.'); + //Navigator.of(context).pop(); + Navigator.of(context).pushNamed('myExercisePlan'); break; case 3: diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index b613d38..5c578f5 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -24,7 +24,6 @@ class AitrainerHome extends StatefulWidget { class _HomePageState extends State<AitrainerHome> { GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); - final AppLanguage appLanguage = AppLanguage(); @override void initState() { @@ -45,7 +44,9 @@ class _HomePageState extends State<AitrainerHome> { sessionBloc.add(SessionStart()); // ignore: close_sinks SettingsBloc settingsBloc = BlocProvider.of<SettingsBloc>(context); - settingsBloc.loadLang(); + String lang = AppLanguage().appLocal.languageCode; + print (" -- Loading delayed lang $lang"); + settingsBloc.add(SettingsChangeLanguage(language: lang)); } } }); diff --git a/lib/widgets/loading.dart b/lib/widgets/loading.dart index 2dce838..974b39b 100644 --- a/lib/widgets/loading.dart +++ b/lib/widgets/loading.dart @@ -6,8 +6,8 @@ class LoadingScreenMain extends StatelessWidget { Widget build(BuildContext context) { Image _backgroundImage = Image.asset('asset/image/WT01_loading_layers.png', fit: BoxFit.cover, - //height: double.infinity, - //width: double.infinity, + height: double.infinity, + width: double.infinity, alignment: Alignment.center, ); return Scaffold( diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index 8dfd097..ac3c14c 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -37,7 +37,7 @@ class MenuPageWidget extends StatelessWidget { child: Center( child: Stack( alignment: Alignment.bottomLeft, - fit: StackFit.loose, + clipBehavior: Clip.antiAliasWithSaveLayer, children: [ FlatButton( child: _getButtonImage(workoutTree), @@ -45,21 +45,35 @@ class MenuPageWidget extends StatelessWidget { shape: getShape(workoutTree), onPressed: () => menuClick(workoutTree, menuBloc, context), ), - InkWell( - onTap:() => menuClick(workoutTree, menuBloc, context), - child: Text( - " " + workoutTree.name, - maxLines: 2, - style: TextStyle( - color: workoutTree.color, - fontSize: workoutTree.fontSize, - fontFamily: 'Arial', - fontWeight: FontWeight.w900), + Positioned( + top: workoutTree.name.length > 30 ? 140 : 150, + left: 5, + child: Container( + height: 300, + width: 280, + child: InkWell( + onTap:() => menuClick(workoutTree, menuBloc, context), + child: Text( + " " + workoutTree.name, + maxLines: 2, + style: TextStyle( + color: workoutTree.color, + fontSize: workoutTree.fontSize, + fontFamily: 'Arial', + fontWeight: FontWeight.w900), + ), + + highlightColor: workoutTree.color, + ), + color: Colors.transparent, ), - highlightColor: workoutTree.color, ), - ])))); + ] + ) + ) + ) + ); }); SliverList sliverList = diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index a18d982..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,888 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.dartlang.org" - source: hosted - version: "7.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.39.17" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.13" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - bloc: - dependency: transitive - description: - name: bloc - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.3" - bloc_test: - dependency: "direct dev" - description: - name: bloc_test - url: "https://pub.dartlang.org" - source: hosted - version: "7.0.3" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.2" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.11" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "5.2.0" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.3.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "7.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.3" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.4.1" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.14.12" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - coverage: - dependency: transitive - description: - name: coverage - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.11" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.6" - devicelocale: - dependency: "direct main" - description: - name: devicelocale - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.1" - equatable: - dependency: "direct main" - description: - name: equatable - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.4" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.0" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.11" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_bloc: - dependency: "direct main" - description: - name: flutter_bloc - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.4" - flutter_driver: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_facebook_login: - dependency: "direct main" - description: - name: flutter_facebook_login - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - flutter_form_bloc: - dependency: "direct main" - description: - name: flutter_form_bloc - url: "https://pub.dartlang.org" - source: hosted - version: "0.19.0" - flutter_keyboard_visibility: - dependency: transitive - description: - name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.2" - flutter_launcher_icons: - dependency: "direct dev" - description: - name: flutter_launcher_icons - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_treeview: - dependency: "direct main" - description: - name: flutter_treeview - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.0+1" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - form_bloc: - dependency: transitive - description: - name: form_bloc - url: "https://pub.dartlang.org" - source: hosted - version: "0.19.1" - freezed: - dependency: "direct main" - description: - name: freezed - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.6" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.0+1" - fuchsia_remote_debug_protocol: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - gradient_bottom_navigation_bar: - dependency: "direct main" - description: - name: gradient_bottom_navigation_bar - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0+4" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+3" - http: - dependency: "direct dev" - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.1" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.4" - image: - dependency: transitive - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.12" - intl: - dependency: "direct dev" - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.4" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - json_rpc_2: - dependency: transitive - description: - name: json_rpc_2 - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.4" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.6" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.8" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7" - mockito: - dependency: "direct main" - description: - name: mockito - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.1" - multi_server_socket: - dependency: transitive - description: - name: multi_server_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.12" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1+2" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.0" - percent_indicator: - dependency: "direct main" - description: - name: percent_indicator - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.5" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.1" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.12" - provider: - dependency: "direct dev" - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "4.3.2+1" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.4" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - rainbow_color: - dependency: "direct main" - description: - name: rainbow_color - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1" - rainbow_vis: - dependency: transitive - description: - name: rainbow_vis - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - rxdart: - dependency: transitive - description: - name: rxdart - url: "https://pub.dartlang.org" - source: hosted - version: "0.24.1" - sentry: - dependency: "direct main" - description: - name: sentry - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - shared_preferences: - dependency: "direct dev" - description: - name: shared_preferences - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.10" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.2+2" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1+10" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2+7" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.9" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.8" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_gen: - dependency: transitive - description: - name: source_gen - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - spider_chart: - dependency: "direct main" - description: - name: spider_chart - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - sync_http: - dependency: transitive - description: - name: sync_http - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - test: - dependency: "direct dev" - description: - name: test - url: "https://pub.dartlang.org" - source: hosted - version: "1.14.4" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.15" - test_core: - dependency: transitive - description: - name: test_core - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.4" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" - usage: - dependency: transitive - description: - name: usage - url: "https://pub.dartlang.org" - source: hosted - version: "3.4.2" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.2" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - vm_service: - dependency: transitive - description: - name: vm_service - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.0" - vm_service_client: - dependency: transitive - description: - name: vm_service_client - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.6+2" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+15" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - webdriver: - dependency: transitive - description: - name: webdriver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.0" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.1" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.1" -sdks: - dart: ">=2.7.0 <3.0.0" - flutter: ">=1.16.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0fef500..78a0b86 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+12 +version: 1.1.0+20 environment: sdk: ">=2.7.0 <3.0.0" @@ -26,19 +26,19 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - devicelocale: ^0.3.1 + cupertino_icons: ^1.0.0 + devicelocale: ^0.3.2 sentry: ^3.0.1 # firebase_messaging: ^6.0.16 flutter_local_notifications: 1.1.1 flutter_facebook_login: ^3.0.0 - flutter_bloc: ^6.0.4 + flutter_bloc: ^6.0.5 equatable: ^1.2.4 freezed: ^0.11.6 flutter_form_bloc: ^0.19.0 spider_chart: ^0.1.5 rainbow_color: ^0.1.1 - percent_indicator: ^2.1.5 + percent_indicator: ^2.1.6 gradient_bottom_navigation_bar: ^1.0.0+4 flutter_treeview: ^0.6.0+1 @@ -51,17 +51,16 @@ dev_dependencies: flutter_driver: sdk: flutter test: any - bloc_test: ^7.0.1 + bloc_test: ^7.0.3 build_runner: http: 0.12.1 - provider: ^4.3.2+1 intl: 0.16.1 shared_preferences: ^0.5.10 - flutter_launcher_icons: ^0.7.5 + flutter_launcher_icons: ^0.8.0 flutter_icons: android: "launcher_icon" @@ -94,6 +93,7 @@ flutter: - asset/image/WT_weight_loss.png - asset/image/WT_welcome.png - asset/image/login_fb.png + - asset/image/lock.png - asset/menu/1.cardio.png - asset/menu/1.1.aerob.png - asset/menu/1.2.anaerob.png diff --git a/test/account_bloc_test.dart b/test/account_bloc_test.dart new file mode 100644 index 0000000..7cfb618 --- /dev/null +++ b/test/account_bloc_test.dart @@ -0,0 +1,79 @@ +import 'package:aitrainer_app/bloc/account/account_bloc.dart'; +import 'package:aitrainer_app/repository/customer_repository.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart' as test; +import 'package:flutter_test/flutter_test.dart'; + +class MockCustomerRepository extends Mock implements CustomerRepository { + +} + +void main() { + MockCustomerRepository customerRepository; + AccountBloc accountBloc; + + TestWidgetsFlutterBinding.ensureInitialized(); + + test.setUp(() { + customerRepository = MockCustomerRepository(); + accountBloc = AccountBloc(customerRepository: customerRepository); + }); + + test.tearDown(() { + accountBloc?.close(); + }); + + test.test('initial state is correct', () { + expect(accountBloc.state, AccountInitial()); + }); + + group('Account', () { + test.test( + 'emits [loading, logged in] when the customer clicked login', + () { + final expectedResponse = [ + AccountLoading(), + AccountLoggedIn(), + ]; + + //verify(accountBloc.customerRepository.customer == null); + + expectLater( + accountBloc, emitsInOrder(expectedResponse), + ); + + accountBloc.add(AccountLogin()); + }); + }); + + test.test( + 'emits [loading, logged out] when the customer clicked logout', + () { + final expectedResponse = [ + AccountLoading(), + AccountLoggedOut(), + ]; + + expectLater( + accountBloc, emitsInOrder(expectedResponse), + ); + + accountBloc.add(AccountLogout()); + }); + + test.test( + 'emits [loading, logged out] when the customer data changed', + () { + final expectedResponse = [ + AccountLoading(), + AccountReady(), + ]; + + expectLater( + accountBloc, emitsInOrder(expectedResponse), + ); + + accountBloc.add(AccountChangeCustomer()); + }); + +} diff --git a/test/customer_service_test.dart b/test/customer_service_test.dart new file mode 100644 index 0000000..d2d7018 --- /dev/null +++ b/test/customer_service_test.dart @@ -0,0 +1,52 @@ +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/customer.dart'; +import 'package:aitrainer_app/service/customer_service.dart'; +import 'package:aitrainer_app/util/env.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +//import 'package:mockito/mockito.dart'; + +// Create a MockClient using the Mock class provided by the Mockito package. +// Create new instances of this class in each test. +/* class MockClient extends Mock implements CustomerApi { + Future<List<Customer>> getTrainees(int trainerId) async { + if (trainerId == 62) { + List<Customer> list = List<Customer>(); + Customer customer1 = Customer(firstname: "Zalán", name: "Boss"); + Customer customer2 = Customer(firstname: "Zétény", name: "Boss"); + list.add(customer1); + list.add(customer2); + return list; + } else { + throw Exception("No trainees found"); + } + } +} */ + +main() { + + + setUp(() { + Cache().setTestBaseUrl(); + }); + + group('fetchPost', () { + test('returns a List<Customer> if the http call completes successfully', () async { + final client = CustomerApi(); + + // Use Mockito to return a successful response when it calls the + // provided http.Client. + List<Customer> trainees = List<Customer>(); + trainees = await client.getTrainees(62); + + expect(trainees.length, 2); + expect(trainees[0].firstname, "Zalán"); + }); + + test('throws an exception if the http call completes with an error', () async { + final client = CustomerApi(); + + expect(client.getTrainees(22), throwsException); + }); + }); +} \ No newline at end of file diff --git a/test/exercise_plan_test.dart b/test/exercise_plan_test.dart new file mode 100644 index 0000000..2836749 --- /dev/null +++ b/test/exercise_plan_test.dart @@ -0,0 +1,72 @@ +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/service/exercise_plan_service.dart'; +import 'package:test/test.dart'; + +main() { + + setUp(() { + Cache().setTestBaseUrl(); + }); + + group('new Plan', () { + test('add new plan and plan details', () async { + final client = ExercisePlanApi(); + + ExercisePlan exercisePlan = ExercisePlan( + "Test plan " + DateTime.now().toIso8601String(), + 62 + ); + exercisePlan.dateAdd = DateTime.now(); + + ExercisePlan savedPlan = await client.saveExercisePlan(exercisePlan); + expect(savedPlan.name.substring(0, 9), "Test plan"); + expect(savedPlan.customerId, 62); + + + + int newPlanId = savedPlan.exercisePlanId; + //savedPlan.exercisePlanId = newPlanId; + + ExercisePlanDetail detail = ExercisePlanDetail( + 39 //exerciseTypeId + ); + detail.serie = 3; + detail.repeats = 12; + detail.weightEquation = "90"; + detail.exercisePlanId = newPlanId; + + ExercisePlanDetail savedDetail = await client.saveExercisePlanDetail(detail); + + expect(savedDetail.weightEquation, "90"); + expect(savedDetail.repeats, 12); + expect(savedDetail.exercisePlanId, newPlanId); + + await client.deleteExercisePlanDetail(savedDetail.exercisePlanDetailId); + await client.deleteExercisePlan(savedPlan.exercisePlanId); + + }); + }); + + test('get the last plan and change plan details', () async { + final client = ExercisePlanApi(); + + ExercisePlan exercisePlan = await client.getLastExercisePlan(61); + List<ExercisePlanDetail> list = await client.getExercisePlanDetail(exercisePlan.exercisePlanId); + + expect(list.length, 2); + ExercisePlanDetail detail = ExercisePlanDetail(3); + detail.serie = 4; + detail.repeats = 12; + detail.weightEquation = "10"; + detail.exercisePlanId = exercisePlan.exercisePlanId; + //list.add(detail); + ExercisePlanDetail newObjectToSave = await client.saveExercisePlanDetail(detail); + List<ExercisePlanDetail> list2 = await client.getExercisePlanDetail(exercisePlan.exercisePlanId); + expect(list2.length, 3); + expect(list2.last.weightEquation, "10"); + await client.deleteExercisePlanDetail(newObjectToSave.exercisePlanDetailId); + + }); +} \ No newline at end of file