diff --git a/asset/menu/machine_test.jpg b/asset/menu/machine_test.jpg new file mode 100644 index 0000000..83c54ab Binary files /dev/null and b/asset/menu/machine_test.jpg differ diff --git a/asset/menu/own_body.jpg b/asset/menu/own_body.jpg new file mode 100644 index 0000000..e5a2910 Binary files /dev/null and b/asset/menu/own_body.jpg differ diff --git a/asset/menu/test_center.jpg b/asset/menu/test_center.jpg new file mode 100644 index 0000000..7b5a605 Binary files /dev/null and b/asset/menu/test_center.jpg differ diff --git a/asset/menu/under_body.jpg b/asset/menu/under_body.jpg new file mode 100644 index 0000000..cfb1cab Binary files /dev/null and b/asset/menu/under_body.jpg differ diff --git a/asset/menu/upper_body.jpg b/asset/menu/upper_body.jpg new file mode 100644 index 0000000..e7aca76 Binary files /dev/null and b/asset/menu/upper_body.jpg differ diff --git a/asset/menu/weight_test.jpg b/asset/menu/weight_test.jpg new file mode 100644 index 0000000..7b74edc Binary files /dev/null and b/asset/menu/weight_test.jpg differ diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 21db79b..60937e8 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -388,7 +388,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -405,7 +405,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.7; + MARKETING_VERSION = 1.1.10; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -531,7 +531,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -548,7 +548,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.7; + MARKETING_VERSION = 1.1.10; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -566,7 +566,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -583,7 +583,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.7; + MARKETING_VERSION = 1.1.10; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bloc/menu/menu_bloc.dart b/lib/bloc/menu/menu_bloc.dart index c0928b4..3c26bf0 100644 --- a/lib/bloc/menu/menu_bloc.dart +++ b/lib/bloc/menu/menu_bloc.dart @@ -173,6 +173,11 @@ class MenuBloc extends Bloc with Trans, Logging { ability = ExerciseAbility.endurance; break; case "Cardio": + ability = ExerciseAbility.running; + break; + case "Test Center": + ability = ExerciseAbility.mini_test; + break; case "My Body": ability = ExerciseAbility.none; break; diff --git a/lib/bloc/test_set_edit/test_set_edit_bloc.dart b/lib/bloc/test_set_edit/test_set_edit_bloc.dart new file mode 100644 index 0000000..b3143d4 --- /dev/null +++ b/lib/bloc/test_set_edit/test_set_edit_bloc.dart @@ -0,0 +1,83 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise_ability.dart'; +import 'package:aitrainer_app/model/exercise_plan.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +import 'package:aitrainer_app/model/exercise_plan_template.dart'; +import 'package:aitrainer_app/model/exercise_type.dart'; +import 'package:aitrainer_app/repository/workout_tree_repository.dart'; +import 'package:aitrainer_app/service/exercise_plan_service.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'test_set_edit_event.dart'; +part 'test_set_edit_state.dart'; + +class TestSetEditBloc extends Bloc { + final String templateName; + final WorkoutTreeRepository workoutTreeRepository; + final MenuBloc menuBloc; + final List _exerciseTypes = List(); + final HashMap _exercisePlanDetails = HashMap(); + + TestSetEditBloc({this.templateName, this.workoutTreeRepository, this.menuBloc}) : super(TestSetEditInitial()) { + if (Cache().exercisePlanTemplates.isNotEmpty) { + Cache().exercisePlanTemplates.forEach((element) { + final ExercisePlanTemplate template = element as ExercisePlanTemplate; + if (template.name == templateName) { + template.exerciseTypes.forEach((id) { + final ExerciseType exerciseType = Cache().getExerciseTypeById(id); + _exerciseTypes.add(exerciseType); + _exercisePlanDetails[exerciseType.exerciseTypeId] = exerciseType; + }); + } + }); + } + } + + @override + Stream mapEventToState(TestSetEditEvent event) async* { + try { + if (event is TestSetEditChangeExerciseType) { + final List alternatives = workoutTreeRepository.getExerciseTypeAlternatives(event.exerciseTypeId); + final ExerciseType exerciseType = Cache().getExerciseTypeById(event.exerciseTypeId); + if (event.index > alternatives.length) { + /// skip + _exercisePlanDetails[exerciseType.exerciseTypeId] = null; + } else if (event.index == 0) { + _exercisePlanDetails[exerciseType.exerciseTypeId] = exerciseType; + } else { + final changedExerciseType = alternatives[event.index - 1]; + _exercisePlanDetails[exerciseType.exerciseTypeId] = changedExerciseType; + } + } else if (event is TestSetEditSubmit) { + yield TestSetEditLoading(); + ExercisePlan exercisePlan = ExercisePlan(templateName, Cache().userLoggedIn.customerId); + exercisePlan.private = true; + exercisePlan.type = ExerciseAbility.mini_test.toString(); + exercisePlan.dateAdd = DateTime.now(); + ExercisePlan savedExercisePlan = await ExercisePlanApi().saveExercisePlan(exercisePlan); + + List details = List(); + for (var entry in _exercisePlanDetails.entries) { + if (entry.value != null) { + ExercisePlanDetail exercisePlanDetail = ExercisePlanDetail(entry.value.exerciseTypeId); + exercisePlanDetail.exercisePlanId = savedExercisePlan.exercisePlanId; + exercisePlanDetail.serie = 1; + ExercisePlanDetail savedDetail = await ExercisePlanApi().saveExercisePlanDetail(exercisePlanDetail); + details.add(savedDetail); + } + } + Cache().saveActiveExercisePlan(exercisePlan, details); + yield TestSetEditSaved(); + } + } on Exception catch (e) { + yield TestSetEditError(message: e.toString()); + } + } + + List get exerciseTypes => this._exerciseTypes; +} diff --git a/lib/bloc/test_set_edit/test_set_edit_event.dart b/lib/bloc/test_set_edit/test_set_edit_event.dart new file mode 100644 index 0000000..2db990b --- /dev/null +++ b/lib/bloc/test_set_edit/test_set_edit_event.dart @@ -0,0 +1,33 @@ +part of 'test_set_edit_bloc.dart'; + +abstract class TestSetEditEvent extends Equatable { + const TestSetEditEvent(); + + @override + List get props => []; +} + +class TestSetEditLoad extends TestSetEditEvent { + const TestSetEditLoad(); +} + +class TestSetEditChangeExerciseType extends TestSetEditEvent { + final int index; + final int exerciseTypeId; + const TestSetEditChangeExerciseType({this.index, this.exerciseTypeId}); + + @override + List get props => [index, exerciseTypeId]; +} + +class TestSetEditSkipExerciseType extends TestSetEditEvent { + final int exerciseTypeId; + const TestSetEditSkipExerciseType({this.exerciseTypeId}); + + @override + List get props => [exerciseTypeId]; +} + +class TestSetEditSubmit extends TestSetEditEvent { + const TestSetEditSubmit(); +} diff --git a/lib/bloc/test_set_edit/test_set_edit_state.dart b/lib/bloc/test_set_edit/test_set_edit_state.dart new file mode 100644 index 0000000..b34b444 --- /dev/null +++ b/lib/bloc/test_set_edit/test_set_edit_state.dart @@ -0,0 +1,32 @@ +part of 'test_set_edit_bloc.dart'; + +abstract class TestSetEditState extends Equatable { + const TestSetEditState(); + + @override + List get props => []; +} + +class TestSetEditInitial extends TestSetEditState { + const TestSetEditInitial(); +} + +class TestSetEditReady extends TestSetEditState { + const TestSetEditReady(); +} + +class TestSetEditSaved extends TestSetEditState { + const TestSetEditSaved(); +} + +class TestSetEditLoading extends TestSetEditState { + const TestSetEditLoading(); +} + +class TestSetEditError extends TestSetEditState { + final String message; + const TestSetEditError({this.message}); + + @override + List get props => [message]; +} diff --git a/lib/model/cache.dart b/lib/model/cache.dart index 1e68b38..ded0a72 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -1,7 +1,9 @@ import 'dart:collection'; +import 'dart:convert'; 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_plan_template.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/model_change.dart'; @@ -63,6 +65,8 @@ class Cache with Logging { static final String hardwareKey = 'hardware'; static final String loginTypeKey = 'login_type'; static final String timerDisplayKey = 'timer_display'; + static final String activeExercisePlanKey = 'active_exercise_plan'; + static final String activeExercisePlanDetailsKey = 'active_exercise_details_plan'; static String baseUrl = 'http://aitrainer.info:8888/api/'; static final String mediaUrl = 'https://aitrainer.info:4343/media/'; @@ -89,6 +93,10 @@ class Cache with Logging { List _products; List _purchases = List(); List _productTests; + List _exercisePlanTemplates = List(); + + ExercisePlan activeExercisePlan; + List activeExercisePlanDetails; List _devices; List _customerDevices; @@ -132,6 +140,38 @@ class Cache with Logging { return this.authToken; } + Future saveActiveExercisePlan(ExercisePlan exercisePlan, List exercisePlanDetails) async { + this.activeExercisePlan = exercisePlan; + this.activeExercisePlanDetails = exercisePlanDetails; + String exercisePlanJson = JsonEncoder().convert(exercisePlan.toJson()); + String detailsJson = jsonEncode(exercisePlanDetails); + + Future prefs = SharedPreferences.getInstance(); + SharedPreferences sharedPreferences; + sharedPreferences = await prefs; + + sharedPreferences.setString(Cache.activeExercisePlanKey, exercisePlanJson); + sharedPreferences.setString(Cache.activeExercisePlanDetailsKey, detailsJson); + } + + Future getActiveExercisePlan() async { + Future prefs = SharedPreferences.getInstance(); + SharedPreferences sharedPreferences; + sharedPreferences = await prefs; + + String exercisePlanJson = sharedPreferences.getString(Cache.activeExercisePlanKey); + if (exercisePlanJson != null) { + final Map map = JsonDecoder().convert(exercisePlanJson); + this.activeExercisePlan = ExercisePlan.fromJson(map); + } + + String detailsJson = sharedPreferences.getString(Cache.activeExercisePlanDetailsKey); + if (detailsJson != null) { + Iterable json = jsonDecode(detailsJson); + this.activeExercisePlanDetails = json.map((details) => ExercisePlanDetail.fromJson(details)).toList(); + } + } + Future setServer(bool live) async { if (this.testEnvironment == "1") { liveServer = false; @@ -146,7 +186,6 @@ class Cache with Logging { void getHardware(SharedPreferences prefs) { final bool hasHardware = prefs.getBool(Cache.hardwareKey); - //log("Has Hardware: " + hasHardware.toString()); this.hasHardware = hasHardware; if (hasHardware == null) { this.hasHardware = false; @@ -301,9 +340,7 @@ class Cache with Logging { this._exerciseTree = exerciseTree; } - void setExercises(List exercises) { - this._exercises = exercises; - } + void setExercises(List exercises) => this._exercises = exercises; void setExercisesTrainee(List exercises) { this._exercisesTrainee = exercises; @@ -485,10 +522,10 @@ class Cache with Logging { Future initCustomer(int customerId) async { log(" *** initCustomer"); await PackageApi().getCustomerPackage(customerId); - Flurry.setUserId(customerId.toString()); await setLoginTypeFromPrefs(); + await getActiveExercisePlan(); Cache().startPage = "home"; Track().track(TrackingEvent.enter); } @@ -498,4 +535,7 @@ class Cache with Logging { LoginType getLoginType() => loginType; void setLoginType(LoginType type) => this.loginType = type; + + List get exercisePlanTemplates => this._exercisePlanTemplates; + setExercisePlanTemplates(value) => this._exercisePlanTemplates = value; } diff --git a/lib/model/exercise_ability.dart b/lib/model/exercise_ability.dart index 88113d9..6b8d444 100644 --- a/lib/model/exercise_ability.dart +++ b/lib/model/exercise_ability.dart @@ -1,4 +1,4 @@ -enum ExerciseAbility { oneRepMax, endurance, running, none } +enum ExerciseAbility { oneRepMax, endurance, running, mini_test, none } extension ExerciseAbilityExt on ExerciseAbility { bool equalsTo(ExerciseAbility ability) => this.toString() == ability.toString(); diff --git a/lib/model/exercise_plan.dart b/lib/model/exercise_plan.dart index 129a536..04b08f1 100644 --- a/lib/model/exercise_plan.dart +++ b/lib/model/exercise_plan.dart @@ -8,6 +8,8 @@ class ExercisePlan { bool private; DateTime dateAdd; DateTime dateUpd; + String type; + int exercisePlanTemplateId; ExercisePlan(String name, int customerId) { this.customerId = customerId; @@ -23,6 +25,8 @@ class ExercisePlan { this.description = json['description']; this.dateAdd = json['dateAdd'] == null ? null : DateTime.parse(json['dateAdd']); this.dateUpd = json['dateUpd'] == null ? null : DateTime.parse(json['dateUpd']); + this.type = json['type']; + this.exercisePlanTemplateId = json['exercisePlanTemplateId']; } Map toJson() { @@ -40,6 +44,8 @@ class ExercisePlan { "private": private, "dateAdd": formattedDateAdd, "dateUpd": formattedDateUpd, + "type": type, + "exercisePlanTemplateId": exercisePlanTemplateId }; } else { return { @@ -50,6 +56,8 @@ class ExercisePlan { "private": private, "dateAdd": formattedDateAdd, "dateUpd": formattedDateUpd, + "type": type, + "exercisePlanTemplateId": exercisePlanTemplateId }; } } diff --git a/lib/model/exercise_plan_template.dart b/lib/model/exercise_plan_template.dart new file mode 100644 index 0000000..f2c9f0f --- /dev/null +++ b/lib/model/exercise_plan_template.dart @@ -0,0 +1,37 @@ +class ExercisePlanTemplate { + int exercisePlanTemplateId; + String name; + String description; + String templateType; + String nameTranslation; + String descriptionTranslation; + List exerciseTypes = List(); + + ExercisePlanTemplate.fromJson(Map json) { + this.exercisePlanTemplateId = json['exercisePlanId']; + this.name = json['name']; + this.description = json['description']; + this.templateType = json['templateType']; + this.nameTranslation = json['translations'] != null && (json['translations']).length > 0 ? json['translations'][0]['name'] : this.name; + this.descriptionTranslation = + json['translations'] != null && (json['translations']).length > 0 ? json['translations'][0]['description'] : this.description; + if (json['details'] != null && (json['details']).length > 0) { + List details = json['details']; + details.forEach((element) { + exerciseTypes.add(element['exerciseTypeId']); + }); + } + } + + Map toJson() { + return { + "exercisePlanTemplateId": exercisePlanTemplateId, + "name": name, + "description": "description", + "templateType": templateType, + "nameTranslation": nameTranslation, + "descriptionTranslation": descriptionTranslation, + "exerciseTypes": exerciseTypes.toString() + }; + } +} diff --git a/lib/model/exercise_tree.dart b/lib/model/exercise_tree.dart index 8e99c41..0a9ba64 100644 --- a/lib/model/exercise_tree.dart +++ b/lib/model/exercise_tree.dart @@ -5,6 +5,7 @@ class ExerciseTree { String imageUrl; bool active; String nameTranslation; + int sort; ExerciseTree(); @@ -25,6 +26,7 @@ class ExerciseTree { "imageUrl": imageUrl, "active": active.toString(), "nameTranslation": nameTranslation, + "sort": sort, }; } @@ -38,6 +40,7 @@ class ExerciseTree { newTree.parentId = parentId; } newTree.active = this.active; + newTree.sort = this.sort; return newTree; } diff --git a/lib/model/exercise_tree_parents.dart b/lib/model/exercise_tree_parents.dart index b603003..73e6980 100644 --- a/lib/model/exercise_tree_parents.dart +++ b/lib/model/exercise_tree_parents.dart @@ -2,10 +2,12 @@ class ExerciseTreeParents { int exerciseTreeParentsId; int exerciseTreeParentId; int exerciseTreeChildId; + int sort; ExerciseTreeParents.fromJson(Map json) { this.exerciseTreeParentsId = json['exerciseTreeParentsId']; this.exerciseTreeParentId = json['exerciseTreeParentId']; this.exerciseTreeChildId = json['exerciseTreeChildId']; + this.sort = json['sort']; } } diff --git a/lib/model/exercise_type.dart b/lib/model/exercise_type.dart index 0176011..63c5e7d 100644 --- a/lib/model/exercise_type.dart +++ b/lib/model/exercise_type.dart @@ -17,6 +17,7 @@ class ExerciseType { String descriptionTranslation = ""; List devices = List(); List parents = List(); + List alternatives = List(); ExerciseAbility ability; @@ -55,6 +56,14 @@ class ExerciseType { this.parents.add(parent['exerciseTreeId']); }); } + + if (json['alternatives'].length > 0) { + final List jsonAlternatives = json['alternatives']; + + jsonAlternatives.forEach((alternative) { + this.alternatives.add(alternative['exerciseTypeChildId']); + }); + } } Map toJson() => { diff --git a/lib/model/workout_menu_tree.dart b/lib/model/workout_menu_tree.dart index f65c7c1..8593df6 100644 --- a/lib/model/workout_menu_tree.dart +++ b/lib/model/workout_menu_tree.dart @@ -40,6 +40,7 @@ class WorkoutMenuTree { String nameEnglish; String parentName; String parentNameEnglish; + int sort; WorkoutMenuTree( this.id, @@ -57,7 +58,8 @@ class WorkoutMenuTree { this.isRunning, this.nameEnglish, this.parentName, - this.parentNameEnglish); + this.parentNameEnglish, + this.sort); Map toJson() { return { @@ -73,6 +75,7 @@ class WorkoutMenuTree { "is1RM": is1RM.toString(), "isEndurance": isEndurance.toString(), "isRunning": isRunning.toString(), + "sort": sort, }; } diff --git a/lib/repository/workout_tree_repository.dart b/lib/repository/workout_tree_repository.dart index eb9f439..934dab9 100644 --- a/lib/repository/workout_tree_repository.dart +++ b/lib/repository/workout_tree_repository.dart @@ -103,7 +103,8 @@ class WorkoutTreeRepository with Logging { isRunning, treeItem.name, parent != null ? parent.name : "", - parent != null ? parent.nameEnglish : ""); + parent != null ? parent.nameEnglish : "", + treeItem.sort); menuItem = this.setWorkoutTypes(menuItem, treeItem); this.tree[treeItem.name + "_" + treeItem.parentId.toString()] = menuItem; //log("WorkoutMenuTree item " + menuItem.toJson().toString()); @@ -141,7 +142,8 @@ class WorkoutTreeRepository with Logging { isRunning, exerciseType.name, parent != null ? parent.name : "", - parent != null ? parent.nameEnglish : ""); + parent != null ? parent.nameEnglish : "", + 0); this.tree[exerciseType.name] = menuItem; menuAsExercise.add(menuItem); //log("WorkoutMenuTree item " + menuItem.toJson().toString()); @@ -283,6 +285,56 @@ class WorkoutTreeRepository with Logging { return parentItem; } + WorkoutMenuTree getMenuItemByExerciseTypeId(int exerciseTypeId) { + WorkoutMenuTree menuItem; + this.menuAsExercise.forEach((element) { + if (element.exerciseTypeId == exerciseTypeId) { + menuItem = element; + } + }); + return menuItem; + } + + List getWorkoutTreeAlternatives(WorkoutMenuTree workoutMenuTree) { + if (workoutMenuTree == null) { + return null; + } + if (workoutMenuTree.exerciseType == null) { + return null; + } + final List alternatives = this.getExerciseTypeAlternatives(workoutMenuTree.exerciseTypeId); + if (alternatives == null) { + return null; + } + + List list = List(); + list.add(workoutMenuTree); + alternatives.forEach((element) { + final WorkoutMenuTree alternativeMenuItem = this.getMenuItemByExerciseTypeId(element.exerciseTypeId); + list.add(alternativeMenuItem); + }); + + return list; + } + + List getExerciseTypeAlternatives(int exerciseTypeId) { + if (exerciseTypeId == null || exerciseTypeId <= 0) { + return null; + } + List list = List(); + Cache().getExerciseTypes().forEach((exerciseType) { + if (exerciseType.alternatives.isNotEmpty) { + exerciseType.alternatives.forEach((childId) { + if (childId == exerciseTypeId) { + list.add(exerciseType); + } + }); + } + }); + + return list; + } + void sortByMuscleType() { sortedTree = SplayTreeMap>(); tree.forEach((key, value) { diff --git a/lib/service/exercise_tree_service.dart b/lib/service/exercise_tree_service.dart index 734dfd4..26045a0 100644 --- a/lib/service/exercise_tree_service.dart +++ b/lib/service/exercise_tree_service.dart @@ -56,18 +56,16 @@ class ExerciseTreeApi with Logging { if (index > 0) { ExerciseTree newElement = element.copy(parent.exerciseTreeParentId); exerciseTree.add(newElement); - //print("ExerciseTree " + newElement.toJson().toString()); } else { element.parentId = parent.exerciseTreeParentId; + element.sort = parent.sort; exerciseTree[treeIndex].parentId = parent.exerciseTreeParentId; } index++; } }); - //print("ExerciseTree " + element.toJson().toString()); treeIndex++; }); - return exerciseTree; } diff --git a/lib/service/package_service.dart b/lib/service/package_service.dart index 44282c0..059ec5f 100644 --- a/lib/service/package_service.dart +++ b/lib/service/package_service.dart @@ -6,6 +6,7 @@ import 'package:aitrainer_app/model/customer_exercise_device.dart'; import 'package:aitrainer_app/model/customer_property.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_device.dart'; +import 'package:aitrainer_app/model/exercise_plan_template.dart'; import 'package:aitrainer_app/model/exercise_result.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise_tree_parents.dart'; @@ -53,6 +54,10 @@ class PackageApi { Cache().setExerciseTypes(exerciseTypes); } } else if (headRecord[0] == "ExerciseAbility") { + } else if (headRecord[0] == "ExercisePlanTemplate") { + final List exercisePlanTemplates = + json.map((exercisePlanTemplate) => ExercisePlanTemplate.fromJson(exercisePlanTemplate)).toList(); + Cache().setExercisePlanTemplates(exercisePlanTemplates); } else if (headRecord[0] == "ExerciseTreeParents") { exerciseTreeParents = json.map((exerciseTreeParent) => ExerciseTreeParents.fromJson(exerciseTreeParent)).toList(); } diff --git a/lib/view/test_set_edit.dart b/lib/view/test_set_edit.dart index 3090bd0..61a4442 100644 --- a/lib/view/test_set_edit.dart +++ b/lib/view/test_set_edit.dart @@ -1,20 +1,257 @@ -import 'package:aitrainer_app/widgets/app_bar.dart'; -import 'package:flutter/material.dart'; +import 'dart:convert'; -class TestSetEdit extends StatelessWidget { +import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; +import 'package:aitrainer_app/bloc/test_set_edit/test_set_edit_bloc.dart'; +import 'package:aitrainer_app/library/custom_icon_icons.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; +import 'package:aitrainer_app/util/trans.dart'; +import 'package:aitrainer_app/widgets/app_bar.dart'; +import 'package:aitrainer_app/widgets/dialog_common.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:modal_progress_hud/modal_progress_hud.dart'; +import 'package:transparent_image/transparent_image.dart'; +import 'package:aitrainer_app/library/image_cache.dart' as wt; + +// ignore: must_be_immutable +class TestSetEdit extends StatelessWidget with Trans { @override Widget build(BuildContext context) { + final String templateName = ModalRoute.of(context).settings.arguments; + // ignore: close_sinks + final MenuBloc menuBloc = BlocProvider.of(context); + TestSetEditBloc bloc; + + setContext(context); return Scaffold( - appBar: AppBarNav(depth: 1), - body: Container( - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('asset/image/WT_black_background.jpg'), - fit: BoxFit.cover, - alignment: Alignment.center, - ), + appBar: AppBarNav(depth: 1), + body: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_black_background.jpg'), + fit: BoxFit.cover, + alignment: Alignment.center, ), - child: Text("H"))); + ), + child: BlocProvider( + create: (context) => + TestSetEditBloc(templateName: templateName, workoutTreeRepository: menuBloc.menuTreeRepository, menuBloc: menuBloc), + child: BlocConsumer(listener: (context, state) { + if (state is TestSetEditError) { + Scaffold.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); + } else if (state is TestSetEditSaved) { + Navigator.of(context).pushNamed("home"); + } + }, builder: (context, state) { + bloc = BlocProvider.of(context); + return ModalProgressHUD( + child: getTestSetWidget(bloc, templateName), + inAsyncCall: state is TestSetEditLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + }))), + floatingActionButton: FloatingActionButton.extended( + onPressed: () => showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DialogCommon( + title: "Start!", + descriptions: "GO", + text: "OK", + onTap: () => { + Navigator.of(context).pop(), + if (bloc != null) + { + bloc.add(TestSetEditSubmit()), + } + }, + onCancel: () => {Navigator.of(context).pop()}, + ); + }), + backgroundColor: Colors.orange[800], + icon: Icon(CustomIcon.clock), + label: Text( + "Start training", + style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + ); + } + + Widget getTestSetWidget(TestSetEditBloc bloc, String templateName) { + return Container( + padding: EdgeInsets.only(left: 10, right: 10), + child: CustomScrollView(scrollDirection: Axis.vertical, slivers: [ + SliverAppBar( + toolbarHeight: 135, + automaticallyImplyLeading: false, + backgroundColor: Colors.transparent, + title: Column(mainAxisAlignment: MainAxisAlignment.start, children: [ + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: GoogleFonts.archivoBlack( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 12.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], + ), + children: [ + TextSpan(text: t("Edit Your Training Test Set"), style: GoogleFonts.inter(color: Colors.white, fontSize: 18)), + TextSpan(text: "\n", style: GoogleFonts.inter(color: Colors.white, fontSize: 18)), + TextSpan(text: t(templateName), style: GoogleFonts.archivoBlack(color: Colors.yellow[300], fontSize: 18)), + ])), + Divider( + color: Colors.transparent, + ), + GestureDetector( + onTap: () => showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DialogCommon( + title: "Own Body", + descriptions: t("execute these exercises with maximum repeats. Leave at least 3 min rest time between time"), + text: "OK", + onTap: () => {Navigator.of(context).pop()}, + onCancel: () => {Navigator.of(context).pop()}, + ); + }), + child: Icon( + CustomIcon.question_circle, + color: Colors.orange[400], + size: 40, + )), + ]), + centerTitle: true, + ), + SliverList(delegate: SliverChildListDelegate(getExerciseTypeWidgets(bloc))), + ])); + } + + List imageSliders(List alternatives, MenuBloc menuBloc) { + final List list = List(); + alternatives.forEach((element) { + list.add(getImageStack(element, menuBloc)); + }); + list.add(Container( + padding: EdgeInsets.only(top: 25, bottom: 25), + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.red[600], + child: Center( + child: Text( + "X", + style: GoogleFonts.archivoBlack( + color: Colors.white, + fontSize: 80, + ), + ), + ))))); + + return list; + } + + Stack getImageStack(WorkoutMenuTree element, MenuBloc menuBloc) { + print(element.toJson()); + return Stack(alignment: Alignment.bottomLeft, children: [ + _getButtonImage(element, menuBloc), + Container( + padding: EdgeInsets.only(left: 15, bottom: 15, right: 15), + child: Text( + element.name, + maxLines: 4, + style: GoogleFonts.archivoBlack(color: element.color, fontSize: 16, height: 1.1), + ), + ), + ]); + } + + List getExerciseTypeWidgets(TestSetEditBloc bloc) { + final List widgets = List(); + bloc.exerciseTypes.forEach((element) { + final WorkoutMenuTree workoutTree = bloc.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(element.exerciseTypeId); + + final List alternativeMenuItems = bloc.menuBloc.menuTreeRepository.getWorkoutTreeAlternatives(workoutTree); + if (workoutTree != null && alternativeMenuItems != null) { + final Widget widget = CarouselSlider( + options: CarouselOptions( + viewportFraction: 0.80, + reverse: false, + enableInfiniteScroll: false, + autoPlay: false, + aspectRatio: 2, + enlargeCenterPage: true, + onPageChanged: (index, reason) => + bloc.add(TestSetEditChangeExerciseType(index: index, exerciseTypeId: element.exerciseTypeId)), + enlargeStrategy: CenterPageEnlargeStrategy.scale), + items: imageSliders(alternativeMenuItems, bloc.menuBloc), + ); + widgets.add(widget); + } + }); + return widgets; + } + + Widget _getButtonImage(WorkoutMenuTree workoutTree, MenuBloc menuBloc) { + if (workoutTree == null) { + return Offstage(); + } + String imageString = menuBloc.getImage(workoutTree.id, workoutTree.imageName); + Widget widget; + if (imageString != null) { + widget = ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.transparent, + child: FadeInImage( + fadeInDuration: Duration(milliseconds: 100), + image: MemoryImage(base64Decode(imageString)), + placeholder: MemoryImage(kTransparentImage), + ), + )); + } else { + if (workoutTree.imageName.contains("https")) { + if (!wt.ImageCache().existsImageInMap(workoutTree.id, workoutTree.imageName)) { + widget = ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.transparent, + child: FadeInImage( + fadeInDuration: Duration(milliseconds: 500), + image: NetworkImage(workoutTree.imageName), + placeholder: MemoryImage(kTransparentImage), + ), + )); + } + } else { + widget = ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.transparent, + child: Image.asset(workoutTree.imageName), + )); + } + } + return widget; } } diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index ffac545..8b1886e 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -14,6 +14,7 @@ import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:badges/badges.dart'; +import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; @@ -24,7 +25,6 @@ import 'package:transparent_image/transparent_image.dart'; import 'package:aitrainer_app/library/image_cache.dart' as wt; import 'dialog_html.dart'; -import 'menu_info_widget.dart'; // ignore: must_be_immutable class MenuPageWidget extends StatefulWidget { @@ -39,14 +39,24 @@ class MenuPageWidget extends StatefulWidget { class _MenuPageWidgetState extends State with Trans, Logging { final double baseWidth = 312; final double baseHeight = 675.2; - bool isFirst = true; - bool wait = false; MenuBloc menuBloc; final scrollController = ScrollController(); + final bool activeExercisePlan = Cache().activeExercisePlan != null; + final EzAnimation animation = EzAnimation(35.0, 10.0, Duration(seconds: 2), reverseCurve: Curves.linear); @override void initState() { - isFirst = true; + if (activeExercisePlan) { + animation.start(); + animation.addStatusListener((status) { + if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) { + animation.reverse(); + } + }); + animation.addListener(() { + setState(() {}); + }); + } /// We require the initializers to run after the loading screen is rendered SchedulerBinding.instance.addPostFrameCallback((_) { @@ -88,7 +98,8 @@ class _MenuPageWidgetState extends State with Trans, Logging { padding: EdgeInsets.only(top: 15.0), child: Center( child: Stack(alignment: Alignment.bottomLeft, children: [ - Text(t("All Exercises has been filtered out"), style: GoogleFonts.inter(color: Colors.white)), + Text(AppLocalizations.of(context).translate("All Exercises has been filtered out"), + style: GoogleFonts.inter(color: Colors.white)), ])))); } else { menuBloc.getFilteredBranch(menuBloc.parent).forEach((treeName, value) { @@ -103,7 +114,7 @@ class _MenuPageWidgetState extends State with Trans, Logging { animationType: BadgeAnimationType.slide, badgeColor: Colors.orange[800], showBadge: workoutTree.base, - badgeContent: Text(t("base"), + badgeContent: Text(AppLocalizations.of(context).translate("base"), style: TextStyle( color: Colors.white, fontSize: 12, @@ -120,7 +131,9 @@ class _MenuPageWidgetState extends State with Trans, Logging { builder: (BuildContext context) { return DialogHTML( title: workoutTree.name, - htmlData: workoutTree.nameEnglish == "Endurance" ? t("Endurance_desc") : t("OneRepMax_desc"), + htmlData: workoutTree.nameEnglish == "Endurance" + ? AppLocalizations.of(context).translate("Endurance_desc") + : AppLocalizations.of(context).translate("OneRepMax_desc"), ); }) }, @@ -171,7 +184,7 @@ class _MenuPageWidgetState extends State with Trans, Logging { delegate: SliverChildListDelegate([ Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - t("Equipment Filter"), + AppLocalizations.of(context).translate("Equipment Filter"), textAlign: TextAlign.center, style: GoogleFonts.archivoBlack( fontSize: 24, @@ -241,6 +254,7 @@ class _MenuPageWidgetState extends State with Trans, Logging { menuBloc.setMenuInfo(); SliverAppBar sliverAppBar = SliverAppBar( + automaticallyImplyLeading: false, backgroundColor: Colors.transparent, title: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ SizedBox( @@ -283,6 +297,35 @@ class _MenuPageWidgetState extends State with Trans, Logging { } }, ), + activeExercisePlan + ? GestureDetector( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + title: t("You have an acive Compact Test"), + descriptions: t("Press OK to continue!"), + text: "OK", + onTap: () => { + Navigator.of(context).pop(), + }, + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }), + child: AnimatedBuilder( + animation: animation, + builder: (context, snapshot) { + return Center( + child: Container( + width: animation.value, + height: animation.value, + child: Image.asset("asset/image/pict_reps_volumen_db.png"), + ), + ); + })) + : Offstage(), SizedBox( width: 10, ), @@ -292,6 +335,9 @@ class _MenuPageWidgetState extends State with Trans, Logging { void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc, BuildContext context) { if (workoutTree.child == false) { + if (ExerciseAbility.mini_test.equalsTo(menuBloc.ability) && workoutTree.parent != 0) { + Navigator.of(context).pushNamed('testSetEdit', arguments: workoutTree.nameEnglish); + } menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id)); } else { menuBloc.add(MenuClickExercise(exerciseTypeId: workoutTree.id)); diff --git a/pubspec.lock b/pubspec.lock index 05e872f..dbecfce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -141,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.1.0" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" characters: dependency: transitive description: @@ -274,6 +281,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.5" + ezanimation: + dependency: "direct main" + description: + name: ezanimation + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 763d7e8..3413689 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.9+59 +version: 1.1.10+60 environment: sdk: ">=2.7.0 <3.0.0" @@ -54,6 +54,7 @@ dependencies: liquid_progress_indicator: ^0.3.2 dropdown_search: ^0.4.9 audioplayer: ^0.8.1 + ezanimation: ^0.4.1 firebase_core: ^0.5.0 @@ -66,6 +67,7 @@ dependencies: crypto: ^2.1.5 transparent_image: ^1.0.0 #auto_animated: ^2.1.0 + carousel_slider: ^3.0.0 flurry: ^0.0.7 @@ -299,10 +301,12 @@ flutter: - asset/menu/lying_scissors.jpg - asset/menu/lying_triceps_extension.jpg - asset/menu/machine_shoulder_press.jpg + - asset/menu/machine_test.jpg - asset/menu/oblique_crunch.jpg - asset/menu/olympic_squat.jpg - asset/menu/one_arm_row.jpg - asset/menu/overhead_dumbbell_triceps_extension.jpg + - asset/menu/own_body.jpg - asset/menu/peck_deck_flyes.jpg - asset/menu/plank.jpg - asset/menu/pull_up.jpg @@ -348,16 +352,21 @@ flutter: - asset/menu/stiff_legged_deadlift.jpg - asset/menu/straight-arm_rope_pull-down.jpg - asset/menu/t_bar_rows.jpg + - asset/menu/test_center.jpg - asset/menu/thigh_adductor.jpg - asset/menu/triceps_extension_on_cable_with_rope.jpg - asset/menu/triceps_kickback.jpg - asset/menu/triceps_pushdown.jpg - asset/menu/twisted_crunches.jpg + - asset/menu/under_body.jpg + - asset/menu/upper_body.jpg - asset/menu/v_ups.jpg - asset/menu/wall_sit.jpg + - asset/menu/weight_test.jpg - asset/menu/weighted_bench_dip.jpg - asset/menu/wide_grip_behind_the_neck_pull_ups.jpg - asset/menu/wide_grip_front_lat_pulldown.jpg + - asset/wine-glass.mp3