WT1.1.10+1 Compact Test Edit

This commit is contained in:
bossanyit 2021-03-09 15:49:15 +01:00
parent 9c84714822
commit 2e0d7fc37d
26 changed files with 656 additions and 40 deletions

BIN
asset/menu/machine_test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
asset/menu/own_body.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
asset/menu/test_center.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
asset/menu/under_body.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
asset/menu/upper_body.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
asset/menu/weight_test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -388,7 +388,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = SFJJBDCU6Z; DEVELOPMENT_TEAM = SFJJBDCU6Z;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -405,7 +405,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 1.1.7; MARKETING_VERSION = 1.1.10;
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -531,7 +531,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = SFJJBDCU6Z; DEVELOPMENT_TEAM = SFJJBDCU6Z;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -548,7 +548,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 1.1.7; MARKETING_VERSION = 1.1.10;
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -566,7 +566,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = SFJJBDCU6Z; DEVELOPMENT_TEAM = SFJJBDCU6Z;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -583,7 +583,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 1.1.7; MARKETING_VERSION = 1.1.10;
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View File

@ -173,6 +173,11 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
ability = ExerciseAbility.endurance; ability = ExerciseAbility.endurance;
break; break;
case "Cardio": case "Cardio":
ability = ExerciseAbility.running;
break;
case "Test Center":
ability = ExerciseAbility.mini_test;
break;
case "My Body": case "My Body":
ability = ExerciseAbility.none; ability = ExerciseAbility.none;
break; break;

View File

@ -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<TestSetEditEvent, TestSetEditState> {
final String templateName;
final WorkoutTreeRepository workoutTreeRepository;
final MenuBloc menuBloc;
final List<ExerciseType> _exerciseTypes = List();
final HashMap<int, ExerciseType> _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<TestSetEditState> mapEventToState(TestSetEditEvent event) async* {
try {
if (event is TestSetEditChangeExerciseType) {
final List<ExerciseType> 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<ExercisePlanDetail> 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;
}

View File

@ -0,0 +1,33 @@
part of 'test_set_edit_bloc.dart';
abstract class TestSetEditEvent extends Equatable {
const TestSetEditEvent();
@override
List<Object> 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<Object> get props => [index, exerciseTypeId];
}
class TestSetEditSkipExerciseType extends TestSetEditEvent {
final int exerciseTypeId;
const TestSetEditSkipExerciseType({this.exerciseTypeId});
@override
List<Object> get props => [exerciseTypeId];
}
class TestSetEditSubmit extends TestSetEditEvent {
const TestSetEditSubmit();
}

View File

@ -0,0 +1,32 @@
part of 'test_set_edit_bloc.dart';
abstract class TestSetEditState extends Equatable {
const TestSetEditState();
@override
List<Object> 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<Object> get props => [message];
}

View File

@ -1,7 +1,9 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:convert';
import 'package:aitrainer_app/model/customer.dart'; import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/model/exercise_plan.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_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_tree.dart';
import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/model_change.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 hardwareKey = 'hardware';
static final String loginTypeKey = 'login_type'; static final String loginTypeKey = 'login_type';
static final String timerDisplayKey = 'timer_display'; 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 String baseUrl = 'http://aitrainer.info:8888/api/';
static final String mediaUrl = 'https://aitrainer.info:4343/media/'; static final String mediaUrl = 'https://aitrainer.info:4343/media/';
@ -89,6 +93,10 @@ class Cache with Logging {
List<wt_product.Product> _products; List<wt_product.Product> _products;
List<Purchase> _purchases = List(); List<Purchase> _purchases = List();
List<ProductTest> _productTests; List<ProductTest> _productTests;
List<ExercisePlanTemplate> _exercisePlanTemplates = List();
ExercisePlan activeExercisePlan;
List<ExercisePlanDetail> activeExercisePlanDetails;
List<ExerciseDevice> _devices; List<ExerciseDevice> _devices;
List<CustomerExerciseDevice> _customerDevices; List<CustomerExerciseDevice> _customerDevices;
@ -132,6 +140,38 @@ class Cache with Logging {
return this.authToken; return this.authToken;
} }
Future<void> saveActiveExercisePlan(ExercisePlan exercisePlan, List<ExercisePlanDetail> exercisePlanDetails) async {
this.activeExercisePlan = exercisePlan;
this.activeExercisePlanDetails = exercisePlanDetails;
String exercisePlanJson = JsonEncoder().convert(exercisePlan.toJson());
String detailsJson = jsonEncode(exercisePlanDetails);
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
SharedPreferences sharedPreferences;
sharedPreferences = await prefs;
sharedPreferences.setString(Cache.activeExercisePlanKey, exercisePlanJson);
sharedPreferences.setString(Cache.activeExercisePlanDetailsKey, detailsJson);
}
Future<void> getActiveExercisePlan() async {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
SharedPreferences sharedPreferences;
sharedPreferences = await prefs;
String exercisePlanJson = sharedPreferences.getString(Cache.activeExercisePlanKey);
if (exercisePlanJson != null) {
final Map<String, dynamic> 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<void> setServer(bool live) async { Future<void> setServer(bool live) async {
if (this.testEnvironment == "1") { if (this.testEnvironment == "1") {
liveServer = false; liveServer = false;
@ -146,7 +186,6 @@ class Cache with Logging {
void getHardware(SharedPreferences prefs) { void getHardware(SharedPreferences prefs) {
final bool hasHardware = prefs.getBool(Cache.hardwareKey); final bool hasHardware = prefs.getBool(Cache.hardwareKey);
//log("Has Hardware: " + hasHardware.toString());
this.hasHardware = hasHardware; this.hasHardware = hasHardware;
if (hasHardware == null) { if (hasHardware == null) {
this.hasHardware = false; this.hasHardware = false;
@ -301,9 +340,7 @@ class Cache with Logging {
this._exerciseTree = exerciseTree; this._exerciseTree = exerciseTree;
} }
void setExercises(List<Exercise> exercises) { void setExercises(List<Exercise> exercises) => this._exercises = exercises;
this._exercises = exercises;
}
void setExercisesTrainee(List<Exercise> exercises) { void setExercisesTrainee(List<Exercise> exercises) {
this._exercisesTrainee = exercises; this._exercisesTrainee = exercises;
@ -485,10 +522,10 @@ class Cache with Logging {
Future<void> initCustomer(int customerId) async { Future<void> initCustomer(int customerId) async {
log(" *** initCustomer"); log(" *** initCustomer");
await PackageApi().getCustomerPackage(customerId); await PackageApi().getCustomerPackage(customerId);
Flurry.setUserId(customerId.toString()); Flurry.setUserId(customerId.toString());
await setLoginTypeFromPrefs(); await setLoginTypeFromPrefs();
await getActiveExercisePlan();
Cache().startPage = "home"; Cache().startPage = "home";
Track().track(TrackingEvent.enter); Track().track(TrackingEvent.enter);
} }
@ -498,4 +535,7 @@ class Cache with Logging {
LoginType getLoginType() => loginType; LoginType getLoginType() => loginType;
void setLoginType(LoginType type) => this.loginType = type; void setLoginType(LoginType type) => this.loginType = type;
List get exercisePlanTemplates => this._exercisePlanTemplates;
setExercisePlanTemplates(value) => this._exercisePlanTemplates = value;
} }

View File

@ -1,4 +1,4 @@
enum ExerciseAbility { oneRepMax, endurance, running, none } enum ExerciseAbility { oneRepMax, endurance, running, mini_test, none }
extension ExerciseAbilityExt on ExerciseAbility { extension ExerciseAbilityExt on ExerciseAbility {
bool equalsTo(ExerciseAbility ability) => this.toString() == ability.toString(); bool equalsTo(ExerciseAbility ability) => this.toString() == ability.toString();

View File

@ -8,6 +8,8 @@ class ExercisePlan {
bool private; bool private;
DateTime dateAdd; DateTime dateAdd;
DateTime dateUpd; DateTime dateUpd;
String type;
int exercisePlanTemplateId;
ExercisePlan(String name, int customerId) { ExercisePlan(String name, int customerId) {
this.customerId = customerId; this.customerId = customerId;
@ -23,6 +25,8 @@ class ExercisePlan {
this.description = json['description']; this.description = json['description'];
this.dateAdd = json['dateAdd'] == null ? null : DateTime.parse(json['dateAdd']); this.dateAdd = json['dateAdd'] == null ? null : DateTime.parse(json['dateAdd']);
this.dateUpd = json['dateUpd'] == null ? null : DateTime.parse(json['dateUpd']); this.dateUpd = json['dateUpd'] == null ? null : DateTime.parse(json['dateUpd']);
this.type = json['type'];
this.exercisePlanTemplateId = json['exercisePlanTemplateId'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -40,6 +44,8 @@ class ExercisePlan {
"private": private, "private": private,
"dateAdd": formattedDateAdd, "dateAdd": formattedDateAdd,
"dateUpd": formattedDateUpd, "dateUpd": formattedDateUpd,
"type": type,
"exercisePlanTemplateId": exercisePlanTemplateId
}; };
} else { } else {
return { return {
@ -50,6 +56,8 @@ class ExercisePlan {
"private": private, "private": private,
"dateAdd": formattedDateAdd, "dateAdd": formattedDateAdd,
"dateUpd": formattedDateUpd, "dateUpd": formattedDateUpd,
"type": type,
"exercisePlanTemplateId": exercisePlanTemplateId
}; };
} }
} }

View File

@ -0,0 +1,37 @@
class ExercisePlanTemplate {
int exercisePlanTemplateId;
String name;
String description;
String templateType;
String nameTranslation;
String descriptionTranslation;
List<int> 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<String, dynamic> toJson() {
return {
"exercisePlanTemplateId": exercisePlanTemplateId,
"name": name,
"description": "description",
"templateType": templateType,
"nameTranslation": nameTranslation,
"descriptionTranslation": descriptionTranslation,
"exerciseTypes": exerciseTypes.toString()
};
}
}

View File

@ -5,6 +5,7 @@ class ExerciseTree {
String imageUrl; String imageUrl;
bool active; bool active;
String nameTranslation; String nameTranslation;
int sort;
ExerciseTree(); ExerciseTree();
@ -25,6 +26,7 @@ class ExerciseTree {
"imageUrl": imageUrl, "imageUrl": imageUrl,
"active": active.toString(), "active": active.toString(),
"nameTranslation": nameTranslation, "nameTranslation": nameTranslation,
"sort": sort,
}; };
} }
@ -38,6 +40,7 @@ class ExerciseTree {
newTree.parentId = parentId; newTree.parentId = parentId;
} }
newTree.active = this.active; newTree.active = this.active;
newTree.sort = this.sort;
return newTree; return newTree;
} }

View File

@ -2,10 +2,12 @@ class ExerciseTreeParents {
int exerciseTreeParentsId; int exerciseTreeParentsId;
int exerciseTreeParentId; int exerciseTreeParentId;
int exerciseTreeChildId; int exerciseTreeChildId;
int sort;
ExerciseTreeParents.fromJson(Map json) { ExerciseTreeParents.fromJson(Map json) {
this.exerciseTreeParentsId = json['exerciseTreeParentsId']; this.exerciseTreeParentsId = json['exerciseTreeParentsId'];
this.exerciseTreeParentId = json['exerciseTreeParentId']; this.exerciseTreeParentId = json['exerciseTreeParentId'];
this.exerciseTreeChildId = json['exerciseTreeChildId']; this.exerciseTreeChildId = json['exerciseTreeChildId'];
this.sort = json['sort'];
} }
} }

View File

@ -17,6 +17,7 @@ class ExerciseType {
String descriptionTranslation = ""; String descriptionTranslation = "";
List<int> devices = List(); List<int> devices = List();
List<int> parents = List(); List<int> parents = List();
List<int> alternatives = List();
ExerciseAbility ability; ExerciseAbility ability;
@ -55,6 +56,14 @@ class ExerciseType {
this.parents.add(parent['exerciseTreeId']); this.parents.add(parent['exerciseTreeId']);
}); });
} }
if (json['alternatives'].length > 0) {
final List jsonAlternatives = json['alternatives'];
jsonAlternatives.forEach((alternative) {
this.alternatives.add(alternative['exerciseTypeChildId']);
});
}
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {

View File

@ -40,6 +40,7 @@ class WorkoutMenuTree {
String nameEnglish; String nameEnglish;
String parentName; String parentName;
String parentNameEnglish; String parentNameEnglish;
int sort;
WorkoutMenuTree( WorkoutMenuTree(
this.id, this.id,
@ -57,7 +58,8 @@ class WorkoutMenuTree {
this.isRunning, this.isRunning,
this.nameEnglish, this.nameEnglish,
this.parentName, this.parentName,
this.parentNameEnglish); this.parentNameEnglish,
this.sort);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
@ -73,6 +75,7 @@ class WorkoutMenuTree {
"is1RM": is1RM.toString(), "is1RM": is1RM.toString(),
"isEndurance": isEndurance.toString(), "isEndurance": isEndurance.toString(),
"isRunning": isRunning.toString(), "isRunning": isRunning.toString(),
"sort": sort,
}; };
} }

View File

@ -103,7 +103,8 @@ class WorkoutTreeRepository with Logging {
isRunning, isRunning,
treeItem.name, treeItem.name,
parent != null ? parent.name : "", parent != null ? parent.name : "",
parent != null ? parent.nameEnglish : ""); parent != null ? parent.nameEnglish : "",
treeItem.sort);
menuItem = this.setWorkoutTypes(menuItem, treeItem); menuItem = this.setWorkoutTypes(menuItem, treeItem);
this.tree[treeItem.name + "_" + treeItem.parentId.toString()] = menuItem; this.tree[treeItem.name + "_" + treeItem.parentId.toString()] = menuItem;
//log("WorkoutMenuTree item " + menuItem.toJson().toString()); //log("WorkoutMenuTree item " + menuItem.toJson().toString());
@ -141,7 +142,8 @@ class WorkoutTreeRepository with Logging {
isRunning, isRunning,
exerciseType.name, exerciseType.name,
parent != null ? parent.name : "", parent != null ? parent.name : "",
parent != null ? parent.nameEnglish : ""); parent != null ? parent.nameEnglish : "",
0);
this.tree[exerciseType.name] = menuItem; this.tree[exerciseType.name] = menuItem;
menuAsExercise.add(menuItem); menuAsExercise.add(menuItem);
//log("WorkoutMenuTree item " + menuItem.toJson().toString()); //log("WorkoutMenuTree item " + menuItem.toJson().toString());
@ -283,6 +285,56 @@ class WorkoutTreeRepository with Logging {
return parentItem; return parentItem;
} }
WorkoutMenuTree getMenuItemByExerciseTypeId(int exerciseTypeId) {
WorkoutMenuTree menuItem;
this.menuAsExercise.forEach((element) {
if (element.exerciseTypeId == exerciseTypeId) {
menuItem = element;
}
});
return menuItem;
}
List<WorkoutMenuTree> getWorkoutTreeAlternatives(WorkoutMenuTree workoutMenuTree) {
if (workoutMenuTree == null) {
return null;
}
if (workoutMenuTree.exerciseType == null) {
return null;
}
final List<ExerciseType> alternatives = this.getExerciseTypeAlternatives(workoutMenuTree.exerciseTypeId);
if (alternatives == null) {
return null;
}
List<WorkoutMenuTree> list = List();
list.add(workoutMenuTree);
alternatives.forEach((element) {
final WorkoutMenuTree alternativeMenuItem = this.getMenuItemByExerciseTypeId(element.exerciseTypeId);
list.add(alternativeMenuItem);
});
return list;
}
List<ExerciseType> getExerciseTypeAlternatives(int exerciseTypeId) {
if (exerciseTypeId == null || exerciseTypeId <= 0) {
return null;
}
List<ExerciseType> list = List();
Cache().getExerciseTypes().forEach((exerciseType) {
if (exerciseType.alternatives.isNotEmpty) {
exerciseType.alternatives.forEach((childId) {
if (childId == exerciseTypeId) {
list.add(exerciseType);
}
});
}
});
return list;
}
void sortByMuscleType() { void sortByMuscleType() {
sortedTree = SplayTreeMap<String, List<WorkoutMenuTree>>(); sortedTree = SplayTreeMap<String, List<WorkoutMenuTree>>();
tree.forEach((key, value) { tree.forEach((key, value) {

View File

@ -56,18 +56,16 @@ class ExerciseTreeApi with Logging {
if (index > 0) { if (index > 0) {
ExerciseTree newElement = element.copy(parent.exerciseTreeParentId); ExerciseTree newElement = element.copy(parent.exerciseTreeParentId);
exerciseTree.add(newElement); exerciseTree.add(newElement);
//print("ExerciseTree " + newElement.toJson().toString());
} else { } else {
element.parentId = parent.exerciseTreeParentId; element.parentId = parent.exerciseTreeParentId;
element.sort = parent.sort;
exerciseTree[treeIndex].parentId = parent.exerciseTreeParentId; exerciseTree[treeIndex].parentId = parent.exerciseTreeParentId;
} }
index++; index++;
} }
}); });
//print("ExerciseTree " + element.toJson().toString());
treeIndex++; treeIndex++;
}); });
return exerciseTree; return exerciseTree;
} }

View File

@ -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/customer_property.dart';
import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_device.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_result.dart';
import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise_tree.dart';
import 'package:aitrainer_app/model/exercise_tree_parents.dart'; import 'package:aitrainer_app/model/exercise_tree_parents.dart';
@ -53,6 +54,10 @@ class PackageApi {
Cache().setExerciseTypes(exerciseTypes); Cache().setExerciseTypes(exerciseTypes);
} }
} else if (headRecord[0] == "ExerciseAbility") { } else if (headRecord[0] == "ExerciseAbility") {
} else if (headRecord[0] == "ExercisePlanTemplate") {
final List<ExercisePlanTemplate> exercisePlanTemplates =
json.map((exercisePlanTemplate) => ExercisePlanTemplate.fromJson(exercisePlanTemplate)).toList();
Cache().setExercisePlanTemplates(exercisePlanTemplates);
} else if (headRecord[0] == "ExerciseTreeParents") { } else if (headRecord[0] == "ExerciseTreeParents") {
exerciseTreeParents = json.map((exerciseTreeParent) => ExerciseTreeParents.fromJson(exerciseTreeParent)).toList(); exerciseTreeParents = json.map((exerciseTreeParent) => ExerciseTreeParents.fromJson(exerciseTreeParent)).toList();
} }

View File

@ -1,20 +1,257 @@
import 'package:aitrainer_app/widgets/app_bar.dart'; import 'dart:convert';
import 'package:flutter/material.dart';
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String templateName = ModalRoute.of(context).settings.arguments;
// ignore: close_sinks
final MenuBloc menuBloc = BlocProvider.of<MenuBloc>(context);
TestSetEditBloc bloc;
setContext(context);
return Scaffold( return Scaffold(
appBar: AppBarNav(depth: 1), appBar: AppBarNav(depth: 1),
body: Container( body: Container(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage('asset/image/WT_black_background.jpg'), image: AssetImage('asset/image/WT_black_background.jpg'),
fit: BoxFit.cover, fit: BoxFit.cover,
alignment: Alignment.center, alignment: Alignment.center,
),
), ),
child: Text("H"))); ),
child: BlocProvider(
create: (context) =>
TestSetEditBloc(templateName: templateName, workoutTreeRepository: menuBloc.menuTreeRepository, menuBloc: menuBloc),
child: BlocConsumer<TestSetEditBloc, TestSetEditState>(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<TestSetEditBloc>(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>[
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<Widget> imageSliders(List<WorkoutMenuTree> alternatives, MenuBloc menuBloc) {
final List<Widget> 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<Widget> getExerciseTypeWidgets(TestSetEditBloc bloc) {
final List<Widget> widgets = List();
bloc.exerciseTypes.forEach((element) {
final WorkoutMenuTree workoutTree = bloc.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(element.exerciseTypeId);
final List<WorkoutMenuTree> 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;
} }
} }

View File

@ -14,6 +14,7 @@ import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart';
import 'package:badges/badges.dart'; import 'package:badges/badges.dart';
import 'package:ezanimation/ezanimation.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/painting.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 'package:aitrainer_app/library/image_cache.dart' as wt;
import 'dialog_html.dart'; import 'dialog_html.dart';
import 'menu_info_widget.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class MenuPageWidget extends StatefulWidget { class MenuPageWidget extends StatefulWidget {
@ -39,14 +39,24 @@ class MenuPageWidget extends StatefulWidget {
class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging { class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
final double baseWidth = 312; final double baseWidth = 312;
final double baseHeight = 675.2; final double baseHeight = 675.2;
bool isFirst = true;
bool wait = false;
MenuBloc menuBloc; MenuBloc menuBloc;
final scrollController = ScrollController(); final scrollController = ScrollController();
final bool activeExercisePlan = Cache().activeExercisePlan != null;
final EzAnimation animation = EzAnimation(35.0, 10.0, Duration(seconds: 2), reverseCurve: Curves.linear);
@override @override
void initState() { 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 /// We require the initializers to run after the loading screen is rendered
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
@ -88,7 +98,8 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
padding: EdgeInsets.only(top: 15.0), padding: EdgeInsets.only(top: 15.0),
child: Center( child: Center(
child: Stack(alignment: Alignment.bottomLeft, children: [ 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 { } else {
menuBloc.getFilteredBranch(menuBloc.parent).forEach((treeName, value) { menuBloc.getFilteredBranch(menuBloc.parent).forEach((treeName, value) {
@ -103,7 +114,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
animationType: BadgeAnimationType.slide, animationType: BadgeAnimationType.slide,
badgeColor: Colors.orange[800], badgeColor: Colors.orange[800],
showBadge: workoutTree.base, showBadge: workoutTree.base,
badgeContent: Text(t("base"), badgeContent: Text(AppLocalizations.of(context).translate("base"),
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12, fontSize: 12,
@ -120,7 +131,9 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
builder: (BuildContext context) { builder: (BuildContext context) {
return DialogHTML( return DialogHTML(
title: workoutTree.name, 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<MenuPageWidget> with Trans, Logging {
delegate: SliverChildListDelegate([ delegate: SliverChildListDelegate([
Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Text( Text(
t("Equipment Filter"), AppLocalizations.of(context).translate("Equipment Filter"),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.archivoBlack( style: GoogleFonts.archivoBlack(
fontSize: 24, fontSize: 24,
@ -241,6 +254,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
menuBloc.setMenuInfo(); menuBloc.setMenuInfo();
SliverAppBar sliverAppBar = SliverAppBar( SliverAppBar sliverAppBar = SliverAppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ title: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
SizedBox( SizedBox(
@ -283,6 +297,35 @@ class _MenuPageWidgetState extends State<MenuPageWidget> 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( SizedBox(
width: 10, width: 10,
), ),
@ -292,6 +335,9 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc, BuildContext context) { void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc, BuildContext context) {
if (workoutTree.child == false) { 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)); menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id));
} else { } else {
menuBloc.add(MenuClickExercise(exerciseTypeId: workoutTree.id)); menuBloc.add(MenuClickExercise(exerciseTypeId: workoutTree.id));

View File

@ -141,6 +141,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "7.1.0" 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: characters:
dependency: transitive dependency: transitive
description: description:
@ -274,6 +281,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.5" version: "1.2.5"
ezanimation:
dependency: "direct main"
description:
name: ezanimation
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:

View File

@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.1.9+59 version: 1.1.10+60
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
@ -54,6 +54,7 @@ dependencies:
liquid_progress_indicator: ^0.3.2 liquid_progress_indicator: ^0.3.2
dropdown_search: ^0.4.9 dropdown_search: ^0.4.9
audioplayer: ^0.8.1 audioplayer: ^0.8.1
ezanimation: ^0.4.1
firebase_core: ^0.5.0 firebase_core: ^0.5.0
@ -66,6 +67,7 @@ dependencies:
crypto: ^2.1.5 crypto: ^2.1.5
transparent_image: ^1.0.0 transparent_image: ^1.0.0
#auto_animated: ^2.1.0 #auto_animated: ^2.1.0
carousel_slider: ^3.0.0
flurry: ^0.0.7 flurry: ^0.0.7
@ -299,10 +301,12 @@ flutter:
- asset/menu/lying_scissors.jpg - asset/menu/lying_scissors.jpg
- asset/menu/lying_triceps_extension.jpg - asset/menu/lying_triceps_extension.jpg
- asset/menu/machine_shoulder_press.jpg - asset/menu/machine_shoulder_press.jpg
- asset/menu/machine_test.jpg
- asset/menu/oblique_crunch.jpg - asset/menu/oblique_crunch.jpg
- asset/menu/olympic_squat.jpg - asset/menu/olympic_squat.jpg
- asset/menu/one_arm_row.jpg - asset/menu/one_arm_row.jpg
- asset/menu/overhead_dumbbell_triceps_extension.jpg - asset/menu/overhead_dumbbell_triceps_extension.jpg
- asset/menu/own_body.jpg
- asset/menu/peck_deck_flyes.jpg - asset/menu/peck_deck_flyes.jpg
- asset/menu/plank.jpg - asset/menu/plank.jpg
- asset/menu/pull_up.jpg - asset/menu/pull_up.jpg
@ -348,16 +352,21 @@ flutter:
- asset/menu/stiff_legged_deadlift.jpg - asset/menu/stiff_legged_deadlift.jpg
- asset/menu/straight-arm_rope_pull-down.jpg - asset/menu/straight-arm_rope_pull-down.jpg
- asset/menu/t_bar_rows.jpg - asset/menu/t_bar_rows.jpg
- asset/menu/test_center.jpg
- asset/menu/thigh_adductor.jpg - asset/menu/thigh_adductor.jpg
- asset/menu/triceps_extension_on_cable_with_rope.jpg - asset/menu/triceps_extension_on_cable_with_rope.jpg
- asset/menu/triceps_kickback.jpg - asset/menu/triceps_kickback.jpg
- asset/menu/triceps_pushdown.jpg - asset/menu/triceps_pushdown.jpg
- asset/menu/twisted_crunches.jpg - asset/menu/twisted_crunches.jpg
- asset/menu/under_body.jpg
- asset/menu/upper_body.jpg
- asset/menu/v_ups.jpg - asset/menu/v_ups.jpg
- asset/menu/wall_sit.jpg - asset/menu/wall_sit.jpg
- asset/menu/weight_test.jpg
- asset/menu/weighted_bench_dip.jpg - asset/menu/weighted_bench_dip.jpg
- asset/menu/wide_grip_behind_the_neck_pull_ups.jpg - asset/menu/wide_grip_behind_the_neck_pull_ups.jpg
- asset/menu/wide_grip_front_lat_pulldown.jpg - asset/menu/wide_grip_front_lat_pulldown.jpg
- asset/wine-glass.mp3 - asset/wine-glass.mp3